mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 13:38:32 +01:00
Merge branch 'MDL-80190-main' of https://github.com/ferranrecio/moodle
This commit is contained in:
commit
b91ec287dc
2
course/format/amd/build/local/content.min.js
vendored
2
course/format/amd/build/local/content.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndcmitem",["exports","core/reactiv
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value)}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){return"cm"===(null==dropdata?void 0:dropdata.type)}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default}));
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(cmid){this.id=cmid,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value)}dragStart(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("cmDrag",[dropdata.id],!1)}getDraggableData(){return this.reactive.getExporter().cmDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"!==(null==dropdata?void 0:dropdata.type))return!1;if(!0===(null==dropdata?void 0:dropdata.delegatesection)){const mycminfo=this.reactive.get("cm",this.id),mysection=this.reactive.get("section",mycminfo.sectionid);if(null!==(null==mysection?void 0:mysection.component))return!1}return!0}showDropZone(dropdata){dropdata.nextcmid!=this.id&&dropdata.id!=this.id&&this.element.classList.add(this.classes.DROPUP)}hideDropZone(){this.element.classList.remove(this.classes.DROPUP)}drop(dropdata,event){if(dropdata.id!=this.id&&dropdata.nextcmid!=this.id){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],null,this.id)}}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=dndcmitem.min.js.map
|
File diff suppressed because one or more lines are too long
@ -9,6 +9,6 @@ define("core_courseformat/local/courseeditor/dndsection",["exports","core/reacti
|
||||
* @class core_courseformat/local/courseeditor/dndsection
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core",["addfilehere"]);class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){return"files"===(null==dropdata?void 0:dropdata.type)||("cm"===(null==dropdata?void 0:dropdata.type)||"section"===(null==dropdata?void 0:dropdata.type)&&((null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.number)!=this.section.number+1))}showDropZone(dropdata){var _this$getLastCm;("files"==dropdata.type&&this.addOverlay({content:(0,_str.getString)("addfilehere","core"),icon:_templates.default.renderPix("t/download","core")}).then((()=>{var _this$dragdrop;null!==(_this$dragdrop=this.dragdrop)&&void 0!==_this$dragdrop&&_this$dragdrop.isDropzoneVisible()||this.removeOverlay()})).catch((error=>{throw error})),"cm"==dropdata.type)&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN),this.removeOverlay()}drop(dropdata,event){if("files"!=dropdata.type){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMoveAfter",[dropdata.id],this.id)}else this.reactive.uploadFiles(this.section.id,this.section.number,dropdata.files)}}return _exports.default=_default,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core",["addfilehere"]);class _default extends _reactive.BaseComponent{configState(state){this.id=this.element.dataset.id,this.section=state.section.get(this.id),this.course=state.course}configDragDrop(sectionitem){this.reactive.isEditing&&this.reactive.supportComponents&&(this.sectionitem=sectionitem,this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.sectionitem&&this.sectionitem.unregister(),void 0!==this.dragdrop&&this.dragdrop.unregister()}getLastCm(){return null}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}validateDropData(dropdata){return"files"===(null==dropdata?void 0:dropdata.type)||("cm"===(null==dropdata?void 0:dropdata.type)?null===(_this$section=this.section)||void 0===_this$section||!_this$section.component||!0!==(null==dropdata?void 0:dropdata.delegatesection):"section"===(null==dropdata?void 0:dropdata.type)&&((null==dropdata?void 0:dropdata.id)!=this.id&&(null==dropdata?void 0:dropdata.number)!=this.section.number+1));var _this$section}showDropZone(dropdata){var _this$getLastCm;("files"==dropdata.type&&this.addOverlay({content:(0,_str.getString)("addfilehere","core"),icon:_templates.default.renderPix("t/download","core")}).then((()=>{var _this$dragdrop;null!==(_this$dragdrop=this.dragdrop)&&void 0!==_this$dragdrop&&_this$dragdrop.isDropzoneVisible()||this.removeOverlay()})).catch((error=>{throw error})),"cm"==dropdata.type)&&(null===(_this$getLastCm=this.getLastCm())||void 0===_this$getLastCm||_this$getLastCm.classList.add(this.classes.DROPDOWN));"section"==dropdata.type&&(this.element.classList.remove(this.classes.DROPUP),this.element.classList.add(this.classes.DROPDOWN))}hideDropZone(){var _this$getLastCm2;null===(_this$getLastCm2=this.getLastCm())||void 0===_this$getLastCm2||_this$getLastCm2.classList.remove(this.classes.DROPDOWN),this.element.classList.remove(this.classes.DROPUP),this.element.classList.remove(this.classes.DROPDOWN),this.removeOverlay()}drop(dropdata,event){if("files"!=dropdata.type){if("cm"==dropdata.type){const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id)}"section"==dropdata.type&&this.reactive.dispatch("sectionMoveAfter",[dropdata.id],this.id)}else this.reactive.uploadFiles(this.section.id,this.section.number,dropdata.files)}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=dndsection.min.js.map
|
File diff suppressed because one or more lines are too long
@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/re
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section;const firstcmid=null===(_this$section=this.section)||void 0===_this$section?void 0:_this$section.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section2;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0])}}}return _exports.default=_default,_exports.default}));
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section,_this$section2;if(null!==(_this$section=this.section)&&void 0!==_this$section&&_this$section.component&&!0===(null==dropdata?void 0:dropdata.delegatesection))return!1;const firstcmid=null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section3;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section3=this.section)||void 0===_this$section3?void 0:_this$section3.cmlist[0])}}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=dndsectionitem.min.js.map
|
File diff suppressed because one or more lines are too long
@ -8,6 +8,6 @@ define("core_courseformat/local/courseeditor/exporter",["exports"],(function(_ex
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig,_state$course$section;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default}));
|
||||
class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return this.listedSectionIds(state).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}listedSectionIds(state){var _state$course$section;return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).filter((sectionid=>{var _state$section$get2;return null===(null!==(_state$section$get2=state.section.get(sectionid))&&void 0!==_state$section$get2?_state$section$get2:{}).component}))}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,delegatesection:cminfo.delegatesection,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=exporter.min.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -450,14 +450,14 @@ export default class Component extends BaseComponent {
|
||||
* Refresh the section list.
|
||||
*
|
||||
* @param {Object} param
|
||||
* @param {Object} param.element details the update details.
|
||||
* @param {Object} param.state the full state object.
|
||||
*/
|
||||
_refreshCourseSectionlist({element}) {
|
||||
_refreshCourseSectionlist({state}) {
|
||||
// If we have a section return means we only show a single section so no need to fix order.
|
||||
if (this.reactive.sectionReturn !== null) {
|
||||
return;
|
||||
}
|
||||
const sectionlist = element.sectionlist ?? [];
|
||||
const sectionlist = this.reactive.getExporter().listedSectionIds(state);
|
||||
const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);
|
||||
// For now section cannot be created at a frontend level.
|
||||
const createSection = this._createSectionItem.bind(this);
|
||||
|
@ -102,7 +102,18 @@ export default class extends BaseComponent {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateDropData(dropdata) {
|
||||
return dropdata?.type === 'cm';
|
||||
if (dropdata?.type !== 'cm') {
|
||||
return false;
|
||||
}
|
||||
// Prevent delegated sections loops.
|
||||
if (dropdata?.delegatesection === true) {
|
||||
const mycminfo = this.reactive.get('cm', this.id);
|
||||
const mysection = this.reactive.get('section', mycminfo.sectionid);
|
||||
if (mysection?.component !== null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,8 +115,11 @@ export default class extends BaseComponent {
|
||||
if (dropdata?.type === 'files') {
|
||||
return true;
|
||||
}
|
||||
// We accept any course module.
|
||||
// We accept any course module unless it can form a subsection loop.
|
||||
if (dropdata?.type === 'cm') {
|
||||
if (this.section?.component && dropdata?.delegatesection === true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// We accept any section but yourself and the next one.
|
||||
|
@ -121,6 +121,10 @@ export default class extends BaseComponent {
|
||||
validateDropData(dropdata) {
|
||||
// Course module validation.
|
||||
if (dropdata?.type === 'cm') {
|
||||
// Prevent content loops with subsections.
|
||||
if (this.section?.component && dropdata?.delegatesection === true) {
|
||||
return false;
|
||||
}
|
||||
// The first section element is already there so we can ignore it.
|
||||
const firstcmid = this.section?.cmlist[0];
|
||||
return dropdata.id !== firstcmid;
|
||||
|
@ -51,7 +51,7 @@ export default class {
|
||||
editmode: this.reactive.isEditing,
|
||||
highlighted: state.course.highlighted ?? '',
|
||||
};
|
||||
const sectionlist = state.course.sectionlist ?? [];
|
||||
const sectionlist = this.listedSectionIds(state);
|
||||
sectionlist.forEach(sectionid => {
|
||||
const sectioninfo = state.section.get(sectionid) ?? {};
|
||||
const section = this.section(state, sectioninfo);
|
||||
@ -62,6 +62,20 @@ export default class {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IDs of the sections that are listed as regular sections.
|
||||
* @param {Object} state the current state.
|
||||
* @returns {Number[]} the list of section ids that are listed.
|
||||
*/
|
||||
listedSectionIds(state) {
|
||||
const fullSectionList = state.course.sectionlist ?? [];
|
||||
return fullSectionList.filter(sectionid => {
|
||||
const sectioninfo = state.section.get(sectionid) ?? {};
|
||||
// Delegated sections (controlled by a component) are not listed in course.
|
||||
return sectioninfo.component === null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a section export data from the state.
|
||||
*
|
||||
@ -130,6 +144,7 @@ export default class {
|
||||
id: cminfo.id,
|
||||
name: cminfo.name,
|
||||
sectionid: cminfo.sectionid,
|
||||
delegatesection: cminfo.delegatesection,
|
||||
nextcmid,
|
||||
};
|
||||
}
|
||||
|
@ -293,10 +293,10 @@ export default class Component extends BaseComponent {
|
||||
* Refresh the section list.
|
||||
*
|
||||
* @param {object} param
|
||||
* @param {Object} param.element
|
||||
* @param {Object} param.state
|
||||
*/
|
||||
_refreshCourseSectionlist({element}) {
|
||||
const sectionlist = element.sectionlist ?? [];
|
||||
_refreshCourseSectionlist({state}) {
|
||||
const sectionlist = this.reactive.getExporter().listedSectionIds(state);
|
||||
this._fixOrder(this.element, sectionlist, this.sections);
|
||||
}
|
||||
|
||||
|
@ -172,6 +172,10 @@ class sectionactions extends baseactions {
|
||||
|
||||
/**
|
||||
* Create course sections if they are not created yet.
|
||||
*
|
||||
* The calculations will ignore sections delegated to components.
|
||||
* If the section is created, all delegated sections will be pushed down.
|
||||
*
|
||||
* @param int[] $sectionnums the section numbers to create
|
||||
* @return bool whether any section was created
|
||||
*/
|
||||
|
@ -187,7 +187,6 @@ class content implements named_templatable, renderable {
|
||||
$modinfo->get_section_info_by_id($singlesectionid),
|
||||
];
|
||||
}
|
||||
|
||||
return $modinfo->get_section_info_all();
|
||||
return $modinfo->get_listed_section_info_all();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ namespace core_courseformat\output\local\state;
|
||||
|
||||
use core_courseformat\base as course_format;
|
||||
use completion_info;
|
||||
use core_courseformat\sectiondelegate;
|
||||
use renderer_base;
|
||||
use section_info;
|
||||
use cm_info;
|
||||
@ -94,6 +95,8 @@ class cm implements renderable {
|
||||
'groupmode' => $cm->groupmode,
|
||||
'module' => $cm->modname,
|
||||
'plugin' => 'mod_' . $cm->modname,
|
||||
// Activities with delegate section has some restriction to prevent structure loops.
|
||||
'delegatesection' => sectiondelegate::has_delegate_class('mod_'.$cm->modname),
|
||||
];
|
||||
|
||||
// Check the user access type to this cm.
|
||||
|
@ -89,6 +89,8 @@ class section implements renderable {
|
||||
'contentcollapsed' => $contentcollapsed,
|
||||
'hasrestrictions' => $this->get_has_restrictions(),
|
||||
'bulkeditable' => $this->is_bulk_editable(),
|
||||
'component' => $section->component,
|
||||
'itemid' => $section->itemid,
|
||||
];
|
||||
|
||||
if (empty($modinfo->sections[$section->section])) {
|
||||
|
@ -49,10 +49,32 @@ abstract class sectiondelegate {
|
||||
if (empty($sectioninfo->component)) {
|
||||
return null;
|
||||
}
|
||||
$classname = $sectioninfo->component . '\courseformat\sectiondelegate';
|
||||
if (!class_exists($classname)) {
|
||||
$classname = self::get_delegate_class_name($sectioninfo->component);
|
||||
if ($classname === null) {
|
||||
return null;
|
||||
}
|
||||
return new $classname($sectioninfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the delgate class name of a plugin, if any.
|
||||
* @param string $pluginname
|
||||
* @return string|null the delegate class name or null if not found.
|
||||
*/
|
||||
protected static function get_delegate_class_name(string $pluginname): ?string {
|
||||
$classname = $pluginname . '\courseformat\sectiondelegate';
|
||||
if (!class_exists($classname)) {
|
||||
return null;
|
||||
}
|
||||
return $classname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a plugin has a delegate class.
|
||||
* @param string $pluginname
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_delegate_class(string $pluginname): bool {
|
||||
return self::get_delegate_class_name($pluginname) !== null;
|
||||
}
|
||||
}
|
||||
|
@ -72,4 +72,10 @@ class sectiondelegate_test extends \advanced_testcase {
|
||||
$this->assertInstanceOf('\test_component\courseformat\sectiondelegate', sectiondelegate::instance($sectioninfos[2]));
|
||||
$this->assertNull(sectiondelegate::instance($sectioninfos[3]));
|
||||
}
|
||||
|
||||
public function test_has_delegate_class(): void {
|
||||
$this->assertFalse(sectiondelegate::has_delegate_class('missing_component'));
|
||||
$this->assertFalse(sectiondelegate::has_delegate_class('mod_label'));
|
||||
$this->assertTrue(sectiondelegate::has_delegate_class('test_component'));
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
use \core_grades\component_gradeitems;
|
||||
use core_courseformat\formatactions;
|
||||
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
|
||||
@ -497,27 +498,33 @@ function set_moduleinfo_defaults($moduleinfo) {
|
||||
* The fucntion create the course section if it doesn't exist.
|
||||
*
|
||||
* @param object $course the course of the module
|
||||
* @param object $modulename the module name
|
||||
* @param object $section the section of the module
|
||||
* @param string $modulename the module name
|
||||
* @param int $sectionnum the section of the module
|
||||
* @return array list containing module, context, course section.
|
||||
* @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
|
||||
*/
|
||||
function can_add_moduleinfo($course, $modulename, $section) {
|
||||
function can_add_moduleinfo($course, $modulename, $sectionnum) {
|
||||
global $DB;
|
||||
|
||||
$module = $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
|
||||
$module = $DB->get_record('modules', ['name' => $modulename], '*', MUST_EXIST);
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
require_capability('moodle/course:manageactivities', $context);
|
||||
|
||||
course_create_sections_if_missing($course, $section);
|
||||
$cw = get_fast_modinfo($course)->get_section_info($section);
|
||||
// If the $sectionnum is a delegated section, we cannot execute create_if_missing
|
||||
// because it only works to create regular sections. To prevent that from happening, we
|
||||
// check if the section is already there, no matter if it is delegated or not.
|
||||
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
|
||||
if (!$sectioninfo) {
|
||||
formatactions::section($course)->create_if_missing([$sectionnum]);
|
||||
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
|
||||
}
|
||||
|
||||
if (!course_allowed_module($course, $module->name)) {
|
||||
throw new \moodle_exception('moduledisable');
|
||||
}
|
||||
|
||||
return array($module, $context, $cw);
|
||||
return [$module, $context, $sectioninfo];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
namespace core_course;
|
||||
|
||||
use core_courseformat\formatactions;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
@ -276,4 +278,188 @@ class modlib_test extends \advanced_testcase {
|
||||
$this->assertEquals($expectedorder, $modinfo->get_sections()[$sectionnumber]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for can_add_moduleinfo on a non-existing module.
|
||||
*
|
||||
* @covers \can_add_moduleinfo
|
||||
*/
|
||||
public function test_can_add_moduleinfo_invalid_module(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['numsections' => 2, 'enablecompletion' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$section = $modinfo->get_section_info(2);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
$this->expectException(\dml_missing_record_exception::class);
|
||||
|
||||
can_add_moduleinfo($course, 'non-existent-module!', $section->section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for can_add_moduleinfo when the user does not have addinstance capability.
|
||||
*
|
||||
* @covers \can_add_moduleinfo
|
||||
*/
|
||||
public function test_can_add_moduleinfo_deny_add_instance(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['numsections' => 2, 'enablecompletion' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$section = $modinfo->get_section_info(2);
|
||||
|
||||
// The can_add_moduleinfo uses course_allowed_module to check if the module is allowed in the course.
|
||||
// This method uses capabilities like the specific module addinstance capability.
|
||||
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher'], '*', MUST_EXIST);
|
||||
role_change_permission(
|
||||
$teacherrole->id,
|
||||
\context_course::instance($course->id),
|
||||
'mod/label:addinstance',
|
||||
CAP_PROHIBIT
|
||||
);
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
|
||||
$this->setUser($user);
|
||||
|
||||
$this->expectException(\moodle_exception::class);
|
||||
|
||||
can_add_moduleinfo($course, 'label', $section->section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for can_add_moduleinfo.
|
||||
*
|
||||
* @dataProvider provider_can_add_moduleinfo
|
||||
* @covers \can_add_moduleinfo
|
||||
* @param string $rolename
|
||||
* @param bool $hascapability
|
||||
*/
|
||||
public function test_can_add_moduleinfo_capability(string $rolename, bool $hascapability): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$module = $DB->get_record('modules', ['name' => 'label'], '*', MUST_EXIST);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['numsections' => 2, 'enablecompletion' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$section = $modinfo->get_section_info(2);
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename);
|
||||
$this->setUser($user);
|
||||
|
||||
if (!$hascapability) {
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
}
|
||||
|
||||
$result = can_add_moduleinfo($course, 'label', $section->section);
|
||||
|
||||
$this->assertEquals($module, $result[0]);
|
||||
$this->assertEquals(
|
||||
\context_course::instance($course->id),
|
||||
$result[1]
|
||||
);
|
||||
$this->assertEquals($section->id, $result[2]->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for can_add_moduleinfo returns true on a delegate section.
|
||||
*
|
||||
* @dataProvider provider_can_add_moduleinfo
|
||||
* @covers \can_add_moduleinfo
|
||||
* @param string $rolename
|
||||
* @param bool $hascapability
|
||||
*/
|
||||
public function test_can_add_moduleinfo_delegate_section(string $rolename, bool $hascapability): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$module = $DB->get_record('modules', ['name' => 'label'], '*', MUST_EXIST);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['numsections' => 2, 'enablecompletion' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
|
||||
$section = formatactions::section($course)->create_delegated('mod_label', 0);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$this->assertCount(4, $modinfo->get_section_info_all());
|
||||
$this->assertCount(3, $modinfo->get_listed_section_info_all());
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename);
|
||||
$this->setUser($user);
|
||||
|
||||
if (!$hascapability) {
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
}
|
||||
|
||||
$result = can_add_moduleinfo($course, 'label', $section->section);
|
||||
|
||||
$this->assertEquals($module, $result[0]);
|
||||
$this->assertEquals(
|
||||
\context_course::instance($course->id),
|
||||
$result[1]
|
||||
);
|
||||
$this->assertEquals($section->id, $result[2]->id);
|
||||
|
||||
// Validate no section has been created.
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$this->assertCount(4, $modinfo->get_section_info_all());
|
||||
$this->assertCount(3, $modinfo->get_listed_section_info_all());
|
||||
$this->assertEquals(
|
||||
$section->section,
|
||||
$modinfo->get_section_info_by_id($section->id)->section
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_can_add_moduleinfo.
|
||||
* @return array
|
||||
*/
|
||||
public static function provider_can_add_moduleinfo(): array {
|
||||
return [
|
||||
'Editing teacher' => [
|
||||
'rolename' => 'editingteacher',
|
||||
'hascapability' => true,
|
||||
],
|
||||
'Manager' => [
|
||||
'rolename' => 'manager',
|
||||
'hascapability' => true,
|
||||
],
|
||||
'Course creator' => [
|
||||
'rolename' => 'coursecreator',
|
||||
'hascapability' => false,
|
||||
],
|
||||
'Non-editing teacher' => [
|
||||
'rolename' => 'teacher',
|
||||
'hascapability' => false,
|
||||
],
|
||||
'Student' => [
|
||||
'rolename' => 'student',
|
||||
'hascapability' => false,
|
||||
],
|
||||
'Guest' => [
|
||||
'rolename' => 'guest',
|
||||
'hascapability' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -316,12 +316,36 @@ class course_modinfo {
|
||||
|
||||
/**
|
||||
* Gets all sections as array from section number => data about section.
|
||||
*
|
||||
* The method will return all sections of the course, including the ones
|
||||
* delegated to a component.
|
||||
*
|
||||
* @return section_info[] Array of section_info objects organised by section number
|
||||
*/
|
||||
public function get_section_info_all() {
|
||||
return $this->sectioninfobynum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all sections listed in course page as array from section number => data about section.
|
||||
*
|
||||
* The method is similar to get_section_info_all but filtering all sections delegated to components.
|
||||
*
|
||||
* @return section_info[] Array of section_info objects organised by section number
|
||||
*/
|
||||
public function get_listed_section_info_all() {
|
||||
if (empty($this->delegatedsections)) {
|
||||
return $this->sectioninfobynum;
|
||||
}
|
||||
$sections = [];
|
||||
foreach ($this->sectioninfobynum as $section) {
|
||||
if (!$section->is_delegated()) {
|
||||
$sections[$section->section] = $section;
|
||||
}
|
||||
}
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data about specific numbered section.
|
||||
* @param int $sectionnumber Number (not id) of section
|
||||
@ -3491,6 +3515,14 @@ class section_info implements IteratorAggregate {
|
||||
return $this->_delegateinstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this section is a delegate to a component.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_delegated(): bool {
|
||||
return !empty($this->_component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares section data for inclusion in sectioncache cache, removing items
|
||||
* that are set to defaults, and adding availability data if required.
|
||||
|
@ -1039,6 +1039,35 @@ class modinfolib_test extends advanced_testcase {
|
||||
$this->assertTrue($cm->uservisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get_listed_section_info_all method.
|
||||
* @covers \course_modinfo::get_listed_section_info_all
|
||||
* @covers \course_modinfo::get_section_info_all
|
||||
*/
|
||||
public function test_get_listed_section_info_all(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a course with 4 sections.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 3]);
|
||||
|
||||
$listed = get_fast_modinfo($course)->get_section_info_all();
|
||||
$this->assertCount(4, $listed);
|
||||
|
||||
// Generate some delegated sections (not listed).
|
||||
formatactions::section($course)->create_delegated('mod_label', 0);
|
||||
formatactions::section($course)->create_delegated('mod_label', 1);
|
||||
|
||||
$this->assertCount(6, get_fast_modinfo($course)->get_section_info_all());
|
||||
|
||||
$result = get_fast_modinfo($course)->get_listed_section_info_all();
|
||||
|
||||
$this->assertCount(4, $result);
|
||||
$this->assertEquals($listed[0]->id, $result[0]->id);
|
||||
$this->assertEquals($listed[1]->id, $result[1]->id);
|
||||
$this->assertEquals($listed[2]->id, $result[2]->id);
|
||||
$this->assertEquals($listed[3]->id, $result[3]->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test test_get_section_info_by_id method
|
||||
*
|
||||
@ -1525,4 +1554,22 @@ class modinfolib_test extends advanced_testcase {
|
||||
$this->assertEquals('test_component', $sectioninfos[2]->component);
|
||||
$this->assertEquals(1, $sectioninfos[2]->itemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for section_info is_delegated.
|
||||
* @covers \section_info::is_delegated
|
||||
*/
|
||||
public function test_is_delegated(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 1]);
|
||||
|
||||
formatactions::section($course)->create_delegated('mod_label', 0);
|
||||
|
||||
$modinfo = get_fast_modinfo($course->id);
|
||||
$sectioninfos = $modinfo->get_section_info_all();
|
||||
|
||||
$this->assertFalse($sectioninfos[1]->is_delegated());
|
||||
$this->assertTrue($sectioninfos[2]->is_delegated());
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,11 @@ information provided here is intended especially for developers.
|
||||
|
||||
=== 4.4 ===
|
||||
|
||||
* New modinfo methods related to delegated sections (sections controlled by a component):
|
||||
- course_modinfo::get_listed_section_info_all to get only the listed course sections.
|
||||
- course_modinfo::has_delegated_sections to know if a course has delegated sections.
|
||||
- section_info::is_delegated to check if the section is delegated.
|
||||
- section_info::get_component_instance to get the component section delegate integration instance.
|
||||
* Added modalform config object `moduleName` param that can be used to define alternative modal type for the modalform. By default 'core/modal_save_cancel' is used.
|
||||
* Add a new parameter to the debounce (core/utils) function to allow for cancellation.
|
||||
* Add a new method core_user::get_initials to get the initials of a user in a way compatible with internationalisation.
|
||||
|
Loading…
x
Reference in New Issue
Block a user