MDL-80190 core_courseformat: prevent delegated sections loops

This patch prevent dropping an activity with delegated sections inside
another delegated section. Without this patch the teacher can create a
deadlock drag&droping the subsection inside itself. We won't allow
subsections inside subsections so any activity with subsection cannot
be dropped inside a subsección.
This commit is contained in:
Ferran Recio
2024-02-07 12:14:59 +01:00
parent d244a206a3
commit 34ce0eddc0
15 changed files with 62 additions and 12 deletions
@@ -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
+1 -1
View File
@@ -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;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,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
@@ -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;
@@ -144,6 +144,7 @@ export default class {
id: cminfo.id,
name: cminfo.name,
sectionid: cminfo.sectionid,
delegatesection: cminfo.delegatesection,
nextcmid,
};
}
@@ -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.
+24 -2
View File
@@ -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'));
}
}