diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dcff5409..68651f63 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,14 +16,14 @@ jobs: fail-fast: false matrix: include: - - php: '7.4' - moodle-branch: 'MOODLE_401_STABLE' + - php: '8.1' + moodle-branch: 'MOODLE_403_STABLE' database: 'pgsql' - db-version: '12' - - php: '7.4' - moodle-branch: 'MOODLE_401_STABLE' + db-version: '13' + - php: '8.1' + moodle-branch: 'MOODLE_403_STABLE' database: 'mysqli' - db-version: '5.7' + db-version: '8.0' - php: '8.1' moodle-branch: 'MOODLE_404_STABLE' database: 'pgsql' @@ -49,9 +49,21 @@ jobs: database: 'mysqli' db-version: '8.4' - php: '8.4' - moodle-branch: 'v5.0.1' + moodle-branch: 'MOODLE_501_STABLE' database: 'pgsql' db-version: '15' + - php: '8.4' + moodle-branch: 'MOODLE_501_STABLE' + database: 'mysqli' + db-version: '8.4' + - php: '8.4' + moodle-branch: 'MOODLE_502_STABLE' + database: 'pgsql' + db-version: '16' + - php: '8.4' + moodle-branch: 'MOODLE_502_STABLE' + database: 'mysqli' + db-version: '8.4' services: postgres: diff --git a/amd/build/duedate_form.min.js b/amd/build/duedate_form.min.js index f4825738..366ade3a 100644 --- a/amd/build/duedate_form.min.js +++ b/amd/build/duedate_form.min.js @@ -1,3 +1,3 @@ -define("mod_mumie/duedate_form",["jquery","core/str","core/modal_factory","core/modal_events","core/fragment","core/ajax","core/yui"],(function($,Str,ModalFactory,ModalEvents,Fragment,Ajax,Y){var MumieDueDate=function(selector,contextid,formdata){this.contextid=contextid,this.init(selector,formdata)};return MumieDueDate.prototype.modal=null,MumieDueDate.prototype.contextid=-1,MumieDueDate.prototype.init=function(selector,formdata){const triggers=$(selector);return Str.get_string("mumie_duedate_form","mod_mumie").then(function(title){return ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:title,body:this.getBody(formdata)},triggers)}.bind(this)).then(function(modal){return this.modal=modal,this.modal.setLarge(),this.modal.getRoot().on(ModalEvents.hidden,function(){this.modal.setBody(this.getBody(formdata))}.bind(this)),this.modal.getRoot().on(ModalEvents.save,this.submitForm.bind(this)),this.modal.getRoot().on("submit","form",this.submitFormAjax.bind(this)),this.modal}.bind(this))},MumieDueDate.prototype.getBody=function(formdata){void 0===formdata&&(formdata={});var params={jsonformdata:JSON.stringify(formdata)};return Fragment.loadFragment("mod_mumie","new_duedate_form",this.contextid,params)},MumieDueDate.prototype.handleFormSubmissionResponse=function(){this.modal.hide(),Y.use("moodle-core-formchangechecker",(function(){M.core_formchangechecker.reset_form_dirty_state()})),document.location.reload()},MumieDueDate.prototype.handleFormSubmissionFailure=function(data){this.modal.setBody(this.getBody(data))},MumieDueDate.prototype.submitFormAjax=function(e){e.preventDefault();var formData=this.modal.getRoot().find("form").serialize();Ajax.call([{methodname:"mod_mumie_submit_mumieduedate_form",args:{contextid:this.contextid,jsonformdata:JSON.stringify(formData)},done:this.handleFormSubmissionResponse.bind(this,formData),fail:this.handleFormSubmissionFailure.bind(this,formData)}])},MumieDueDate.prototype.submitForm=function(e){e.preventDefault(),this.modal.getRoot().find("form").submit()},{init:function(selector,contextid,formdata){return new MumieDueDate(selector,contextid,formdata)}}})); +define("mod_mumie/duedate_form",["jquery","core/str","core/modal_save_cancel","core/modal_events","core/fragment","core/ajax","core_form/changechecker"],(function($,Str,ModalSaveCancel,ModalEvents,Fragment,Ajax,FormChangeChecker){var MumieDueDate=function(selector,contextid,formdata){this.contextid=contextid,this.init(selector,formdata)};return MumieDueDate.prototype.modal=null,MumieDueDate.prototype.contextid=-1,MumieDueDate.prototype.init=function(selector,formdata){$(selector).on("click",function(e){return e.preventDefault(),Str.get_string("mumie_duedate_form","mod_mumie").then(function(title){return ModalSaveCancel.create({title:title,body:this.getBody(formdata),large:!0,removeOnClose:!0})}.bind(this)).then(function(modal){return this.modal=modal,modal.getRoot().on(ModalEvents.save,this.submitForm.bind(this)),modal.getRoot().on("submit","form",this.submitFormAjax.bind(this)),modal.show(),modal}.bind(this))}.bind(this))},MumieDueDate.prototype.getBody=function(formdata){void 0===formdata&&(formdata={});var params={jsonformdata:JSON.stringify(formdata)};return Fragment.loadFragment("mod_mumie","new_duedate_form",this.contextid,params)},MumieDueDate.prototype.handleFormSubmissionResponse=function(){this.modal.hide(),FormChangeChecker.resetAllFormDirtyStates(),document.location.reload()},MumieDueDate.prototype.handleFormSubmissionFailure=function(data){this.modal.setBody(this.getBody(data))},MumieDueDate.prototype.submitFormAjax=function(e){e.preventDefault();var formData=this.modal.getRoot().find("form").serialize();Ajax.call([{methodname:"mod_mumie_submit_mumieduedate_form",args:{contextid:this.contextid,jsonformdata:JSON.stringify(formData)},done:this.handleFormSubmissionResponse.bind(this,formData),fail:this.handleFormSubmissionFailure.bind(this,formData)}])},MumieDueDate.prototype.submitForm=function(e){e.preventDefault(),this.modal.getRoot().find("form").submit()},{init:function(selector,contextid,formdata){return new MumieDueDate(selector,contextid,formdata)}}})); //# sourceMappingURL=duedate_form.min.js.map \ No newline at end of file diff --git a/amd/build/duedate_form.min.js.map b/amd/build/duedate_form.min.js.map index c1f541a2..39a38f30 100644 --- a/amd/build/duedate_form.min.js.map +++ b/amd/build/duedate_form.min.js.map @@ -1 +1 @@ -{"version":3,"file":"duedate_form.min.js","sources":["../src/duedate_form.js"],"sourcesContent":["define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'],\n function($, Str, ModalFactory, ModalEvents, Fragment, Ajax, Y) {\n\n var MumieDueDate = function(selector, contextid, formdata) {\n this.contextid = contextid;\n this.init(selector, formdata);\n };\n\n MumieDueDate.prototype.modal = null;\n\n MumieDueDate.prototype.contextid = -1;\n\n MumieDueDate.prototype.init = function(selector, formdata) {\n const triggers = $(selector);\n return Str.get_string('mumie_duedate_form', 'mod_mumie').then(function(title) {\n // Create the modal.\n return ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: this.getBody(formdata)\n }, triggers);\n }.bind(this)).then(function(modal) {\n // Keep a reference to the modal.\n this.modal = modal;\n\n // Forms are big, we want a big modal.\n this.modal.setLarge();\n\n // We want to reset the form every time it is opened.\n this.modal.getRoot().on(ModalEvents.hidden, function() {\n this.modal.setBody(this.getBody(formdata));\n }.bind(this));\n\n // We catch the modal save event, and use it to submit the form inside the modal.\n // Triggering a form submission will give JS validation scripts a chance to check for errors.\n this.modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));\n // We also catch the form submit event and use it to submit the form with ajax.\n this.modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));\n\n return this.modal;\n\n }.bind(this));\n };\n\n /**\n * @method getBody\n * @private\n * @param {Object} formdata\n * @return {Promise}\n */\n MumieDueDate.prototype.getBody = function(formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n // Get the content of the modal.\n var params = {\n jsonformdata: JSON.stringify(formdata)\n };\n return Fragment.loadFragment('mod_mumie', 'new_duedate_form', this.contextid, params);\n };\n\n /**\n * @method handleFormSubmissionResponse\n * @private\n */\n MumieDueDate.prototype.handleFormSubmissionResponse = function() {\n this.modal.hide();\n // We could trigger an event instead.\n // Yuk.\n Y.use('moodle-core-formchangechecker', function() {\n M.core_formchangechecker.reset_form_dirty_state();\n });\n\n document.location.reload();\n };\n\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @param {Object} data\n */\n MumieDueDate.prototype.handleFormSubmissionFailure = function(data) {\n // Oh noes! Epic fail :(\n // Ah wait - this is normal. We need to re-display the form with errors!\n this.modal.setBody(this.getBody(data));\n };\n\n /**\n * Private method\n *\n * @method submitFormAjax\n * @private\n * @param {Event} e Form submission event.\n */\n MumieDueDate.prototype.submitFormAjax = function(e) {\n // We don't want to do a real form submission.\n e.preventDefault();\n\n // Convert all the form elements values to a serialized string.\n var formData = this.modal.getRoot().find('form').serialize();\n // Now we can continue...\n Ajax.call([{\n methodname: 'mod_mumie_submit_mumieduedate_form',\n args: {\n contextid: this.contextid,\n jsonformdata: JSON.stringify(formData)\n },\n done: this.handleFormSubmissionResponse.bind(this, formData),\n fail: this.handleFormSubmissionFailure.bind(this, formData)\n }]);\n };\n\n /**\n * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.\n *\n * @method submitForm\n * @param {Event} e Form submission event.\n * @private\n */\n MumieDueDate.prototype.submitForm = function(e) {\n e.preventDefault();\n this.modal.getRoot().find('form').submit();\n };\n\n return /** @alias module:mod_mumie/newduedate */ {\n // Public variables and functions.\n /**\n * Attach event listeners to initialize this module.\n *\n * @method init\n * @param {string} selector The CSS selector used to find nodes that will trigger this module.\n * @param {int} contextid The contextid for the course.\n * @param {Object} formdata\n * @return {Promise}\n */\n init: function(selector, contextid, formdata) {\n return new MumieDueDate(selector, contextid, formdata);\n }\n };\n\n });"],"names":["define","$","Str","ModalFactory","ModalEvents","Fragment","Ajax","Y","MumieDueDate","selector","contextid","formdata","init","prototype","modal","triggers","get_string","then","title","create","type","types","SAVE_CANCEL","body","this","getBody","bind","setLarge","getRoot","on","hidden","setBody","save","submitForm","submitFormAjax","params","jsonformdata","JSON","stringify","loadFragment","handleFormSubmissionResponse","hide","use","M","core_formchangechecker","reset_form_dirty_state","document","location","reload","handleFormSubmissionFailure","data","e","preventDefault","formData","find","serialize","call","methodname","args","done","fail","submit"],"mappings":"AAAAA,gCAAO,CAAC,SAAU,WAAY,qBAAsB,oBAAqB,gBAAiB,YAAa,aACnG,SAASC,EAAGC,IAAKC,aAAcC,YAAaC,SAAUC,KAAMC,OAEpDC,aAAe,SAASC,SAAUC,UAAWC,eACxCD,UAAYA,eACZE,KAAKH,SAAUE,kBAGxBH,aAAaK,UAAUC,MAAQ,KAE/BN,aAAaK,UAAUH,WAAa,EAEpCF,aAAaK,UAAUD,KAAO,SAASH,SAAUE,gBACvCI,SAAWd,EAAEQ,iBACZP,IAAIc,WAAW,qBAAsB,aAAaC,KAAK,SAASC,cAE5Df,aAAagB,OAAO,CACvBC,KAAMjB,aAAakB,MAAMC,YACzBJ,MAAOA,MACPK,KAAMC,KAAKC,QAAQd,WACpBI,WACLW,KAAKF,OAAOP,KAAK,SAASH,mBAEnBA,MAAQA,WAGRA,MAAMa,gBAGNb,MAAMc,UAAUC,GAAGzB,YAAY0B,OAAQ,gBACnChB,MAAMiB,QAAQP,KAAKC,QAAQd,YAClCe,KAAKF,YAIFV,MAAMc,UAAUC,GAAGzB,YAAY4B,KAAMR,KAAKS,WAAWP,KAAKF,YAE1DV,MAAMc,UAAUC,GAAG,SAAU,OAAQL,KAAKU,eAAeR,KAAKF,OAE5DA,KAAKV,OAEdY,KAAKF,QASXhB,aAAaK,UAAUY,QAAU,SAASd,eACd,IAAbA,WACPA,SAAW,QAGXwB,OAAS,CACTC,aAAcC,KAAKC,UAAU3B,kBAE1BN,SAASkC,aAAa,YAAa,mBAAoBf,KAAKd,UAAWyB,SAOlF3B,aAAaK,UAAU2B,6BAA+B,gBAC7C1B,MAAM2B,OAGXlC,EAAEmC,IAAI,iCAAiC,WACnCC,EAAEC,uBAAuBC,4BAG7BC,SAASC,SAASC,UAQtBxC,aAAaK,UAAUoC,4BAA8B,SAASC,WAGrDpC,MAAMiB,QAAQP,KAAKC,QAAQyB,QAUpC1C,aAAaK,UAAUqB,eAAiB,SAASiB,GAE7CA,EAAEC,qBAGEC,SAAW7B,KAAKV,MAAMc,UAAU0B,KAAK,QAAQC,YAEjDjD,KAAKkD,KAAK,CAAC,CACPC,WAAY,qCACZC,KAAM,CACFhD,UAAWc,KAAKd,UAChB0B,aAAcC,KAAKC,UAAUe,WAEjCM,KAAMnC,KAAKgB,6BAA6Bd,KAAKF,KAAM6B,UACnDO,KAAMpC,KAAKyB,4BAA4BvB,KAAKF,KAAM6B,cAW1D7C,aAAaK,UAAUoB,WAAa,SAASkB,GACzCA,EAAEC,sBACGtC,MAAMc,UAAU0B,KAAK,QAAQO,UAGW,CAW7CjD,KAAM,SAASH,SAAUC,UAAWC,iBACzB,IAAIH,aAAaC,SAAUC,UAAWC"} \ No newline at end of file +{"version":3,"file":"duedate_form.min.js","sources":["../src/duedate_form.js"],"sourcesContent":["define([\n 'jquery',\n 'core/str',\n 'core/modal_save_cancel',\n 'core/modal_events',\n 'core/fragment',\n 'core/ajax',\n 'core_form/changechecker',\n], function($, Str, ModalSaveCancel, ModalEvents, Fragment, Ajax, FormChangeChecker) {\n\n var MumieDueDate = function(selector, contextid, formdata) {\n this.contextid = contextid;\n this.init(selector, formdata);\n };\n\n MumieDueDate.prototype.modal = null;\n\n MumieDueDate.prototype.contextid = -1;\n\n MumieDueDate.prototype.init = function(selector, formdata) {\n $(selector).on('click', function(e) {\n e.preventDefault();\n return Str.get_string('mumie_duedate_form', 'mod_mumie').then(function(title) {\n return ModalSaveCancel.create({\n title: title,\n body: this.getBody(formdata),\n large: true,\n removeOnClose: true,\n });\n }.bind(this)).then(function(modal) {\n this.modal = modal;\n\n // We catch the modal save event, and use it to submit the form inside the modal.\n // Triggering a form submission will give JS validation scripts a chance to check for errors.\n modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));\n // We also catch the form submit event and use it to submit the form with ajax.\n modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));\n\n modal.show();\n return modal;\n }.bind(this));\n }.bind(this));\n };\n\n /**\n * @method getBody\n * @private\n * @param {Object} formdata\n * @return {Promise}\n */\n MumieDueDate.prototype.getBody = function(formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n // Get the content of the modal.\n var params = {\n jsonformdata: JSON.stringify(formdata)\n };\n return Fragment.loadFragment('mod_mumie', 'new_duedate_form', this.contextid, params);\n };\n\n /**\n * @method handleFormSubmissionResponse\n * @private\n */\n MumieDueDate.prototype.handleFormSubmissionResponse = function() {\n this.modal.hide();\n FormChangeChecker.resetAllFormDirtyStates();\n document.location.reload();\n };\n\n /**\n * @method handleFormSubmissionFailure\n * @private\n * @param {Object} data\n */\n MumieDueDate.prototype.handleFormSubmissionFailure = function(data) {\n // Oh noes! Epic fail :(\n // Ah wait - this is normal. We need to re-display the form with errors!\n this.modal.setBody(this.getBody(data));\n };\n\n /**\n * Private method\n *\n * @method submitFormAjax\n * @private\n * @param {Event} e Form submission event.\n */\n MumieDueDate.prototype.submitFormAjax = function(e) {\n // We don't want to do a real form submission.\n e.preventDefault();\n\n // Convert all the form elements values to a serialized string.\n var formData = this.modal.getRoot().find('form').serialize();\n // Now we can continue...\n Ajax.call([{\n methodname: 'mod_mumie_submit_mumieduedate_form',\n args: {\n contextid: this.contextid,\n jsonformdata: JSON.stringify(formData)\n },\n done: this.handleFormSubmissionResponse.bind(this, formData),\n fail: this.handleFormSubmissionFailure.bind(this, formData)\n }]);\n };\n\n /**\n * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.\n *\n * @method submitForm\n * @param {Event} e Form submission event.\n * @private\n */\n MumieDueDate.prototype.submitForm = function(e) {\n e.preventDefault();\n this.modal.getRoot().find('form').submit();\n };\n\n return /** @alias module:mod_mumie/newduedate */ {\n // Public variables and functions.\n /**\n * Attach event listeners to initialize this module.\n *\n * @method init\n * @param {string} selector The CSS selector used to find nodes that will trigger this module.\n * @param {int} contextid The contextid for the course.\n * @param {Object} formdata\n * @return {Promise}\n */\n init: function(selector, contextid, formdata) {\n return new MumieDueDate(selector, contextid, formdata);\n }\n };\n\n });"],"names":["define","$","Str","ModalSaveCancel","ModalEvents","Fragment","Ajax","FormChangeChecker","MumieDueDate","selector","contextid","formdata","init","prototype","modal","on","e","preventDefault","get_string","then","title","create","body","this","getBody","large","removeOnClose","bind","getRoot","save","submitForm","submitFormAjax","show","params","jsonformdata","JSON","stringify","loadFragment","handleFormSubmissionResponse","hide","resetAllFormDirtyStates","document","location","reload","handleFormSubmissionFailure","data","setBody","formData","find","serialize","call","methodname","args","done","fail","submit"],"mappings":"AAAAA,gCAAO,CACH,SACA,WACA,yBACA,oBACA,gBACA,YACA,4BACD,SAASC,EAAGC,IAAKC,gBAAiBC,YAAaC,SAAUC,KAAMC,uBAEtDC,aAAe,SAASC,SAAUC,UAAWC,eACxCD,UAAYA,eACZE,KAAKH,SAAUE,kBAGxBH,aAAaK,UAAUC,MAAQ,KAE/BN,aAAaK,UAAUH,WAAa,EAEpCF,aAAaK,UAAUD,KAAO,SAASH,SAAUE,UAC7CV,EAAEQ,UAAUM,GAAG,QAAS,SAASC,UAC7BA,EAAEC,iBACKf,IAAIgB,WAAW,qBAAsB,aAAaC,KAAK,SAASC,cAC5DjB,gBAAgBkB,OAAO,CAC1BD,MAAOA,MACPE,KAAMC,KAAKC,QAAQb,UACnBc,OAAO,EACPC,eAAe,KAErBC,KAAKJ,OAAOJ,KAAK,SAASL,mBACnBA,MAAQA,MAIbA,MAAMc,UAAUb,GAAGX,YAAYyB,KAAMN,KAAKO,WAAWH,KAAKJ,OAE1DT,MAAMc,UAAUb,GAAG,SAAU,OAAQQ,KAAKQ,eAAeJ,KAAKJ,OAE9DT,MAAMkB,OACClB,OACTa,KAAKJ,QACTI,KAAKJ,QASXf,aAAaK,UAAUW,QAAU,SAASb,eACd,IAAbA,WACPA,SAAW,QAGXsB,OAAS,CACTC,aAAcC,KAAKC,UAAUzB,kBAE1BN,SAASgC,aAAa,YAAa,mBAAoBd,KAAKb,UAAWuB,SAOlFzB,aAAaK,UAAUyB,6BAA+B,gBAC7CxB,MAAMyB,OACXhC,kBAAkBiC,0BAClBC,SAASC,SAASC,UAQtBnC,aAAaK,UAAU+B,4BAA8B,SAASC,WAGrD/B,MAAMgC,QAAQvB,KAAKC,QAAQqB,QAUpCrC,aAAaK,UAAUkB,eAAiB,SAASf,GAE7CA,EAAEC,qBAGE8B,SAAWxB,KAAKT,MAAMc,UAAUoB,KAAK,QAAQC,YAEjD3C,KAAK4C,KAAK,CAAC,CACPC,WAAY,qCACZC,KAAM,CACF1C,UAAWa,KAAKb,UAChBwB,aAAcC,KAAKC,UAAUW,WAEjCM,KAAM9B,KAAKe,6BAA6BX,KAAKJ,KAAMwB,UACnDO,KAAM/B,KAAKqB,4BAA4BjB,KAAKJ,KAAMwB,cAW1DvC,aAAaK,UAAUiB,WAAa,SAASd,GACzCA,EAAEC,sBACGH,MAAMc,UAAUoB,KAAK,QAAQO,UAGW,CAW7C3C,KAAM,SAASH,SAAUC,UAAWC,iBACzB,IAAIH,aAAaC,SAAUC,UAAWC"} \ No newline at end of file diff --git a/amd/build/mod_form.min.js b/amd/build/mod_form.min.js index cf6efb47..1884b2b6 100644 --- a/amd/build/mod_form.min.js +++ b/amd/build/mod_form.min.js @@ -1,3 +1,3 @@ -define("mod_mumie/mod_form",["jquery","core/templates","core/modal_factory","auth_mumie/mumie_server_config","core/ajax"],(function(){const addServerButton=document.getElementById("id_add_server_button"),missingConfig=document.getElementsByName("mumie_missing_config")[0];let lmsSelectorUrl,systemLanguage,contextId;const durationController=function(){const durationSelector=document.getElementById("id_duration_selector"),gradedElem=document.getElementById("id_mumie_isgraded");function isUngraded(){return"0"===gradedElem.value}function updateDurationElements(){isUngraded()?(durationSelector.setAttribute("disabled","disabled"),durationSelector.value="unlimited"):durationSelector.removeAttribute("disabled");"unlimited"===durationSelector.value?(document.getElementById("fitem_id_unlimited_info").style.display="",document.getElementById("fitem_id_timelimit").style.display="none",document.getElementById("fitem_id_timelimit_info").style.display="none",document.getElementById("fitem_id_duedate").style.display="none",document.getElementById("fitem_id_duedate_info").style.display="none"):"duedate"===durationSelector.value?(document.getElementById("fitem_id_duedate").style.display="",document.getElementById("fitem_id_duedate_info").style.display="",document.getElementById("fitem_id_unlimited_info").style.display="none",document.getElementById("fitem_id_timelimit").style.display="none",document.getElementById("fitem_id_timelimit_info").style.display="none"):"timelimit"===durationSelector.value&&(document.getElementById("fitem_id_timelimit").style.display="",document.getElementById("fitem_id_timelimit_info").style.display="",document.getElementById("fitem_id_duedate").style.display="none",document.getElementById("fitem_id_duedate_info").style.display="none",document.getElementById("fitem_id_unlimited_info").style.display="none")}return{init:function(){durationSelector.onchange=function(){updateDurationElements()},window.addEventListener("load",(()=>{updateDurationElements()}))},setDurationElements:updateDurationElements,isUngraded:isUngraded,setGradedElemValue:function(isGraded){gradedElem.value=isGraded?"1":"0"},getGradedElemValue:function(){return gradedElem.value}}},serverController=function(){let serverStructure;const serverDropDown=document.getElementById("id_server");return{init:function(structure){serverStructure=structure},getSelectedServer:function(){const selectedServerName=serverDropDown.options[serverDropDown.selectedIndex].text;return serverStructure.find((server=>server.name===selectedServerName))},disable:function(){serverDropDown.disabled=!0,function(elem){for(;elem.firstChild;)elem.removeChild(elem.firstChild)}(serverDropDown)}}}(),problemSelectorController=function(){const problemSelectorButton=document.getElementById("id_prb_selector_btn"),multiProblemSelectorButton=document.getElementById("id_multi_problem_selector_btn");let problemSelectorWindow;const mumieOrg=document.getElementsByName("mumie_org")[0].value;function sendResponse(response){problemSelectorWindow&&problemSelectorWindow.postMessage(JSON.stringify(response),lmsSelectorUrl)}function sendSuccess(){let message=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";sendResponse({success:!0,message:message})}function addMessageListener(){window.addEventListener("message",(event=>{var _importObj$worksheet;if(event.origin!==lmsSelectorUrl)return;const importObj=JSON.parse(event.data),isGraded=!1!==importObj.isGraded,worksheet=null!==(_importObj$worksheet=importObj.worksheet)&&void 0!==_importObj$worksheet?_importObj$worksheet:null;try{courseController.setCourse(importObj.path_to_coursefile),langController.setLanguage(importObj.language),taskController.setSelection(importObj.link,importObj.language,importObj.name),taskController.setIsGraded(isGraded),worksheetController.setWorksheet(worksheet),sendSuccess(),window.focus(),require(["core/str","core/notification"],(function(str,notification){str.get_strings([{key:"mumie_form_updated_selection",component:"mod_mumie"}]).done((function(s){notification.addNotification({message:s[0],type:"info"})})).fail(notification.exception)}))}catch(error){!function(){sendResponse({success:!1,message:arguments.length>0&&void 0!==arguments[0]?arguments[0]:""})}(error.message)}}),!1)}function buildURL(){const gradingType=taskController.getGradingType(),selection=taskController.getDelocalizedTaskLink(),selectedServer=serverController.getSelectedServer().urlprefix;var selectedServerUrl;return(selectedServerUrl=selectedServer,new URL(lmsSelectorUrl).origin===new URL(selectedServerUrl).origin)?"/auth/mumie/problem_selector.php?org="+mumieOrg+"&serverurl="+encodeURIComponent(selectedServer)+"&problemlang="+langController.getSelectedLanguage()+"&origin="+encodeURIComponent(window.location.origin)+"&gradingtype="+gradingType+"&contextid="+contextId+(selection?"&selection="+selection:""):lmsSelectorUrl+"/lms-problem-selector?org="+mumieOrg+"&serverUrl="+encodeURIComponent(selectedServer)+"&problemLang="+langController.getSelectedLanguage()+"&origin="+encodeURIComponent(window.location.origin)+"&uiLang="+systemLanguage+"&gradingType="+gradingType+"&multiCourse=true&worksheet=true"+(selection?"&selection="+selection:"")}return{init:function(){problemSelectorButton.onclick=function(){problemSelectorWindow=window.open(buildURL(),"_blank")},window.onclose=function(){sendSuccess()},window.addEventListener("beforeunload",(function(){sendSuccess()}),!1),addMessageListener(),multiProblemSelectorButton.onclick=function(e){e.preventDefault(),problemSelectorWindow=window.open(lmsSelectorUrl+"/lms-problem-selector?serverUrl="+encodeURIComponent(serverController.getSelectedServer().urlprefix)+"&gradingType=all","_blank","toolbar=0,location=0,menubar=0")}},disable:function(){problemSelectorButton.disabled=!0}}}(),courseController=function(){const courseNameElem=document.getElementById("id_mumie_course"),courseFileElem=document.getElementsByName("mumie_coursefile")[0];function updateCourseName(){var _selectedCourse$name$;const selectedCourse=courseController.getSelectedCourse(),selectedLanguage=langController.getSelectedLanguage();selectedCourse&&selectedLanguage&&(courseNameElem.value=null===(_selectedCourse$name$=selectedCourse.name.find((translation=>translation.language===selectedLanguage)))||void 0===_selectedCourse$name$?void 0:_selectedCourse$name$.value)}return{init:function(){updateCourseName()},getSelectedCourse:function(){return serverController.getSelectedServer().courses.find((course=>course.coursefile===courseFileElem.value))},setCourse:function(courseFile){var coursefile;coursefile=courseFile,courseFileElem.value=coursefile,updateCourseName()}}}(),langController=function(){const languageElem=document.getElementById("id_language");return{getSelectedLanguage:function(){return languageElem.value},setLanguage:function(lang){languageElem.value=lang}}}(),taskController=function(){const taskSelectionInput=document.getElementsByName("taskurl")[0],nameElem=document.getElementById("id_name"),taskDisplayElement=document.getElementById("id_task_display_element");function updateTaskDisplayElement(localizedLink){taskDisplayElement.value=localizedLink}function updateTaskUri(link,language){const localizedLink=function(link,language){return link+"?lang="+language}(link,language);taskSelectionInput.value=localizedLink,updateTaskDisplayElement(localizedLink)}return{init:function(){updateTaskDisplayElement(taskSelectionInput.value)},setSelection:function(link,language,name){updateTaskUri(link,language),function(name){nameElem.value=name}(name)},setIsGraded:function(isGraded){durationController().setGradedElemValue(isGraded),function(){const disabled=durationController().isUngraded();document.getElementById("id_points").disabled=disabled,document.getElementById("id_gradepass").disabled=disabled,document.getElementById("id_gradecat").disabled=disabled,durationController().setDurationElements()}()},getGradingType:function(){const isGraded=durationController().getGradedElemValue();return"1"===isGraded?"graded":"0"===isGraded?"ungraded":"all"},getDelocalizedTaskLink:function(){return(link=taskSelectionInput.value).includes("?lang=")?link.split("?lang=")[0]:link;var link}}}(),multiTaskEditController=function(){const propertySelectionInputs=document.getElementsByName("mumie_multi_edit_property"),selectedTaskProperties=document.getElementsByName("mumie_selected_task_properties")[0];let selectedTaskProp=[];const taskSelectionInputs=document.getElementsByName("mumie_multi_edit_task"),selectedTasks=document.getElementsByName("mumie_selected_tasks")[0];let selectedTaskIds=[];const sectionInputs=document.getElementsByName("mumie_multi_edit_section");return{init:function(){taskSelectionInputs.forEach((function(checkbox){checkbox.onchange=function(){checkbox.checked?selectedTaskIds.push(checkbox.value):selectedTaskIds=selectedTaskIds.filter((elem=>elem!==checkbox.value)),selectedTasks.value=JSON.stringify(selectedTaskIds)}})),propertySelectionInputs.forEach((function(checkbox){checkbox.onchange=function(){checkbox.checked?selectedTaskProp.push(checkbox.value):selectedTaskProp=selectedTaskProp.filter((elem=>elem!==checkbox.value)),selectedTaskProperties.value=JSON.stringify(selectedTaskProp)}})),sectionInputs.forEach((function(sectionCheckbox){sectionCheckbox.onchange=function(){sectionCheckbox.checked?taskSelectionInputs.forEach((function(taskCheckbox){var array,element;taskCheckbox.getAttribute("section")===sectionCheckbox.value&&(taskCheckbox.checked=!0,array=selectedTaskIds,element=taskCheckbox.value,array.includes(element)||array.push(element))})):taskSelectionInputs.forEach((function(taskCheckbox){taskCheckbox.getAttribute("section")===sectionCheckbox.value&&(taskCheckbox.checked=!1,selectedTaskIds=selectedTaskIds.filter((elem=>taskCheckbox.value!==elem)))})),selectedTasks.value=JSON.stringify(selectedTaskIds)}}))}}}(),worksheetController=function(){const worksheetElement=document.getElementById("id_mumie_worksheet");return{setWorksheet:function(worksheet){worksheet?worksheetElement.setAttribute("value",JSON.stringify(worksheet)):worksheetElement.removeAttribute("value")}}}();function disableDropDownMenus(errorKey){require(["core/str","core/notification"],(function(str,notification){str.get_strings([{key:errorKey,component:"mod_mumie"}]).done((function(s){notification.addNotification({message:s[0]+""+missingConfig.getAttribute("value")+"",type:"problem"})})).fail(notification.exception)})),serverController.disable(),problemSelectorController.disable()}return{init:function(contextIdParam,prbSelectorUrl,lang){lmsSelectorUrl=prbSelectorUrl,systemLanguage=lang,contextId=contextIdParam;const isEdit=document.getElementById("id_name").getAttribute("value"),serverStructure=JSON.parse(document.getElementsByName("mumie_server_structure")[0].value);isEdit&&""!==document.getElementsByName("mumie_missing_config")[0].getAttribute("value")?disableDropDownMenus("mumie_form_missing_server"):serverStructure.length?(serverController.init(serverStructure),courseController.init(),taskController.init(),multiTaskEditController.init(),problemSelectorController.init(),durationController().init()):disableDropDownMenus("mumie_form_no_server_conf"),multiTaskEditController.init(),addServerButton&&require(["auth_mumie/mumie_server_config"],(function(MumieServer){MumieServer.init(addServerButton,contextId)}))}}})); +define("mod_mumie/mod_form",["jquery","core/templates","auth_mumie/mumie_server_config","core/ajax"],(function(){const addServerButton=document.getElementById("id_add_server_button"),missingConfig=document.getElementsByName("mumie_missing_config")[0];let lmsSelectorUrl,systemLanguage,contextId,section;const durationController=function(){const durationSelector=document.getElementById("id_duration_selector"),gradedElem=document.getElementById("id_mumie_isgraded");function isUngraded(){return"0"===gradedElem.value}function updateDurationElements(){var _document$querySelect;isUngraded()?(durationSelector.setAttribute("disabled","disabled"),durationSelector.value="unlimited"):durationSelector.removeAttribute("disabled");"unlimited"===durationSelector.value?(document.getElementById("fitem_id_unlimited_info").style.display="",document.getElementById("fitem_id_timelimit").style.display="none",document.getElementById("fitem_id_timelimit_info").style.display="none",document.getElementById("fitem_id_duedate").style.display="none",document.getElementById("fitem_id_duedate_info").style.display="none"):"duedate"===durationSelector.value?(document.getElementById("fitem_id_duedate").style.display="",document.getElementById("fitem_id_duedate_info").style.display="",document.getElementById("fitem_id_unlimited_info").style.display="none",document.getElementById("fitem_id_timelimit").style.display="none",document.getElementById("fitem_id_timelimit_info").style.display="none"):"timelimit"===durationSelector.value&&(document.getElementById("fitem_id_timelimit").style.display="",document.getElementById("fitem_id_timelimit_info").style.display="",document.getElementById("fitem_id_duedate").style.display="none",document.getElementById("fitem_id_duedate_info").style.display="none",document.getElementById("fitem_id_unlimited_info").style.display="none");const duedatePropRow=null===(_document$querySelect=document.querySelector('[name="mumie_multi_edit_property"][value="duedate"]'))||void 0===_document$querySelect?void 0:_document$querySelect.closest("tr");if(duedatePropRow){const show="duedate"===durationSelector.value;if(duedatePropRow.style.display=show?"":"none",!show){const duedatePropertyCheckbox=duedatePropRow.querySelector('[name="mumie_multi_edit_property"]');duedatePropertyCheckbox&&duedatePropertyCheckbox.checked&&(duedatePropertyCheckbox.checked=!1,duedatePropertyCheckbox.dispatchEvent(new Event("change")))}}}return{init:function(){durationSelector.onchange=function(){updateDurationElements()},window.addEventListener("load",(()=>{updateDurationElements()})),updateDurationElements()},setDurationElements:updateDurationElements,isUngraded:isUngraded,setGradedElemValue:function(isGraded){gradedElem.value=isGraded?"1":"0"},getGradedElemValue:function(){return gradedElem.value}}}(),serverController=function(){let serverStructure;const serverDropDown=document.getElementById("id_server");return{init:function(structure){serverStructure=structure},getSelectedServer:function(){const selectedServerName=serverDropDown.options[serverDropDown.selectedIndex].text;return serverStructure.find((server=>server.name===selectedServerName))},disable:function(){serverDropDown.disabled=!0,function(elem){for(;elem.firstChild;)elem.removeChild(elem.firstChild)}(serverDropDown)}}}(),problemSelectorController=function(){const problemSelectorButton=document.getElementById("id_prb_selector_btn"),multiProblemSelectorButton=document.getElementById("id_multi_problem_selector_btn");let problemSelectorWindow;const mumieOrg=document.getElementsByName("mumie_org")[0].value;function sendResponse(response){problemSelectorWindow&&problemSelectorWindow.postMessage(JSON.stringify(response),lmsSelectorUrl)}function sendSuccess(){let message=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";sendResponse({success:!0,message:message})}function addMessageListener(){window.addEventListener("message",(event=>{if(event.origin===lmsSelectorUrl)try{const importObj=JSON.parse(event.data);Array.isArray(importObj)?taskController.setMultiSelection(importObj):(applyPickerPayloadToForm(importObj),require(["core/str","core/notification"],(function(str,notification){str.get_strings([{key:"mumie_form_updated_selection",component:"mod_mumie"}]).done((function(s){notification.addNotification({message:s[0],type:"info"})})).fail(notification.exception)}))),sendSuccess(),window.focus()}catch(error){!function(){sendResponse({success:!1,message:arguments.length>0&&void 0!==arguments[0]?arguments[0]:""})}(error.message)}}),!1)}function buildURL(){let multiSelect=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const gradingType=multiSelect?"all":taskController.getGradingType(),selectedServer=serverController.getSelectedServer().urlprefix,useSSO=shouldUseSSO(lmsSelectorUrl,selectedServer),base=useSSO?"/auth/mumie/problem_selector.php?org="+mumieOrg+"&serverurl="+encodeURIComponent(selectedServer)+"&problemlang="+langController.getSelectedLanguage()+"&origin="+encodeURIComponent(window.location.origin)+"&gradingtype="+gradingType+"&contextid="+contextId:lmsSelectorUrl+"/lms-problem-selector?org="+mumieOrg+"&serverUrl="+encodeURIComponent(selectedServer)+"&problemLang="+langController.getSelectedLanguage()+"&origin="+encodeURIComponent(window.location.origin)+"&uiLang="+systemLanguage+"&gradingType="+gradingType+"&multiCourse=true&worksheet=true";if(multiSelect)return base+(useSSO?"&multiselect=true":"&multiSelect=true");const selection=taskController.getDelocalizedTaskLink();return selection?base+"&selection="+selection:base}function shouldUseSSO(problemSelectorUrl,selectedServerUrl){return new URL(problemSelectorUrl).origin===new URL(selectedServerUrl).origin}return{init:function(){problemSelectorButton.onclick=function(){problemSelectorWindow=window.open(buildURL(),"_blank")},window.onclose=function(){sendSuccess()},window.addEventListener("beforeunload",(function(){sendSuccess()}),!1),addMessageListener(),multiProblemSelectorButton.onclick=function(e){e.preventDefault(),problemSelectorWindow=window.open(buildURL(!0),"_blank","toolbar=0,location=0,menubar=0")}},disable:function(){problemSelectorButton.disabled=!0}}}();function applyPickerPayloadToForm(payload){var _payload$worksheet;const isGraded=!1!==payload.isGraded,worksheet=null!==(_payload$worksheet=payload.worksheet)&&void 0!==_payload$worksheet?_payload$worksheet:null;courseController.setCourse(payload.path_to_coursefile),langController.setLanguage(payload.language),taskController.setSelection(payload.link,payload.language,payload.name),taskController.setIsGraded(isGraded),worksheetController.setWorksheet(worksheet)}const courseController=function(){const courseNameElem=document.getElementById("id_mumie_course"),courseFileElem=document.getElementsByName("mumie_coursefile")[0];function updateCourseName(){var _selectedCourse$name$;const selectedCourse=courseController.getSelectedCourse(),selectedLanguage=langController.getSelectedLanguage();selectedCourse&&selectedLanguage&&(courseNameElem.value=null===(_selectedCourse$name$=selectedCourse.name.find((translation=>translation.language===selectedLanguage)))||void 0===_selectedCourse$name$?void 0:_selectedCourse$name$.value)}return{init:function(){updateCourseName()},getSelectedCourse:function(){return serverController.getSelectedServer().courses.find((course=>course.coursefile===courseFileElem.value))},setCourse:function(courseFile){var coursefile;coursefile=courseFile,courseFileElem.value=coursefile,updateCourseName()}}}(),langController=function(){const languageElem=document.getElementById("id_language");return{getSelectedLanguage:function(){return languageElem.value},setLanguage:function(lang){languageElem.value=lang}}}(),taskController=function(){const taskSelectionInput=document.getElementsByName("taskurl")[0],nameElem=document.getElementById("id_name"),taskDisplayElement=document.getElementById("id_task_display_element");function updateTaskDisplayElement(localizedLink){taskDisplayElement.value=localizedLink}function updateTaskUri(link,language){const localizedLink=function(link,language){return link+"?lang="+language}(link,language);taskSelectionInput.value=localizedLink,updateTaskDisplayElement(localizedLink)}function setMultiSelection(tasks){const tasksField=document.getElementsByName("mumie_multi_tasks")[0],summary=document.getElementById("mumie_multi_tasks_summary");if(tasksField&&(tasksField.value=JSON.stringify(tasks)),summary){const taskListItems=tasks.map((task=>{const taskListItem=document.createElement("li");return taskListItem.textContent=task.name,taskListItem}));summary.innerHTML="";const summaryHeading=document.createElement("div"),taskList=document.createElement("ul");require(["core/str"],(function(Str){Str.get_string("mumie_multi_tasks_selected","mod_mumie",tasks.length).then((function(headingText){summaryHeading.textContent=headingText}))})),taskList.classList.add("mumie_multi_tasks_list"),taskListItems.forEach((taskListItem=>taskList.appendChild(taskListItem))),summary.appendChild(summaryHeading),summary.appendChild(taskList),summary.style.display="block"}const nameField=document.getElementById("id_name");nameField.disabled=!0,nameField.value="",nameField.removeAttribute("required");const taskDisplayElem=document.getElementById("id_task_display_element");taskDisplayElem&&(taskDisplayElem.value="");const courseNameElem=document.getElementById("id_mumie_course");courseNameElem&&(courseNameElem.value="");const nameFieldContainer=document.getElementById("fitem_id_name");nameFieldContainer&&nameFieldContainer.querySelectorAll('.req, .text-danger, [title="Required field"]').forEach((requiredIndicator=>{requiredIndicator.style.display="none"}));const requiredLegend=document.querySelector(".fdescription.required");requiredLegend&&(requiredLegend.style.display="none")}return{init:function(){updateTaskDisplayElement(taskSelectionInput.value)},setSelection:function(link,language,name){const tasksField=document.getElementsByName("mumie_multi_tasks")[0];tasksField&&(tasksField.value="");const summary=document.getElementById("mumie_multi_tasks_summary");summary&&(summary.style.display="none",summary.innerHTML="");const nameField=document.getElementById("id_name");nameField&&(nameField.disabled=!1,nameField.setAttribute("required","required"));const nameFieldContainer=document.getElementById("fitem_id_name");nameFieldContainer&&nameFieldContainer.querySelectorAll('.req, .text-danger, [title="Required field"]').forEach((requiredIndicator=>{requiredIndicator.style.display=""}));const requiredLegend=document.querySelector(".fdescription.required");requiredLegend&&(requiredLegend.style.display=""),updateTaskUri(link,language),function(name){nameElem.value=name}(name)},setIsGraded:function(isGraded){durationController.setGradedElemValue(isGraded),function(){const disabled=durationController.isUngraded();document.getElementById("id_points").disabled=disabled,document.getElementById("id_gradepass").disabled=disabled,document.getElementById("id_gradecat").disabled=disabled,durationController.setDurationElements()}()},getGradingType:function(){const isGraded=durationController.getGradedElemValue();return"1"===isGraded?"graded":"0"===isGraded?"ungraded":"all"},getDelocalizedTaskLink:function(){return(link=taskSelectionInput.value).includes("?lang=")?link.split("?lang=")[0]:link;var link},setMultiSelection:setMultiSelection,hasMultiTasks:function(){const tasksField=document.getElementsByName("mumie_multi_tasks")[0];return tasksField&&!!tasksField.value},submitMultiTasks:function(){var _document$getElements;const tasksField=document.getElementsByName("mumie_multi_tasks")[0],courseId=null===(_document$getElements=document.getElementsByName("course")[0])||void 0===_document$getElements?void 0:_document$getElements.value,submitButton=document.getElementById("id_submitbutton"),form=submitButton&&submitButton.closest("form"),tasks=JSON.parse(tasksField.value),tasksFormData=tasks.map((task=>(applyPickerPayloadToForm(task),function(form){const disabledFields=Array.from(form.querySelectorAll(":disabled"));disabledFields.forEach((field=>{field.disabled=!1}));try{const omit=["sesskey","_qf__mod_mumie_mod_form"];return Array.from(new FormData(form)).filter((_ref=>{let[key]=_ref;return!omit.includes(key)})).map((_ref2=>{let[key,value]=_ref2;return encodeURIComponent(key)+"="+encodeURIComponent(value)})).join("&")}finally{disabledFields.forEach((field=>{field.disabled=!0}))}}(form))));setMultiSelection(tasks),require(["core/ajax","core/notification"],(function(Ajax,notification){Ajax.call([{methodname:"mod_mumie_create_multiple_mumie_tasks",args:{contextid:parseInt(contextId),section:section,tasksformdata:tasksFormData}}])[0].done((function(){window.location.href=M.cfg.wwwroot+"/course/view.php?id="+courseId})).fail((function(error){notification.alert("",error.message)}))}))}}}(),multiTaskEditController=function(){const propertySelectionInputs=document.getElementsByName("mumie_multi_edit_property"),selectedTaskProperties=document.getElementsByName("mumie_selected_task_properties")[0];let selectedTaskProp=[];const taskSelectionInputs=document.getElementsByName("mumie_multi_edit_task"),selectedTasks=document.getElementsByName("mumie_selected_tasks")[0];let selectedTaskIds=[];const sectionInputs=document.getElementsByName("mumie_multi_edit_section");function setPropertySelectionListeners(){propertySelectionInputs.forEach((function(checkbox){checkbox.onchange=function(){checkbox.checked?selectedTaskProp.push(checkbox.value):selectedTaskProp=selectedTaskProp.filter((elem=>elem!==checkbox.value)),selectedTaskProperties.value=JSON.stringify(selectedTaskProp),function(){const duedateSelected=Array.from(propertySelectionInputs).some((checkbox=>"duedate"===checkbox.value&&checkbox.checked));document.querySelectorAll(".mumie-form-working-period-not-duedate-warning").forEach((function(elem){elem.style.display=duedateSelected?"":"none"}))}()}}))}return{init:function(){taskSelectionInputs.forEach((function(checkbox){checkbox.onchange=function(){checkbox.checked?selectedTaskIds.push(checkbox.value):selectedTaskIds=selectedTaskIds.filter((elem=>elem!==checkbox.value)),selectedTasks.value=JSON.stringify(selectedTaskIds)}})),setPropertySelectionListeners(),sectionInputs.forEach((function(sectionCheckbox){sectionCheckbox.onchange=function(){sectionCheckbox.checked?taskSelectionInputs.forEach((function(taskCheckbox){var array,element;taskCheckbox.getAttribute("section")===sectionCheckbox.value&&(taskCheckbox.checked=!0,array=selectedTaskIds,element=taskCheckbox.value,array.includes(element)||array.push(element))})):taskSelectionInputs.forEach((function(taskCheckbox){taskCheckbox.getAttribute("section")===sectionCheckbox.value&&(taskCheckbox.checked=!1,selectedTaskIds=selectedTaskIds.filter((elem=>taskCheckbox.value!==elem)))})),selectedTasks.value=JSON.stringify(selectedTaskIds)}}))}}}(),worksheetController=function(){const worksheetElement=document.getElementById("id_mumie_worksheet");return{setWorksheet:function(worksheet){worksheet?worksheetElement.setAttribute("value",JSON.stringify(worksheet)):worksheetElement.removeAttribute("value")}}}();function disableDropDownMenus(errorKey){require(["core/str","core/notification"],(function(str,notification){str.get_strings([{key:errorKey,component:"mod_mumie"}]).done((function(s){notification.addNotification({message:s[0]+""+missingConfig.getAttribute("value")+"",type:"problem"})})).fail(notification.exception)})),serverController.disable(),problemSelectorController.disable()}return{init:function(contextIdParam,prbSelectorUrl,lang,sectionParam){lmsSelectorUrl=prbSelectorUrl,systemLanguage=lang,contextId=contextIdParam,section=sectionParam;const isEdit=document.getElementById("id_name").getAttribute("value"),serverStructure=JSON.parse(document.getElementsByName("mumie_server_structure")[0].value);isEdit&&""!==document.getElementsByName("mumie_missing_config")[0].getAttribute("value")?disableDropDownMenus("mumie_form_missing_server"):serverStructure.length?(serverController.init(serverStructure),courseController.init(),taskController.init(),multiTaskEditController.init(),problemSelectorController.init(),durationController.init()):disableDropDownMenus("mumie_form_no_server_conf"),addServerButton&&require(["auth_mumie/mumie_server_config"],(function(MumieServer){MumieServer.init(addServerButton,contextId)}));const submitButton=document.getElementById("id_submitbutton"),form=submitButton&&submitButton.closest("form");if(form){let cancelClicked=!1;const cancelButton=document.getElementById("id_cancel");cancelButton&&cancelButton.addEventListener("click",(function(){cancelClicked=!0})),form.addEventListener("submit",(function(e){cancelClicked?cancelClicked=!1:taskController.hasMultiTasks()&&(e.preventDefault(),taskController.submitMultiTasks())}))}}}})); //# sourceMappingURL=mod_form.min.js.map \ No newline at end of file diff --git a/amd/build/mod_form.min.js.map b/amd/build/mod_form.min.js.map index 5c5d82b9..8f24a322 100644 --- a/amd/build/mod_form.min.js.map +++ b/amd/build/mod_form.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mod_form.min.js","sources":["../src/mod_form.js"],"sourcesContent":["define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_server_config', 'core/ajax'],\n function() {\n const addServerButton = document.getElementById(\"id_add_server_button\");\n const missingConfig = document.getElementsByName(\"mumie_missing_config\")[0];\n let lmsSelectorUrl;\n let systemLanguage;\n let contextId;\n\n\n const durationController = (function() {\n\n const durationSelector = document.getElementById('id_duration_selector');\n const gradedElem = document.getElementById('id_mumie_isgraded');\n\n /**\n * Returns if task is ungraded like courses and articles.\n */\n function isUngraded() {\n return gradedElem.value === '0';\n }\n\n /**\n * Updates the visibility of elements around duration selector.\n */\n function updateDurationElements() {\n const disabled = isUngraded();\n if (disabled) {\n durationSelector.setAttribute('disabled', 'disabled');\n durationSelector.value = 'unlimited';\n } else {\n durationSelector.removeAttribute('disabled');\n }\n\n const displayNone = 'none';\n\n if (durationSelector.value === 'unlimited') {\n document.getElementById('fitem_id_unlimited_info').style.display = '';\n\n document.getElementById('fitem_id_timelimit').style.display = displayNone;\n document.getElementById('fitem_id_timelimit_info').style.display = displayNone;\n document.getElementById('fitem_id_duedate').style.display = displayNone;\n document.getElementById('fitem_id_duedate_info').style.display = displayNone;\n } else if (durationSelector.value === 'duedate') {\n document.getElementById('fitem_id_duedate').style.display = '';\n document.getElementById('fitem_id_duedate_info').style.display = '';\n\n document.getElementById('fitem_id_unlimited_info').style.display = displayNone;\n document.getElementById('fitem_id_timelimit').style.display = displayNone;\n document.getElementById('fitem_id_timelimit_info').style.display = displayNone;\n } else if (durationSelector.value === 'timelimit') {\n document.getElementById('fitem_id_timelimit').style.display = '';\n document.getElementById('fitem_id_timelimit_info').style.display = '';\n\n document.getElementById('fitem_id_duedate').style.display = displayNone;\n document.getElementById('fitem_id_duedate_info').style.display = displayNone;\n document.getElementById('fitem_id_unlimited_info').style.display = displayNone;\n }\n }\n\n return {\n init: function() {\n durationSelector.onchange = function() {\n updateDurationElements();\n };\n window.addEventListener(\"load\", () => {\n updateDurationElements();\n });\n },\n setDurationElements: updateDurationElements,\n isUngraded: isUngraded,\n setGradedElemValue: function(isGraded) {\n gradedElem.value = isGraded ? '1' : '0';\n },\n getGradedElemValue: function() {\n return gradedElem.value;\n }\n };\n });\n\n const serverController = (function() {\n let serverStructure;\n const serverDropDown = document.getElementById(\"id_server\");\n\n return {\n init: function(structure) {\n serverStructure = structure;\n },\n getSelectedServer: function() {\n const selectedServerName = serverDropDown.options[serverDropDown.selectedIndex].text;\n return serverStructure.find(server => server.name === selectedServerName);\n },\n disable: function() {\n serverDropDown.disabled = true;\n removeChildElems(serverDropDown);\n }\n };\n })();\n\n const problemSelectorController = (function() {\n const problemSelectorButton = document.getElementById('id_prb_selector_btn');\n const multiProblemSelectorButton = document.getElementById('id_multi_problem_selector_btn');\n let problemSelectorWindow;\n const mumieOrg = document.getElementsByName('mumie_org')[0].value;\n\n /**\n * Send a message to the problem selector window.\n *\n * Don't do anything if there is no problem selector window.\n * @param {Object} response\n */\n function sendResponse(response) {\n if (!problemSelectorWindow) {\n return;\n }\n problemSelectorWindow.postMessage(JSON.stringify(response), lmsSelectorUrl);\n }\n\n /**\n * Send a success message to a problem selector window\n * @param {string} message\n */\n function sendSuccess(message = '') {\n sendResponse({\n success: true,\n message: message\n });\n }\n\n /**\n * Send a failure message to a problem selector window\n * @param {string} message\n */\n function sendFailure(message = '') {\n sendResponse({\n success: false,\n message: message\n });\n }\n\n /**\n * Add an event listener that accepts messages from LMS-Browser and updates the selected problem.\n */\n function addMessageListener() {\n window.addEventListener('message', (event) => {\n if (event.origin !== lmsSelectorUrl) {\n return;\n }\n const importObj = JSON.parse(event.data);\n const isGraded = importObj.isGraded !== false;\n const worksheet = importObj.worksheet ?? null;\n try {\n courseController.setCourse(importObj.path_to_coursefile);\n langController.setLanguage(importObj.language);\n taskController.setSelection(importObj.link, importObj.language, importObj.name);\n taskController.setIsGraded(isGraded);\n worksheetController.setWorksheet(worksheet);\n sendSuccess();\n window.focus();\n displayProblemSelectedMessage();\n } catch (error) {\n sendFailure(error.message);\n }\n }, false);\n }\n\n /**\n * Display a success message in Moodle that a problem was successfully selected.\n */\n function displayProblemSelectedMessage() {\n require(['core/str', \"core/notification\"], function(str, notification) {\n str.get_strings([{\n 'key': 'mumie_form_updated_selection',\n component: 'mod_mumie'\n }]).done(function(s) {\n notification.addNotification({\n message: s[0],\n type: \"info\"\n });\n }).fail(notification.exception);\n });\n }\n\n /**\n * Builds the URL to the Problem Selector\n * @returns {string} URL to the Problem Selector\n */\n function buildURL() {\n const gradingType = taskController.getGradingType();\n const selection = taskController.getDelocalizedTaskLink();\n const selectedServer = serverController.getSelectedServer().urlprefix;\n const useSSO = shouldUseSSO(lmsSelectorUrl, selectedServer);\n if (useSSO) {\n return '/auth/mumie/problem_selector.php?' +\n 'org=' +\n mumieOrg +\n '&serverurl=' +\n encodeURIComponent(selectedServer) +\n '&problemlang=' +\n langController.getSelectedLanguage() +\n '&origin=' + encodeURIComponent(window.location.origin) +\n '&gradingtype=' + gradingType +\n '&contextid=' + contextId +\n (selection ? '&selection=' + selection : '');\n }\n return lmsSelectorUrl +\n '/lms-problem-selector?' +\n 'org=' +\n mumieOrg +\n '&serverUrl=' +\n encodeURIComponent(selectedServer) +\n '&problemLang=' +\n langController.getSelectedLanguage() +\n '&origin=' + encodeURIComponent(window.location.origin) +\n '&uiLang=' + systemLanguage +\n '&gradingType=' + gradingType +\n '&multiCourse=true' +\n '&worksheet=true' +\n (selection ? '&selection=' + selection : '');\n }\n\n /**\n * Determines whether the Single Sign-On (SSO) should be used when opening the Problem Selector.\n * SSO is only supposed to be used when the Problem Selector URL has the same origin as the\n * URL of the selected MUMIE server.\n *\n * @param {string} problemSelectorUrl - The URL of the problem selector.\n * @param {string} selectedServerUrl - The URL of the selected MUMIE server\n * @returns {boolean} Whether SSO should be used for the Problem Selector or not\n */\n function shouldUseSSO(problemSelectorUrl, selectedServerUrl) {\n return new URL(problemSelectorUrl).origin === new URL(selectedServerUrl).origin;\n }\n\n return {\n init: function() {\n problemSelectorButton.onclick = function() {\n problemSelectorWindow = window.open(buildURL(), '_blank');\n };\n\n window.onclose = function() {\n sendSuccess();\n };\n\n window.addEventListener(\"beforeunload\", function() {\n sendSuccess();\n }, false);\n\n addMessageListener();\n\n multiProblemSelectorButton.onclick = function(e) {\n e.preventDefault();\n problemSelectorWindow = window.open(\n lmsSelectorUrl +\n '/lms-problem-selector?' +\n \"serverUrl=\" +\n encodeURIComponent(serverController.getSelectedServer().urlprefix) +\n '&gradingType=all',\n \"_blank\",\n 'toolbar=0,location=0,menubar=0'\n );\n };\n },\n disable: function() {\n problemSelectorButton.disabled = true;\n }\n };\n })();\n\n const courseController = (function() {\n const courseNameElem = document.getElementById(\"id_mumie_course\");\n const courseFileElem = document.getElementsByName(\"mumie_coursefile\")[0];\n\n\n /**\n * Update the hidden input field with the selected course's course file path\n *\n * @param {string} coursefile\n */\n function updateCourseFilePath(coursefile) {\n courseFileElem.value = coursefile;\n updateCourseName();\n }\n\n /**\n * Update displayed course name.\n */\n function updateCourseName() {\n const selectedCourse = courseController.getSelectedCourse();\n const selectedLanguage = langController.getSelectedLanguage();\n if (!selectedCourse || !selectedLanguage) {\n return;\n }\n courseNameElem.value = selectedCourse.name\n .find(translation => translation.language === selectedLanguage)?.value;\n }\n\n return {\n init: function() {\n updateCourseName();\n },\n getSelectedCourse: function() {\n const courses = serverController.getSelectedServer().courses;\n return courses.find(course => course.coursefile === courseFileElem.value);\n },\n setCourse: function(courseFile) {\n updateCourseFilePath(courseFile);\n }\n };\n })();\n\n const langController = (function() {\n const languageElem = document.getElementById(\"id_language\");\n return {\n getSelectedLanguage: function() {\n return languageElem.value;\n },\n setLanguage: function(lang) {\n languageElem.value = lang;\n }\n };\n })();\n\n const taskController = (function() {\n const taskSelectionInput = document.getElementsByName(\"taskurl\")[0];\n const nameElem = document.getElementById(\"id_name\");\n const taskDisplayElement = document.getElementById(\"id_task_display_element\");\n const LANG_REQUEST_PARAM_PREFIX = \"?lang=\";\n\n /**\n * Update the activity's name in the input field\n * @param {string} name\n */\n function updateName(name) {\n nameElem.value = name;\n }\n\n /**\n * @param {string} localizedLink\n */\n function updateTaskDisplayElement(localizedLink) {\n taskDisplayElement.value = localizedLink;\n }\n\n /**\n * Update task uri\n * @param {string} link\n * @param {string} language\n */\n function updateTaskUri(link, language) {\n const localizedLink = localizeLink(link, language);\n taskSelectionInput.value = localizedLink;\n updateTaskDisplayElement(localizedLink);\n }\n\n /**\n * Add lang request param to link\n * @param {string} link\n * @param {string} language\n * @returns {string} Link with lang request param\n */\n function localizeLink(link, language) {\n return link + LANG_REQUEST_PARAM_PREFIX + language;\n }\n\n /**\n * Remove lang request param from link\n * @param {string} link Link that may have lang request param\n * @returns {string} Link without lang request param\n */\n function delocalizeLink(link) {\n if (link.includes(LANG_REQUEST_PARAM_PREFIX)) {\n return link.split(LANG_REQUEST_PARAM_PREFIX)[0];\n }\n return link;\n }\n\n /**\n * Form inputs related to grades should be disabled if the MUMIE Task is not graded.\n */\n function updateGradeEditability() {\n const disabled = durationController().isUngraded();\n document.getElementById('id_points').disabled = disabled;\n document.getElementById('id_gradepass').disabled = disabled;\n document.getElementById('id_gradecat').disabled = disabled;\n durationController().setDurationElements();\n }\n\n return {\n init: function() {\n updateTaskDisplayElement(taskSelectionInput.value);\n },\n setSelection: function(link, language, name) {\n updateTaskUri(link, language);\n updateName(name);\n },\n setIsGraded: function(isGraded) {\n durationController().setGradedElemValue(isGraded);\n updateGradeEditability();\n },\n getGradingType: function() {\n const isGraded = durationController().getGradedElemValue();\n if (isGraded === '1') {\n return 'graded';\n } else if (isGraded === '0') {\n return 'ungraded';\n }\n return 'all';\n },\n getDelocalizedTaskLink: function() {\n return delocalizeLink(taskSelectionInput.value);\n }\n };\n })();\n\n\n const multiTaskEditController = (function() {\n const propertySelectionInputs = document.getElementsByName(\"mumie_multi_edit_property\");\n const selectedTaskProperties = document.getElementsByName(\"mumie_selected_task_properties\")[0];\n let selectedTaskProp = [];\n const taskSelectionInputs = document.getElementsByName(\"mumie_multi_edit_task\");\n const selectedTasks = document.getElementsByName(\"mumie_selected_tasks\")[0];\n let selectedTaskIds = [];\n const sectionInputs = document.getElementsByName(\"mumie_multi_edit_section\");\n\n /**\n * Push an element to an array, if it's not already included.\n *\n * @param {string[]} array\n * @param {string} element\n */\n function pushIfNotExists(array, element) {\n if (!array.includes(element)) {\n array.push(element);\n }\n }\n\n /**\n * Set selection listeners for other MUMIE Tasks in the course.\n */\n function setTaskSelectionListeners() {\n taskSelectionInputs.forEach(function(checkbox) {\n checkbox.onchange = function() {\n if (!checkbox.checked) {\n selectedTaskIds = selectedTaskIds.filter(elem => elem !== checkbox.value);\n } else {\n selectedTaskIds.push(checkbox.value);\n }\n selectedTasks.value = JSON.stringify(selectedTaskIds);\n };\n });\n }\n\n /**\n * Set selection listeners for properties to apply to MUMIE Tasks in the course.\n */\n function setPropertySelectionListeners() {\n propertySelectionInputs.forEach(function(checkbox) {\n checkbox.onchange = function() {\n if (!checkbox.checked) {\n selectedTaskProp = selectedTaskProp.filter(elem => elem !== checkbox.value);\n } else {\n selectedTaskProp.push(checkbox.value);\n }\n selectedTaskProperties.value = JSON.stringify(selectedTaskProp);\n };\n });\n }\n\n /**\n * Set selection listeners for entire section of MUMIE Tasks in the course\n */\n function setSectionSelectionListeners() {\n sectionInputs.forEach(function(sectionCheckbox) {\n sectionCheckbox.onchange = function() {\n if (!sectionCheckbox.checked) {\n taskSelectionInputs.forEach(function(taskCheckbox) {\n if (taskCheckbox.getAttribute('section') === sectionCheckbox.value) {\n taskCheckbox.checked = false;\n selectedTaskIds = selectedTaskIds.filter(elem => taskCheckbox.value !== elem);\n }\n });\n } else {\n taskSelectionInputs.forEach(function(taskCheckbox) {\n if (taskCheckbox.getAttribute('section') === sectionCheckbox.value) {\n taskCheckbox.checked = true;\n pushIfNotExists(selectedTaskIds, taskCheckbox.value);\n }\n });\n }\n selectedTasks.value = JSON.stringify(selectedTaskIds);\n };\n });\n }\n\n return {\n init: function() {\n setTaskSelectionListeners();\n setPropertySelectionListeners();\n setSectionSelectionListeners();\n },\n };\n })();\n\n const worksheetController = (function() {\n const worksheetElement = document.getElementById(\"id_mumie_worksheet\");\n return {\n setWorksheet: function(worksheet) {\n if (worksheet) {\n worksheetElement.setAttribute(\"value\", JSON.stringify(worksheet));\n } else {\n worksheetElement.removeAttribute(\"value\");\n }\n }\n };\n })();\n\n /**\n * Disable all dropdown menus and show notification\n * @param {string} errorKey\n */\n function disableDropDownMenus(errorKey) {\n require(['core/str', \"core/notification\"], function(str, notification) {\n str.get_strings([{\n 'key': errorKey,\n component: 'mod_mumie'\n }]).done(function(s) {\n notification.addNotification({\n message: s[0] + \"\" + missingConfig.getAttribute(\"value\") + \"\",\n type: \"problem\"\n });\n }).fail(notification.exception);\n });\n serverController.disable();\n problemSelectorController.disable();\n }\n\n return {\n init: function(contextIdParam, prbSelectorUrl, lang) {\n lmsSelectorUrl = prbSelectorUrl;\n systemLanguage = lang;\n contextId = contextIdParam;\n const isEdit = document.getElementById(\"id_name\").getAttribute('value');\n const serverStructure = JSON.parse(document.getElementsByName('mumie_server_structure')[0].value);\n if (isEdit && !serverConfigExists()) {\n disableDropDownMenus('mumie_form_missing_server');\n } else if (!serverStructure.length) {\n disableDropDownMenus('mumie_form_no_server_conf');\n } else {\n serverController.init(serverStructure);\n courseController.init();\n taskController.init();\n multiTaskEditController.init();\n problemSelectorController.init();\n durationController().init();\n }\n multiTaskEditController.init();\n if (addServerButton) {\n require(['auth_mumie/mumie_server_config'], function(MumieServer) {\n MumieServer.init(addServerButton, contextId);\n });\n }\n }\n };\n\n /**\n * Remove all child elements of a given html element\n * @param {Object} elem\n */\n function removeChildElems(elem) {\n while (elem.firstChild) {\n elem.removeChild(elem.firstChild);\n }\n }\n\n /**\n * Check, if the flag for an existing config is set\n * @returns {boolean}\n */\n function serverConfigExists() {\n return document.getElementsByName(\"mumie_missing_config\")[0].getAttribute(\"value\") === \"\";\n }\n });"],"names":["define","addServerButton","document","getElementById","missingConfig","getElementsByName","lmsSelectorUrl","systemLanguage","contextId","durationController","durationSelector","gradedElem","isUngraded","value","updateDurationElements","setAttribute","removeAttribute","style","display","init","onchange","window","addEventListener","setDurationElements","setGradedElemValue","isGraded","getGradedElemValue","serverController","serverStructure","serverDropDown","structure","getSelectedServer","selectedServerName","options","selectedIndex","text","find","server","name","disable","disabled","elem","firstChild","removeChild","removeChildElems","problemSelectorController","problemSelectorButton","multiProblemSelectorButton","problemSelectorWindow","mumieOrg","sendResponse","response","postMessage","JSON","stringify","sendSuccess","message","success","addMessageListener","event","origin","importObj","parse","data","worksheet","courseController","setCourse","path_to_coursefile","langController","setLanguage","language","taskController","setSelection","link","setIsGraded","worksheetController","setWorksheet","focus","require","str","notification","get_strings","component","done","s","addNotification","type","fail","exception","error","sendFailure","buildURL","gradingType","getGradingType","selection","getDelocalizedTaskLink","selectedServer","urlprefix","selectedServerUrl","URL","encodeURIComponent","getSelectedLanguage","location","onclick","open","onclose","e","preventDefault","courseNameElem","courseFileElem","updateCourseName","selectedCourse","getSelectedCourse","selectedLanguage","translation","_selectedCourse$name$","courses","course","coursefile","courseFile","languageElem","lang","taskSelectionInput","nameElem","taskDisplayElement","updateTaskDisplayElement","localizedLink","updateTaskUri","localizeLink","updateName","updateGradeEditability","includes","split","multiTaskEditController","propertySelectionInputs","selectedTaskProperties","selectedTaskProp","taskSelectionInputs","selectedTasks","selectedTaskIds","sectionInputs","forEach","checkbox","checked","push","filter","sectionCheckbox","taskCheckbox","array","element","getAttribute","worksheetElement","disableDropDownMenus","errorKey","contextIdParam","prbSelectorUrl","isEdit","length","MumieServer"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mod_form.min.js","sources":["../src/mod_form.js"],"sourcesContent":["define(['jquery', 'core/templates', 'auth_mumie/mumie_server_config', 'core/ajax'],\n function() {\n const addServerButton = document.getElementById(\"id_add_server_button\");\n const missingConfig = document.getElementsByName(\"mumie_missing_config\")[0];\n let lmsSelectorUrl;\n let systemLanguage;\n let contextId;\n let section;\n\n\n const durationController = (function() {\n\n const durationSelector = document.getElementById('id_duration_selector');\n const gradedElem = document.getElementById('id_mumie_isgraded');\n\n /**\n * Returns if task is ungraded like courses and articles.\n */\n function isUngraded() {\n return gradedElem.value === '0';\n }\n\n /**\n * Updates the visibility of elements around duration selector.\n */\n function updateDurationElements() {\n const disabled = isUngraded();\n if (disabled) {\n durationSelector.setAttribute('disabled', 'disabled');\n durationSelector.value = 'unlimited';\n } else {\n durationSelector.removeAttribute('disabled');\n }\n\n const displayNone = 'none';\n\n if (durationSelector.value === 'unlimited') {\n document.getElementById('fitem_id_unlimited_info').style.display = '';\n\n document.getElementById('fitem_id_timelimit').style.display = displayNone;\n document.getElementById('fitem_id_timelimit_info').style.display = displayNone;\n document.getElementById('fitem_id_duedate').style.display = displayNone;\n document.getElementById('fitem_id_duedate_info').style.display = displayNone;\n } else if (durationSelector.value === 'duedate') {\n document.getElementById('fitem_id_duedate').style.display = '';\n document.getElementById('fitem_id_duedate_info').style.display = '';\n\n document.getElementById('fitem_id_unlimited_info').style.display = displayNone;\n document.getElementById('fitem_id_timelimit').style.display = displayNone;\n document.getElementById('fitem_id_timelimit_info').style.display = displayNone;\n } else if (durationSelector.value === 'timelimit') {\n document.getElementById('fitem_id_timelimit').style.display = '';\n document.getElementById('fitem_id_timelimit_info').style.display = '';\n\n document.getElementById('fitem_id_duedate').style.display = displayNone;\n document.getElementById('fitem_id_duedate_info').style.display = displayNone;\n document.getElementById('fitem_id_unlimited_info').style.display = displayNone;\n }\n\n const duedatePropRow = document\n .querySelector('[name=\"mumie_multi_edit_property\"][value=\"duedate\"]')\n ?.closest('tr');\n if (duedatePropRow) {\n const show = durationSelector.value === 'duedate';\n duedatePropRow.style.display = show ? '' : displayNone;\n if (!show) {\n const duedatePropertyCheckbox = duedatePropRow.querySelector('[name=\"mumie_multi_edit_property\"]');\n if (duedatePropertyCheckbox && duedatePropertyCheckbox.checked) {\n duedatePropertyCheckbox.checked = false;\n duedatePropertyCheckbox.dispatchEvent(new Event('change'));\n }\n }\n }\n }\n\n return {\n init: function() {\n durationSelector.onchange = function() {\n updateDurationElements();\n };\n window.addEventListener(\"load\", () => {\n updateDurationElements();\n });\n updateDurationElements();\n },\n setDurationElements: updateDurationElements,\n isUngraded: isUngraded,\n setGradedElemValue: function(isGraded) {\n gradedElem.value = isGraded ? '1' : '0';\n },\n getGradedElemValue: function() {\n return gradedElem.value;\n }\n };\n })();\n\n const serverController = (function() {\n let serverStructure;\n const serverDropDown = document.getElementById(\"id_server\");\n\n return {\n init: function(structure) {\n serverStructure = structure;\n },\n getSelectedServer: function() {\n const selectedServerName = serverDropDown.options[serverDropDown.selectedIndex].text;\n return serverStructure.find(server => server.name === selectedServerName);\n },\n disable: function() {\n serverDropDown.disabled = true;\n removeChildElems(serverDropDown);\n }\n };\n })();\n\n const problemSelectorController = (function() {\n const problemSelectorButton = document.getElementById('id_prb_selector_btn');\n const multiProblemSelectorButton = document.getElementById('id_multi_problem_selector_btn');\n let problemSelectorWindow;\n const mumieOrg = document.getElementsByName('mumie_org')[0].value;\n\n /**\n * Send a message to the problem selector window.\n *\n * Don't do anything if there is no problem selector window.\n * @param {Object} response\n */\n function sendResponse(response) {\n if (!problemSelectorWindow) {\n return;\n }\n problemSelectorWindow.postMessage(JSON.stringify(response), lmsSelectorUrl);\n }\n\n /**\n * Send a success message to a problem selector window\n * @param {string} message\n */\n function sendSuccess(message = '') {\n sendResponse({\n success: true,\n message: message\n });\n }\n\n /**\n * Send a failure message to a problem selector window\n * @param {string} message\n */\n function sendFailure(message = '') {\n sendResponse({\n success: false,\n message: message\n });\n }\n\n /**\n * Add an event listener that accepts messages from LMS-Browser and updates the selected problem.\n */\n function addMessageListener() {\n window.addEventListener('message', (event) => {\n if (event.origin !== lmsSelectorUrl) {\n return;\n }\n try {\n const importObj = JSON.parse(event.data);\n if (Array.isArray(importObj)) {\n taskController.setMultiSelection(importObj);\n } else {\n applyPickerPayloadToForm(importObj);\n displayProblemSelectedMessage();\n }\n sendSuccess();\n window.focus();\n } catch (error) {\n sendFailure(error.message);\n }\n }, false);\n }\n\n /**\n * Display a success message in Moodle that a problem was successfully selected.\n */\n function displayProblemSelectedMessage() {\n require(['core/str', \"core/notification\"], function(str, notification) {\n str.get_strings([{\n 'key': 'mumie_form_updated_selection',\n component: 'mod_mumie'\n }]).done(function(s) {\n notification.addNotification({\n message: s[0],\n type: \"info\"\n });\n }).fail(notification.exception);\n });\n }\n\n /**\n * Builds the URL to the Problem Selector\n * @param {boolean} multiSelect Whether to build a multi-select URL\n * @returns {string} URL to the Problem Selector\n */\n function buildURL(multiSelect = false) {\n const gradingType = multiSelect ? 'all' : taskController.getGradingType();\n const selectedServer = serverController.getSelectedServer().urlprefix;\n const useSSO = shouldUseSSO(lmsSelectorUrl, selectedServer);\n\n const base = useSSO\n ? '/auth/mumie/problem_selector.php?' +\n 'org=' + mumieOrg +\n '&serverurl=' + encodeURIComponent(selectedServer) +\n '&problemlang=' + langController.getSelectedLanguage() +\n '&origin=' + encodeURIComponent(window.location.origin) +\n '&gradingtype=' + gradingType +\n '&contextid=' + contextId\n : lmsSelectorUrl + '/lms-problem-selector?' +\n 'org=' + mumieOrg +\n '&serverUrl=' + encodeURIComponent(selectedServer) +\n '&problemLang=' + langController.getSelectedLanguage() +\n '&origin=' + encodeURIComponent(window.location.origin) +\n '&uiLang=' + systemLanguage +\n '&gradingType=' + gradingType +\n '&multiCourse=true' +\n '&worksheet=true';\n\n if (multiSelect) {\n return base + (useSSO ? '&multiselect=true' : '&multiSelect=true');\n }\n const selection = taskController.getDelocalizedTaskLink();\n return selection ? base + '&selection=' + selection : base;\n }\n\n /**\n * Determines whether the Single Sign-On (SSO) should be used when opening the Problem Selector.\n * SSO is only supposed to be used when the Problem Selector URL has the same origin as the\n * URL of the selected MUMIE server.\n *\n * @param {string} problemSelectorUrl - The URL of the problem selector.\n * @param {string} selectedServerUrl - The URL of the selected MUMIE server\n * @returns {boolean} Whether SSO should be used for the Problem Selector or not\n */\n function shouldUseSSO(problemSelectorUrl, selectedServerUrl) {\n return new URL(problemSelectorUrl).origin === new URL(selectedServerUrl).origin;\n }\n\n return {\n init: function() {\n problemSelectorButton.onclick = function() {\n problemSelectorWindow = window.open(buildURL(), '_blank');\n };\n\n window.onclose = function() {\n sendSuccess();\n };\n\n window.addEventListener(\"beforeunload\", function() {\n sendSuccess();\n }, false);\n\n addMessageListener();\n\n multiProblemSelectorButton.onclick = function(e) {\n e.preventDefault();\n problemSelectorWindow = window.open(buildURL(true), '_blank', 'toolbar=0,location=0,menubar=0');\n };\n },\n disable: function() {\n problemSelectorButton.disabled = true;\n },\n };\n })();\n\n /**\n * Apply a picker payload to the form's per-task fields.\n *\n * Single source of truth for picker-payload → form-field mapping.\n * Used by both the single-task postMessage handler and the multi-task\n * submit loop, so new picker fields only need to be wired here.\n *\n * @param {Object} payload picker payload for one task\n */\n function applyPickerPayloadToForm(payload) {\n const isGraded = payload.isGraded !== false;\n const worksheet = payload.worksheet ?? null;\n courseController.setCourse(payload.path_to_coursefile);\n langController.setLanguage(payload.language);\n taskController.setSelection(payload.link, payload.language, payload.name);\n taskController.setIsGraded(isGraded);\n worksheetController.setWorksheet(worksheet);\n }\n\n const courseController = (function() {\n const courseNameElem = document.getElementById(\"id_mumie_course\");\n const courseFileElem = document.getElementsByName(\"mumie_coursefile\")[0];\n\n\n /**\n * Update the hidden input field with the selected course's course file path\n *\n * @param {string} coursefile\n */\n function updateCourseFilePath(coursefile) {\n courseFileElem.value = coursefile;\n updateCourseName();\n }\n\n /**\n * Update displayed course name.\n */\n function updateCourseName() {\n const selectedCourse = courseController.getSelectedCourse();\n const selectedLanguage = langController.getSelectedLanguage();\n if (!selectedCourse || !selectedLanguage) {\n return;\n }\n courseNameElem.value = selectedCourse.name\n .find(translation => translation.language === selectedLanguage)?.value;\n }\n\n return {\n init: function() {\n updateCourseName();\n },\n getSelectedCourse: function() {\n const courses = serverController.getSelectedServer().courses;\n return courses.find(course => course.coursefile === courseFileElem.value);\n },\n setCourse: function(courseFile) {\n updateCourseFilePath(courseFile);\n }\n };\n })();\n\n const langController = (function() {\n const languageElem = document.getElementById(\"id_language\");\n return {\n getSelectedLanguage: function() {\n return languageElem.value;\n },\n setLanguage: function(lang) {\n languageElem.value = lang;\n }\n };\n })();\n\n const taskController = (function() {\n const taskSelectionInput = document.getElementsByName(\"taskurl\")[0];\n const nameElem = document.getElementById(\"id_name\");\n const taskDisplayElement = document.getElementById(\"id_task_display_element\");\n const LANG_REQUEST_PARAM_PREFIX = \"?lang=\";\n\n /**\n * Update the activity's name in the input field\n * @param {string} name\n */\n function updateName(name) {\n nameElem.value = name;\n }\n\n /**\n * @param {string} localizedLink\n */\n function updateTaskDisplayElement(localizedLink) {\n taskDisplayElement.value = localizedLink;\n }\n\n /**\n * Update task uri\n * @param {string} link\n * @param {string} language\n */\n function updateTaskUri(link, language) {\n const localizedLink = localizeLink(link, language);\n taskSelectionInput.value = localizedLink;\n updateTaskDisplayElement(localizedLink);\n }\n\n /**\n * Add lang request param to link\n * @param {string} link\n * @param {string} language\n * @returns {string} Link with lang request param\n */\n function localizeLink(link, language) {\n return link + LANG_REQUEST_PARAM_PREFIX + language;\n }\n\n /**\n * Remove lang request param from link\n * @param {string} link Link that may have lang request param\n * @returns {string} Link without lang request param\n */\n function delocalizeLink(link) {\n if (link.includes(LANG_REQUEST_PARAM_PREFIX)) {\n return link.split(LANG_REQUEST_PARAM_PREFIX)[0];\n }\n return link;\n }\n\n /**\n * Form inputs related to grades should be disabled if the MUMIE Task is not graded.\n */\n function updateGradeEditability() {\n const disabled = durationController.isUngraded();\n document.getElementById('id_points').disabled = disabled;\n document.getElementById('id_gradepass').disabled = disabled;\n document.getElementById('id_gradecat').disabled = disabled;\n durationController.setDurationElements();\n }\n\n /**\n * Store selected tasks and update UI for multi-task creation.\n * @param {Array} tasks array of task objects from the selector\n */\n function setMultiSelection(tasks) {\n const tasksField = document.getElementsByName('mumie_multi_tasks')[0];\n const summary = document.getElementById('mumie_multi_tasks_summary');\n if (tasksField) {\n tasksField.value = JSON.stringify(tasks);\n }\n if (summary) {\n const taskListItems = tasks.map(task => {\n const taskListItem = document.createElement('li');\n taskListItem.textContent = task.name;\n return taskListItem;\n });\n summary.innerHTML = '';\n const summaryHeading = document.createElement('div');\n const taskList = document.createElement('ul');\n require(['core/str'], function(Str) {\n Str.get_string('mumie_multi_tasks_selected', 'mod_mumie', tasks.length)\n .then(function(headingText) {\n summaryHeading.textContent = headingText;\n });\n });\n taskList.classList.add('mumie_multi_tasks_list');\n taskListItems.forEach(taskListItem => taskList.appendChild(taskListItem));\n summary.appendChild(summaryHeading);\n summary.appendChild(taskList);\n summary.style.display = 'block';\n }\n const nameField = document.getElementById('id_name');\n nameField.disabled = true;\n nameField.value = '';\n nameField.removeAttribute('required');\n const taskDisplayElem = document.getElementById('id_task_display_element');\n if (taskDisplayElem) { taskDisplayElem.value = ''; }\n const courseNameElem = document.getElementById('id_mumie_course');\n if (courseNameElem) { courseNameElem.value = ''; }\n const nameFieldContainer = document.getElementById('fitem_id_name');\n if (nameFieldContainer) {\n nameFieldContainer.querySelectorAll('.req, .text-danger, [title=\"Required field\"]')\n .forEach(requiredIndicator => { requiredIndicator.style.display = 'none'; });\n }\n const requiredLegend = document.querySelector('.fdescription.required');\n if (requiredLegend) {\n requiredLegend.style.display = 'none';\n }\n }\n\n /**\n * Serialize a form into a URL-encoded POST string, omitting form mechanics\n * the webservice does not need (sesskey, qf form marker).\n *\n * Disabled form controls are temporarily enabled so FormData includes them:\n * the picker writes values to display-only inputs (e.g. mumie_course) that\n * the server-side validator still expects to receive.\n *\n * @param {HTMLFormElement} form\n * @returns {string}\n */\n function serializeFormFields(form) {\n const disabledFields = Array.from(form.querySelectorAll(':disabled'));\n disabledFields.forEach(field => { field.disabled = false; });\n try {\n const omit = ['sesskey', '_qf__mod_mumie_mod_form'];\n return Array.from(new FormData(form))\n .filter(([key]) => !omit.includes(key))\n .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))\n .join('&');\n } finally {\n disabledFields.forEach(field => { field.disabled = true; });\n }\n }\n\n /**\n * Submit multi-tasks via AJAX, then redirect to course.\n *\n * Builds one full form payload per task by applying each picker payload\n * to the live form and serializing it. The server treats each entry as\n * an independent single-task submission, so picker-payload → form-field\n * mapping lives in applyPickerPayloadToForm and is shared with the\n * single-task path. Validation runs server-side via the shared form\n * pipeline; errors surface as a modal alert with the validator's\n * localized message.\n */\n function submitMultiTasks() {\n const tasksField = document.getElementsByName('mumie_multi_tasks')[0];\n const courseId = document.getElementsByName('course')[0]?.value;\n const submitButton = document.getElementById('id_submitbutton');\n const form = submitButton && submitButton.closest('form');\n const tasks = JSON.parse(tasksField.value);\n\n const tasksFormData = tasks.map(task => {\n applyPickerPayloadToForm(task);\n return serializeFormFields(form);\n });\n\n // applyPickerPayloadToForm leaves the form in single-task mode of the last task;\n // restore the multi-task UI so the form is coherent if the AJAX call fails.\n setMultiSelection(tasks);\n\n require(['core/ajax', 'core/notification'], function(Ajax, notification) {\n Ajax.call([{\n methodname: 'mod_mumie_create_multiple_mumie_tasks',\n args: {\n contextid: parseInt(contextId),\n section: section,\n tasksformdata: tasksFormData,\n },\n }])[0].done(function() {\n window.location.href = M.cfg.wwwroot + '/course/view.php?id=' + courseId;\n }).fail(function(error) {\n notification.alert('', error.message);\n });\n });\n }\n\n return {\n init: function() {\n updateTaskDisplayElement(taskSelectionInput.value);\n },\n setSelection: function(link, language, name) {\n const tasksField = document.getElementsByName('mumie_multi_tasks')[0];\n if (tasksField) {\n tasksField.value = '';\n }\n const summary = document.getElementById('mumie_multi_tasks_summary');\n if (summary) {\n summary.style.display = 'none';\n summary.innerHTML = '';\n }\n const nameField = document.getElementById('id_name');\n if (nameField) {\n nameField.disabled = false;\n nameField.setAttribute('required', 'required');\n }\n const nameFieldContainer = document.getElementById('fitem_id_name');\n if (nameFieldContainer) {\n nameFieldContainer.querySelectorAll('.req, .text-danger, [title=\"Required field\"]')\n .forEach(requiredIndicator => { requiredIndicator.style.display = ''; });\n }\n const requiredLegend = document.querySelector('.fdescription.required');\n if (requiredLegend) {\n requiredLegend.style.display = '';\n }\n updateTaskUri(link, language);\n updateName(name);\n },\n setIsGraded: function(isGraded) {\n durationController.setGradedElemValue(isGraded);\n updateGradeEditability();\n },\n getGradingType: function() {\n const isGraded = durationController.getGradedElemValue();\n if (isGraded === '1') {\n return 'graded';\n } else if (isGraded === '0') {\n return 'ungraded';\n }\n return 'all';\n },\n getDelocalizedTaskLink: function() {\n return delocalizeLink(taskSelectionInput.value);\n },\n setMultiSelection: setMultiSelection,\n hasMultiTasks: function() {\n const tasksField = document.getElementsByName('mumie_multi_tasks')[0];\n return tasksField && !!tasksField.value;\n },\n submitMultiTasks: submitMultiTasks,\n };\n })();\n\n\n const multiTaskEditController = (function() {\n const propertySelectionInputs = document.getElementsByName(\"mumie_multi_edit_property\");\n const selectedTaskProperties = document.getElementsByName(\"mumie_selected_task_properties\")[0];\n let selectedTaskProp = [];\n const taskSelectionInputs = document.getElementsByName(\"mumie_multi_edit_task\");\n const selectedTasks = document.getElementsByName(\"mumie_selected_tasks\")[0];\n let selectedTaskIds = [];\n const sectionInputs = document.getElementsByName(\"mumie_multi_edit_section\");\n\n /**\n * Push an element to an array, if it's not already included.\n *\n * @param {string[]} array\n * @param {string} element\n */\n function pushIfNotExists(array, element) {\n if (!array.includes(element)) {\n array.push(element);\n }\n }\n\n /**\n * Set selection listeners for other MUMIE Tasks in the course.\n */\n function setTaskSelectionListeners() {\n taskSelectionInputs.forEach(function(checkbox) {\n checkbox.onchange = function() {\n if (!checkbox.checked) {\n selectedTaskIds = selectedTaskIds.filter(elem => elem !== checkbox.value);\n } else {\n selectedTaskIds.push(checkbox.value);\n }\n selectedTasks.value = JSON.stringify(selectedTaskIds);\n };\n });\n }\n\n /**\n * Show or hide per-task \"Working period: not Deadline\" warnings based on whether the duedate property is selected.\n */\n function updateNoDuedateWarnings() {\n const duedateSelected = Array.from(propertySelectionInputs)\n .some(checkbox => checkbox.value === 'duedate' && checkbox.checked);\n document.querySelectorAll('.mumie-form-working-period-not-duedate-warning').forEach(function(elem) {\n elem.style.display = duedateSelected ? '' : 'none';\n });\n }\n\n /**\n * Set selection listeners for properties to apply to MUMIE Tasks in the course.\n */\n function setPropertySelectionListeners() {\n propertySelectionInputs.forEach(function(checkbox) {\n checkbox.onchange = function() {\n if (!checkbox.checked) {\n selectedTaskProp = selectedTaskProp.filter(elem => elem !== checkbox.value);\n } else {\n selectedTaskProp.push(checkbox.value);\n }\n selectedTaskProperties.value = JSON.stringify(selectedTaskProp);\n updateNoDuedateWarnings();\n };\n });\n }\n\n /**\n * Set selection listeners for entire section of MUMIE Tasks in the course\n */\n function setSectionSelectionListeners() {\n sectionInputs.forEach(function(sectionCheckbox) {\n sectionCheckbox.onchange = function() {\n if (!sectionCheckbox.checked) {\n taskSelectionInputs.forEach(function(taskCheckbox) {\n if (taskCheckbox.getAttribute('section') === sectionCheckbox.value) {\n taskCheckbox.checked = false;\n selectedTaskIds = selectedTaskIds.filter(elem => taskCheckbox.value !== elem);\n }\n });\n } else {\n taskSelectionInputs.forEach(function(taskCheckbox) {\n if (taskCheckbox.getAttribute('section') === sectionCheckbox.value) {\n taskCheckbox.checked = true;\n pushIfNotExists(selectedTaskIds, taskCheckbox.value);\n }\n });\n }\n selectedTasks.value = JSON.stringify(selectedTaskIds);\n };\n });\n }\n\n return {\n init: function() {\n setTaskSelectionListeners();\n setPropertySelectionListeners();\n setSectionSelectionListeners();\n },\n };\n })();\n\n const worksheetController = (function() {\n const worksheetElement = document.getElementById(\"id_mumie_worksheet\");\n return {\n setWorksheet: function(worksheet) {\n if (worksheet) {\n worksheetElement.setAttribute(\"value\", JSON.stringify(worksheet));\n } else {\n worksheetElement.removeAttribute(\"value\");\n }\n }\n };\n })();\n\n /**\n * Disable all dropdown menus and show notification\n * @param {string} errorKey\n */\n function disableDropDownMenus(errorKey) {\n require(['core/str', \"core/notification\"], function(str, notification) {\n str.get_strings([{\n 'key': errorKey,\n component: 'mod_mumie'\n }]).done(function(s) {\n notification.addNotification({\n message: s[0] + \"\" + missingConfig.getAttribute(\"value\") + \"\",\n type: \"problem\"\n });\n }).fail(notification.exception);\n });\n serverController.disable();\n problemSelectorController.disable();\n }\n\n return {\n init: function(contextIdParam, prbSelectorUrl, lang, sectionParam) {\n lmsSelectorUrl = prbSelectorUrl;\n systemLanguage = lang;\n contextId = contextIdParam;\n section = sectionParam;\n const isEdit = document.getElementById(\"id_name\").getAttribute('value');\n const serverStructure = JSON.parse(document.getElementsByName('mumie_server_structure')[0].value);\n if (isEdit && !serverConfigExists()) {\n disableDropDownMenus('mumie_form_missing_server');\n } else if (!serverStructure.length) {\n disableDropDownMenus('mumie_form_no_server_conf');\n } else {\n serverController.init(serverStructure);\n courseController.init();\n taskController.init();\n multiTaskEditController.init();\n problemSelectorController.init();\n durationController.init();\n }\n if (addServerButton) {\n require(['auth_mumie/mumie_server_config'], function(MumieServer) {\n MumieServer.init(addServerButton, contextId);\n });\n }\n\n const submitButton = document.getElementById('id_submitbutton');\n const form = submitButton && submitButton.closest('form');\n if (form) {\n let cancelClicked = false;\n const cancelButton = document.getElementById('id_cancel');\n if (cancelButton) {\n cancelButton.addEventListener('click', function() {\n cancelClicked = true;\n });\n }\n form.addEventListener('submit', function(e) {\n if (cancelClicked) {\n cancelClicked = false;\n return;\n }\n if (taskController.hasMultiTasks()) {\n e.preventDefault();\n taskController.submitMultiTasks();\n }\n });\n }\n }\n };\n\n /**\n * Remove all child elements of a given html element\n * @param {Object} elem\n */\n function removeChildElems(elem) {\n while (elem.firstChild) {\n elem.removeChild(elem.firstChild);\n }\n }\n\n /**\n * Check, if the flag for an existing config is set\n * @returns {boolean}\n */\n function serverConfigExists() {\n return document.getElementsByName(\"mumie_missing_config\")[0].getAttribute(\"value\") === \"\";\n }\n });\n"],"names":["define","addServerButton","document","getElementById","missingConfig","getElementsByName","lmsSelectorUrl","systemLanguage","contextId","section","durationController","durationSelector","gradedElem","isUngraded","value","updateDurationElements","setAttribute","removeAttribute","style","display","duedatePropRow","querySelector","_document$querySelect","closest","show","duedatePropertyCheckbox","checked","dispatchEvent","Event","init","onchange","window","addEventListener","setDurationElements","setGradedElemValue","isGraded","getGradedElemValue","serverController","serverStructure","serverDropDown","structure","getSelectedServer","selectedServerName","options","selectedIndex","text","find","server","name","disable","disabled","elem","firstChild","removeChild","removeChildElems","problemSelectorController","problemSelectorButton","multiProblemSelectorButton","problemSelectorWindow","mumieOrg","sendResponse","response","postMessage","JSON","stringify","sendSuccess","message","success","addMessageListener","event","origin","importObj","parse","data","Array","isArray","taskController","setMultiSelection","applyPickerPayloadToForm","require","str","notification","get_strings","component","done","s","addNotification","type","fail","exception","focus","error","sendFailure","buildURL","multiSelect","gradingType","getGradingType","selectedServer","urlprefix","useSSO","shouldUseSSO","base","encodeURIComponent","langController","getSelectedLanguage","location","selection","getDelocalizedTaskLink","problemSelectorUrl","selectedServerUrl","URL","onclick","open","onclose","e","preventDefault","payload","worksheet","courseController","setCourse","path_to_coursefile","setLanguage","language","setSelection","link","setIsGraded","worksheetController","setWorksheet","courseNameElem","courseFileElem","updateCourseName","selectedCourse","getSelectedCourse","selectedLanguage","translation","_selectedCourse$name$","courses","course","coursefile","courseFile","languageElem","lang","taskSelectionInput","nameElem","taskDisplayElement","updateTaskDisplayElement","localizedLink","updateTaskUri","localizeLink","tasks","tasksField","summary","taskListItems","map","task","taskListItem","createElement","textContent","innerHTML","summaryHeading","taskList","Str","get_string","length","then","headingText","classList","add","forEach","appendChild","nameField","taskDisplayElem","nameFieldContainer","querySelectorAll","requiredIndicator","requiredLegend","updateName","updateGradeEditability","includes","split","hasMultiTasks","submitMultiTasks","courseId","_document$getElements","submitButton","form","tasksFormData","disabledFields","from","field","omit","FormData","filter","_ref","key","_ref2","join","serializeFormFields","Ajax","call","methodname","args","contextid","parseInt","tasksformdata","href","M","cfg","wwwroot","alert","multiTaskEditController","propertySelectionInputs","selectedTaskProperties","selectedTaskProp","taskSelectionInputs","selectedTasks","selectedTaskIds","sectionInputs","setPropertySelectionListeners","checkbox","push","duedateSelected","some","updateNoDuedateWarnings","sectionCheckbox","taskCheckbox","array","element","getAttribute","worksheetElement","disableDropDownMenus","errorKey","contextIdParam","prbSelectorUrl","sectionParam","isEdit","MumieServer","cancelClicked","cancelButton"],"mappings":"AAAAA,4BAAO,CAAC,SAAU,iBAAkB,iCAAkC,cAClE,iBACUC,gBAAkBC,SAASC,eAAe,wBAC1CC,cAAgBF,SAASG,kBAAkB,wBAAwB,OACrEC,eACAC,eACAC,UACAC,cAGEC,mBAAsB,iBAElBC,iBAAmBT,SAASC,eAAe,wBAC3CS,WAAaV,SAASC,eAAe,8BAKlCU,mBACuB,MAArBD,WAAWE,eAMbC,mDACYF,cAEbF,iBAAiBK,aAAa,WAAY,YAC1CL,iBAAiBG,MAAQ,aAEzBH,iBAAiBM,gBAAgB,YAKN,cAA3BN,iBAAiBG,OACjBZ,SAASC,eAAe,2BAA2Be,MAAMC,QAAU,GAEnEjB,SAASC,eAAe,sBAAsBe,MAAMC,QALpC,OAMhBjB,SAASC,eAAe,2BAA2Be,MAAMC,QANzC,OAOhBjB,SAASC,eAAe,oBAAoBe,MAAMC,QAPlC,OAQhBjB,SAASC,eAAe,yBAAyBe,MAAMC,QARvC,QASkB,YAA3BR,iBAAiBG,OACxBZ,SAASC,eAAe,oBAAoBe,MAAMC,QAAU,GAC5DjB,SAASC,eAAe,yBAAyBe,MAAMC,QAAU,GAEjEjB,SAASC,eAAe,2BAA2Be,MAAMC,QAbzC,OAchBjB,SAASC,eAAe,sBAAsBe,MAAMC,QAdpC,OAehBjB,SAASC,eAAe,2BAA2Be,MAAMC,QAfzC,QAgBkB,cAA3BR,iBAAiBG,QACxBZ,SAASC,eAAe,sBAAsBe,MAAMC,QAAU,GAC9DjB,SAASC,eAAe,2BAA2Be,MAAMC,QAAU,GAEnEjB,SAASC,eAAe,oBAAoBe,MAAMC,QApBlC,OAqBhBjB,SAASC,eAAe,yBAAyBe,MAAMC,QArBvC,OAsBhBjB,SAASC,eAAe,2BAA2Be,MAAMC,QAtBzC,cAyBdC,6CAAiBlB,SAClBmB,cAAc,+FADIC,sBAEjBC,QAAQ,SACVH,eAAgB,OACVI,KAAkC,YAA3Bb,iBAAiBG,SAC9BM,eAAeF,MAAMC,QAAUK,KAAO,GA9BtB,QA+BXA,KAAM,OACDC,wBAA0BL,eAAeC,cAAc,sCACzDI,yBAA2BA,wBAAwBC,UACnDD,wBAAwBC,SAAU,EAClCD,wBAAwBE,cAAc,IAAIC,MAAM,oBAMzD,CACHC,KAAM,WACFlB,iBAAiBmB,SAAW,WACxBf,0BAEJgB,OAAOC,iBAAiB,QAAQ,KAC5BjB,4BAEJA,0BAEJkB,oBAAqBlB,uBACrBF,WAAYA,WACZqB,mBAAoB,SAASC,UACzBvB,WAAWE,MAAQqB,SAAW,IAAM,KAExCC,mBAAoB,kBACTxB,WAAWE,QAjFF,GAsFtBuB,iBAAoB,eAClBC,sBACEC,eAAiBrC,SAASC,eAAe,mBAExC,CACH0B,KAAM,SAASW,WACXF,gBAAkBE,WAEtBC,kBAAmB,iBACTC,mBAAqBH,eAAeI,QAAQJ,eAAeK,eAAeC,YACzEP,gBAAgBQ,MAAKC,QAAUA,OAAOC,OAASN,sBAE1DO,QAAS,WACLV,eAAeW,UAAW,WAupBZC,WACfA,KAAKC,YACRD,KAAKE,YAAYF,KAAKC,YAxpBlBE,CAAiBf,kBAdH,GAmBpBgB,0BAA6B,iBACzBC,sBAAwBtD,SAASC,eAAe,uBAChDsD,2BAA6BvD,SAASC,eAAe,qCACvDuD,4BACEC,SAAWzD,SAASG,kBAAkB,aAAa,GAAGS,eAQnD8C,aAAaC,UACbH,uBAGLA,sBAAsBI,YAAYC,KAAKC,UAAUH,UAAWvD,yBAOvD2D,kBAAYC,+DAAU,GAC3BN,aAAa,CACTO,SAAS,EACTD,QAASA,mBAkBRE,qBACLrC,OAAOC,iBAAiB,WAAYqC,WAC5BA,MAAMC,SAAWhE,yBAIXiE,UAAYR,KAAKS,MAAMH,MAAMI,MAC/BC,MAAMC,QAAQJ,WACdK,eAAeC,kBAAkBN,YAEjCO,yBAAyBP,WAerCQ,QAAQ,CAAC,WAAY,sBAAsB,SAASC,IAAKC,cACrDD,IAAIE,YAAY,CAAC,KACN,+BACPC,UAAW,eACXC,MAAK,SAASC,GACdJ,aAAaK,gBAAgB,CACzBpB,QAASmB,EAAE,GACXE,KAAM,YAEXC,KAAKP,aAAaQ,eArBjBxB,cACAlC,OAAO2D,QACT,MAAOC,mBAxBb/B,aAAa,CACTO,SAAS,EACTD,+DAHuB,KA0BnB0B,CAAYD,MAAMzB,aAEvB,YAyBE2B,eAASC,0EACRC,YAAcD,YAAc,MAAQlB,eAAeoB,iBACnDC,eAAiB5D,iBAAiBI,oBAAoByD,UACtDC,OAASC,aAAa9F,eAAgB2F,gBAEtCI,KAAOF,OACP,wCACOxC,SACT,cAAgB2C,mBAAmBL,gBACnC,gBAAkBM,eAAeC,sBACjC,WAAaF,mBAAmBvE,OAAO0E,SAASnC,QAChD,gBAAkByB,YAClB,cAAgBvF,UACdF,eAAAA,6BACOqD,SACT,cAAgB2C,mBAAmBL,gBACnC,gBAAkBM,eAAeC,sBACjC,WAAaF,mBAAmBvE,OAAO0E,SAASnC,QAChD,WAAa/D,eACb,gBAAkBwF,YANhBzF,sCAUFwF,mBACOO,MAAQF,OAAS,oBAAsB,2BAE5CO,UAAY9B,eAAe+B,gCAC1BD,UAAYL,KAAO,cAAgBK,UAAYL,cAYjDD,aAAaQ,mBAAoBC,0BAC/B,IAAIC,IAAIF,oBAAoBtC,SAAW,IAAIwC,IAAID,mBAAmBvC,aAGtE,CACHzC,KAAM,WACF2B,sBAAsBuD,QAAU,WAC5BrD,sBAAwB3B,OAAOiF,KAAKnB,WAAY,WAGpD9D,OAAOkF,QAAU,WACbhD,eAGJlC,OAAOC,iBAAiB,gBAAgB,WACpCiC,iBACD,GAEHG,qBAEAX,2BAA2BsD,QAAU,SAASG,GAC1CA,EAAEC,iBACFzD,sBAAwB3B,OAAOiF,KAAKnB,UAAS,GAAO,SAAU,oCAGtE5C,QAAS,WACLO,sBAAsBN,UAAW,IAxJV,YAsK1B4B,yBAAyBsC,sCACxBjF,UAAgC,IAArBiF,QAAQjF,SACnBkF,qCAAYD,QAAQC,2DAAa,KACvCC,iBAAiBC,UAAUH,QAAQI,oBACnCjB,eAAekB,YAAYL,QAAQM,UACnC9C,eAAe+C,aAAaP,QAAQQ,KAAMR,QAAQM,SAAUN,QAAQpE,MACpE4B,eAAeiD,YAAY1F,UAC3B2F,oBAAoBC,aAAaV,iBAG/BC,iBAAoB,iBAChBU,eAAiB9H,SAASC,eAAe,mBACzC8H,eAAiB/H,SAASG,kBAAkB,oBAAoB,YAgB7D6H,mDACCC,eAAiBb,iBAAiBc,oBAClCC,iBAAmB9B,eAAeC,sBACnC2B,gBAAmBE,mBAGxBL,eAAelH,oCAAQqH,eAAenF,KACjCF,MAAKwF,aAAeA,YAAYZ,WAAaW,2DAD3BE,sBAC8CzH,aAGlE,CACHe,KAAM,WACFqG,oBAEJE,kBAAmB,kBACC/F,iBAAiBI,oBAAoB+F,QACtC1F,MAAK2F,QAAUA,OAAOC,aAAeT,eAAenH,SAEvEyG,UAAW,SAASoB,gBA1BMD,WAAAA,WA2BDC,WA1BzBV,eAAenH,MAAQ4H,WACvBR,qBAZkB,GA0CpB3B,eAAkB,iBACdqC,aAAe1I,SAASC,eAAe,qBACtC,CACHqG,oBAAqB,kBACVoC,aAAa9H,OAExB2G,YAAa,SAASoB,MAClBD,aAAa9H,MAAQ+H,OAPT,GAYlBjE,eAAkB,iBACdkE,mBAAqB5I,SAASG,kBAAkB,WAAW,GAC3D0I,SAAW7I,SAASC,eAAe,WACnC6I,mBAAqB9I,SAASC,eAAe,oCAc1C8I,yBAAyBC,eAC9BF,mBAAmBlI,MAAQoI,uBAQtBC,cAAcvB,KAAMF,gBACnBwB,uBAWYtB,KAAMF,iBACjBE,KAnCuB,SAmCYF,SAZpB0B,CAAaxB,KAAMF,UACzCoB,mBAAmBhI,MAAQoI,cAC3BD,yBAAyBC,wBAwCpBrE,kBAAkBwE,aACjBC,WAAapJ,SAASG,kBAAkB,qBAAqB,GAC7DkJ,QAAUrJ,SAASC,eAAe,gCACpCmJ,aACAA,WAAWxI,MAAQiD,KAAKC,UAAUqF,QAElCE,QAAS,OACHC,cAAgBH,MAAMI,KAAIC,aACtBC,aAAezJ,SAAS0J,cAAc,aAC5CD,aAAaE,YAAcH,KAAK1G,KACzB2G,gBAEXJ,QAAQO,UAAY,SACdC,eAAiB7J,SAAS0J,cAAc,OACxCI,SAAW9J,SAAS0J,cAAc,MACxC7E,QAAQ,CAAC,aAAa,SAASkF,KAC3BA,IAAIC,WAAW,6BAA8B,YAAab,MAAMc,QAC3DC,MAAK,SAASC,aACXN,eAAeF,YAAcQ,kBAGzCL,SAASM,UAAUC,IAAI,0BACvBf,cAAcgB,SAAQb,cAAgBK,SAASS,YAAYd,gBAC3DJ,QAAQkB,YAAYV,gBACpBR,QAAQkB,YAAYT,UACpBT,QAAQrI,MAAMC,QAAU,cAEtBuJ,UAAYxK,SAASC,eAAe,WAC1CuK,UAAUxH,UAAW,EACrBwH,UAAU5J,MAAQ,GAClB4J,UAAUzJ,gBAAgB,kBACpB0J,gBAAkBzK,SAASC,eAAe,2BAC5CwK,kBAAmBA,gBAAgB7J,MAAQ,UACzCkH,eAAiB9H,SAASC,eAAe,mBAC3C6H,iBAAkBA,eAAelH,MAAQ,UACvC8J,mBAAqB1K,SAASC,eAAe,iBAC/CyK,oBACAA,mBAAmBC,iBAAiB,gDAC/BL,SAAQM,oBAAuBA,kBAAkB5J,MAAMC,QAAU,gBAEpE4J,eAAiB7K,SAASmB,cAAc,0BAC1C0J,iBACAA,eAAe7J,MAAMC,QAAU,cAwEhC,CACHU,KAAM,WACFoH,yBAAyBH,mBAAmBhI,QAEhD6G,aAAc,SAASC,KAAMF,SAAU1E,YAC7BsG,WAAapJ,SAASG,kBAAkB,qBAAqB,GAC/DiJ,aACAA,WAAWxI,MAAQ,UAEjByI,QAAUrJ,SAASC,eAAe,6BACpCoJ,UACAA,QAAQrI,MAAMC,QAAU,OACxBoI,QAAQO,UAAY,UAElBY,UAAYxK,SAASC,eAAe,WACtCuK,YACAA,UAAUxH,UAAW,EACrBwH,UAAU1J,aAAa,WAAY,mBAEjC4J,mBAAqB1K,SAASC,eAAe,iBAC/CyK,oBACAA,mBAAmBC,iBAAiB,gDAC/BL,SAAQM,oBAAuBA,kBAAkB5J,MAAMC,QAAU,YAEpE4J,eAAiB7K,SAASmB,cAAc,0BAC1C0J,iBACAA,eAAe7J,MAAMC,QAAU,IAEnCgI,cAAcvB,KAAMF,mBAzMR1E,MAChB+F,SAASjI,MAAQkC,KAyMbgI,CAAWhI,OAEf6E,YAAa,SAAS1F,UAClBzB,mBAAmBwB,mBAAmBC,2BA7JpCe,SAAWxC,mBAAmBG,aACpCX,SAASC,eAAe,aAAa+C,SAAWA,SAChDhD,SAASC,eAAe,gBAAgB+C,SAAWA,SACnDhD,SAASC,eAAe,eAAe+C,SAAWA,SAClDxC,mBAAmBuB,sBA0JfgJ,IAEJjF,eAAgB,iBACN7D,SAAWzB,mBAAmB0B,2BACnB,MAAbD,SACO,SACa,MAAbA,SACA,WAEJ,OAEXwE,uBAAwB,kBApLJiB,KAqLMkB,mBAAmBhI,OApLpCoK,SA5CqB,UA6CnBtD,KAAKuD,MA7Cc,UA6CmB,GAE1CvD,SAJaA,MAuLpB/C,kBAAmBA,kBACnBuG,cAAe,iBACL9B,WAAapJ,SAASG,kBAAkB,qBAAqB,UAC5DiJ,cAAgBA,WAAWxI,OAEtCuK,4DAnFM/B,WAAapJ,SAASG,kBAAkB,qBAAqB,GAC7DiL,uCAAWpL,SAASG,kBAAkB,UAAU,2CAArCkL,sBAAyCzK,MACpD0K,aAAetL,SAASC,eAAe,mBACvCsL,KAAOD,cAAgBA,aAAajK,QAAQ,QAC5C8H,MAAQtF,KAAKS,MAAM8E,WAAWxI,OAE9B4K,cAAgBrC,MAAMI,KAAIC,OAC5B5E,yBAAyB4E,eAjCJ+B,YACnBE,eAAiBjH,MAAMkH,KAAKH,KAAKZ,iBAAiB,cACxDc,eAAenB,SAAQqB,QAAWA,MAAM3I,UAAW,eAEzC4I,KAAO,CAAC,UAAW,kCAClBpH,MAAMkH,KAAK,IAAIG,SAASN,OAC1BO,QAAOC,WAAEC,iBAAUJ,KAAKZ,SAASgB,QACjCzC,KAAI0C,YAAED,IAAKpL,oBAAWwF,mBAAmB4F,KAAO,IAAM5F,mBAAmBxF,UACzEsL,KAAK,aAEVT,eAAenB,SAAQqB,QAAWA,MAAM3I,UAAW,MAwB5CmJ,CAAoBZ,SAK/B5G,kBAAkBwE,OAElBtE,QAAQ,CAAC,YAAa,sBAAsB,SAASuH,KAAMrH,cACvDqH,KAAKC,KAAK,CAAC,CACPC,WAAY,wCACZC,KAAM,CACFC,UAAWC,SAASnM,WACpBC,QAASA,QACTmM,cAAelB,kBAEnB,GAAGtG,MAAK,WACRrD,OAAO0E,SAASoG,KAAOC,EAAEC,IAAIC,QAAU,uBAAyB1B,YACjE9F,MAAK,SAASG,OACbV,aAAagI,MAAM,GAAItH,MAAMzB,iBAlLrB,GAgPlBgJ,wBAA2B,iBACvBC,wBAA0BjN,SAASG,kBAAkB,6BACrD+M,uBAAyBlN,SAASG,kBAAkB,kCAAkC,OACxFgN,iBAAmB,SACjBC,oBAAsBpN,SAASG,kBAAkB,yBACjDkN,cAAgBrN,SAASG,kBAAkB,wBAAwB,OACrEmN,gBAAkB,SAChBC,cAAgBvN,SAASG,kBAAkB,qCA4CxCqN,gCACLP,wBAAwB3C,SAAQ,SAASmD,UACrCA,SAAS7L,SAAW,WACX6L,SAASjM,QAGV2L,iBAAiBO,KAAKD,SAAS7M,OAF/BuM,iBAAmBA,iBAAiBrB,QAAO7I,MAAQA,OAASwK,SAAS7M,QAIzEsM,uBAAuBtM,MAAQiD,KAAKC,UAAUqJ,mCAlBhDQ,gBAAkBnJ,MAAMkH,KAAKuB,yBAC9BW,MAAKH,UAA+B,YAAnBA,SAAS7M,OAAuB6M,SAASjM,UAC/DxB,SAAS2K,iBAAiB,kDAAkDL,SAAQ,SAASrH,MACzFA,KAAKjC,MAAMC,QAAU0M,gBAAkB,GAAK,UAgBxCE,aA+BL,CACHlM,KAAM,WAnENyL,oBAAoB9C,SAAQ,SAASmD,UACjCA,SAAS7L,SAAW,WACX6L,SAASjM,QAGV8L,gBAAgBI,KAAKD,SAAS7M,OAF9B0M,gBAAkBA,gBAAgBxB,QAAO7I,MAAQA,OAASwK,SAAS7M,QAIvEyM,cAAczM,MAAQiD,KAAKC,UAAUwJ,qBA8DzCE,gCAzBJD,cAAcjD,SAAQ,SAASwD,iBAC3BA,gBAAgBlM,SAAW,WAClBkM,gBAAgBtM,QAQjB4L,oBAAoB9C,SAAQ,SAASyD,kBAhE5BC,MAAOC,QAiERF,aAAaG,aAAa,aAAeJ,gBAAgBlN,QACzDmN,aAAavM,SAAU,EAlEtBwM,MAmEeV,gBAnERW,QAmEyBF,aAAanN,MAlE7DoN,MAAMhD,SAASiD,UAChBD,MAAMN,KAAKO,aAuDHb,oBAAoB9C,SAAQ,SAASyD,cAC7BA,aAAaG,aAAa,aAAeJ,gBAAgBlN,QACzDmN,aAAavM,SAAU,EACvB8L,gBAAkBA,gBAAgBxB,QAAO7I,MAAQ8K,aAAanN,QAAUqC,WAWpFoK,cAAczM,MAAQiD,KAAKC,UAAUwJ,uBAtFpB,GAoG3B1F,oBAAuB,iBACnBuG,iBAAmBnO,SAASC,eAAe,4BAC1C,CACH4H,aAAc,SAASV,WACfA,UACAgH,iBAAiBrN,aAAa,QAAS+C,KAAKC,UAAUqD,YAEtDgH,iBAAiBpN,gBAAgB,WAPpB,YAiBpBqN,qBAAqBC,UAC1BxJ,QAAQ,CAAC,WAAY,sBAAsB,SAASC,IAAKC,cACrDD,IAAIE,YAAY,CAAC,KACNqJ,SACPpJ,UAAW,eACXC,MAAK,SAASC,GACdJ,aAAaK,gBAAgB,CACzBpB,QAASmB,EAAE,GAAK,MAAQjF,cAAcgO,aAAa,SAAW,OAC9D7I,KAAM,eAEXC,KAAKP,aAAaQ,cAEzBpD,iBAAiBY,UACjBM,0BAA0BN,gBAGvB,CACHpB,KAAM,SAAS2M,eAAgBC,eAAgB5F,KAAM6F,cACjDpO,eAAiBmO,eACjBlO,eAAiBsI,KACjBrI,UAAYgO,eACZ/N,QAAUiO,mBACJC,OAASzO,SAASC,eAAe,WAAWiO,aAAa,SACzD9L,gBAAkByB,KAAKS,MAAMtE,SAASG,kBAAkB,0BAA0B,GAAGS,OACvF6N,QAyD+E,KAAhFzO,SAASG,kBAAkB,wBAAwB,GAAG+N,aAAa,SAxDlEE,qBAAqB,6BACbhM,gBAAgB6H,QAGxB9H,iBAAiBR,KAAKS,iBACtBgF,iBAAiBzF,OACjB+C,eAAe/C,OACfqL,wBAAwBrL,OACxB0B,0BAA0B1B,OAC1BnB,mBAAmBmB,QAPnByM,qBAAqB,6BASrBrO,iBACA8E,QAAQ,CAAC,mCAAmC,SAAS6J,aACjDA,YAAY/M,KAAK5B,gBAAiBO,oBAIpCgL,aAAetL,SAASC,eAAe,mBACvCsL,KAAOD,cAAgBA,aAAajK,QAAQ,WAC9CkK,KAAM,KACFoD,eAAgB,QACdC,aAAe5O,SAASC,eAAe,aACzC2O,cACAA,aAAa9M,iBAAiB,SAAS,WACnC6M,eAAgB,KAGxBpD,KAAKzJ,iBAAiB,UAAU,SAASkF,GACjC2H,cACAA,eAAgB,EAGhBjK,eAAewG,kBACflE,EAAEC,iBACFvC,eAAeyG"} \ No newline at end of file diff --git a/amd/build/view.min.js b/amd/build/view.min.js index ef3bc370..7465ea24 100644 --- a/amd/build/view.min.js +++ b/amd/build/view.min.js @@ -1,3 +1,3 @@ -define("mod_mumie/view",["jquery","core/templates","core/modal_factory","mod_mumie/duedate_form"],(function($){return{init:function(contextid){!function(contextid){const addBtns=$(".mumie_duedate_add_btn");addBtns.each((function(i){const btn=addBtns[i],data=JSON.parse(btn.children[1].textContent),formdata="&_qf__duedate_form=1&userid="+data.userid+"&mumie="+data.mumie;require(["mod_mumie/duedate_form"],(function(MumieDueDate){MumieDueDate.init(btn,contextid,formdata)}))}))}(contextid),function(contextid){const editBtns=$(".mumie_duedate_edit_btn");editBtns.each((function(i){const btn=editBtns[i],data=JSON.parse(btn.children[1].textContent),formdata="id="+data.id+"&_qf__duedate_form=1&userid="+data.userid+"&mumie="+data.mumie+"&duedate="+data.duedate;require(["mod_mumie/duedate_form"],(function(MumieDueDate){MumieDueDate.init(btn,contextid,formdata)}))}))}(contextid)}}})); +define("mod_mumie/view",["jquery","core/templates","mod_mumie/duedate_form"],(function($){return{init:function(contextid){!function(contextid){const addBtns=$(".mumie_duedate_add_btn");addBtns.each((function(i){const btn=addBtns[i],data=JSON.parse(btn.children[1].textContent),formdata="&_qf__duedate_form=1&userid="+data.userid+"&mumie="+data.mumie;require(["mod_mumie/duedate_form"],(function(MumieDueDate){MumieDueDate.init(btn,contextid,formdata)}))}))}(contextid),function(contextid){const editBtns=$(".mumie_duedate_edit_btn");editBtns.each((function(i){const btn=editBtns[i],data=JSON.parse(btn.children[1].textContent),formdata="id="+data.id+"&_qf__duedate_form=1&userid="+data.userid+"&mumie="+data.mumie+"&duedate="+data.duedate;require(["mod_mumie/duedate_form"],(function(MumieDueDate){MumieDueDate.init(btn,contextid,formdata)}))}))}(contextid)}}})); //# sourceMappingURL=view.min.js.map \ No newline at end of file diff --git a/amd/build/view.min.js.map b/amd/build/view.min.js.map index 5feb4aa6..527937d4 100644 --- a/amd/build/view.min.js.map +++ b/amd/build/view.min.js.map @@ -1 +1 @@ -{"version":3,"file":"view.min.js","sources":["../src/view.js"],"sourcesContent":["define(['jquery', 'core/templates', 'core/modal_factory', 'mod_mumie/duedate_form'],\n function($) {\n return {\n init: function(contextid) {\n setAddDuedateListeners(contextid);\n setEditDuedateListeners(contextid);\n }\n };\n\n /**\n * Set click listener to add a server button\n * @param {number} contextid\n */\n function setAddDuedateListeners(contextid) {\n const addBtns = $(\".mumie_duedate_add_btn\");\n\n addBtns.each(function(i) {\n const btn = addBtns[i];\n const data = JSON.parse(btn.children[1].textContent);\n const formdata = \"&_qf__duedate_form=1&userid=\" + data.userid + \"&mumie=\" + data.mumie;\n require(['mod_mumie/duedate_form'], function(MumieDueDate) {\n MumieDueDate.init(btn, contextid, formdata);\n });\n });\n }\n\n /**\n * Set click listeners for the edit buttons\n * @param {number} contextid\n */\n function setEditDuedateListeners(contextid) {\n const editBtns = $(\".mumie_duedate_edit_btn\");\n\n editBtns.each(function(i) {\n const btn = editBtns[i];\n const data = JSON.parse(btn.children[1].textContent);\n const formdata = \"id=\" + data.id +\n \"&_qf__duedate_form=1&userid=\" + data.userid + \"&mumie=\" + data.mumie + \"&duedate=\" + data.duedate;\n require(['mod_mumie/duedate_form'], function(MumieDueDate) {\n MumieDueDate.init(btn, contextid, formdata);\n });\n });\n }\n });"],"names":["define","$","init","contextid","addBtns","each","i","btn","data","JSON","parse","children","textContent","formdata","userid","mumie","require","MumieDueDate","setAddDuedateListeners","editBtns","id","duedate","setEditDuedateListeners"],"mappings":"AAAAA,wBAAO,CAAC,SAAU,iBAAkB,qBAAsB,2BACtD,SAASC,SACE,CACHC,KAAM,SAASC,qBAUaA,iBACtBC,QAAUH,EAAE,0BAElBG,QAAQC,MAAK,SAASC,SACZC,IAAMH,QAAQE,GACdE,KAAOC,KAAKC,MAAMH,IAAII,SAAS,GAAGC,aAClCC,SAAW,+BAAiCL,KAAKM,OAAS,UAAYN,KAAKO,MACjFC,QAAQ,CAAC,2BAA2B,SAASC,cACzCA,aAAaf,KAAKK,IAAKJ,UAAWU,gBAjBtCK,CAAuBf,oBA0BEA,iBACvBgB,SAAWlB,EAAE,2BAEnBkB,SAASd,MAAK,SAASC,SACbC,IAAMY,SAASb,GACfE,KAAOC,KAAKC,MAAMH,IAAII,SAAS,GAAGC,aAClCC,SAAW,MAAQL,KAAKY,GAC1B,+BAAiCZ,KAAKM,OAAS,UAAYN,KAAKO,MAAQ,YAAcP,KAAKa,QAC/FL,QAAQ,CAAC,2BAA2B,SAASC,cACzCA,aAAaf,KAAKK,IAAKJ,UAAWU,gBAlCtCS,CAAwBnB"} \ No newline at end of file +{"version":3,"file":"view.min.js","sources":["../src/view.js"],"sourcesContent":["define(['jquery', 'core/templates', 'mod_mumie/duedate_form'],\n function($) {\n return {\n init: function(contextid) {\n setAddDuedateListeners(contextid);\n setEditDuedateListeners(contextid);\n }\n };\n\n /**\n * Set click listener to add a server button\n * @param {number} contextid\n */\n function setAddDuedateListeners(contextid) {\n const addBtns = $(\".mumie_duedate_add_btn\");\n\n addBtns.each(function(i) {\n const btn = addBtns[i];\n const data = JSON.parse(btn.children[1].textContent);\n const formdata = \"&_qf__duedate_form=1&userid=\" + data.userid + \"&mumie=\" + data.mumie;\n require(['mod_mumie/duedate_form'], function(MumieDueDate) {\n MumieDueDate.init(btn, contextid, formdata);\n });\n });\n }\n\n /**\n * Set click listeners for the edit buttons\n * @param {number} contextid\n */\n function setEditDuedateListeners(contextid) {\n const editBtns = $(\".mumie_duedate_edit_btn\");\n\n editBtns.each(function(i) {\n const btn = editBtns[i];\n const data = JSON.parse(btn.children[1].textContent);\n const formdata = \"id=\" + data.id +\n \"&_qf__duedate_form=1&userid=\" + data.userid + \"&mumie=\" + data.mumie + \"&duedate=\" + data.duedate;\n require(['mod_mumie/duedate_form'], function(MumieDueDate) {\n MumieDueDate.init(btn, contextid, formdata);\n });\n });\n }\n });"],"names":["define","$","init","contextid","addBtns","each","i","btn","data","JSON","parse","children","textContent","formdata","userid","mumie","require","MumieDueDate","setAddDuedateListeners","editBtns","id","duedate","setEditDuedateListeners"],"mappings":"AAAAA,wBAAO,CAAC,SAAU,iBAAkB,2BAChC,SAASC,SACE,CACHC,KAAM,SAASC,qBAUaA,iBACtBC,QAAUH,EAAE,0BAElBG,QAAQC,MAAK,SAASC,SACZC,IAAMH,QAAQE,GACdE,KAAOC,KAAKC,MAAMH,IAAII,SAAS,GAAGC,aAClCC,SAAW,+BAAiCL,KAAKM,OAAS,UAAYN,KAAKO,MACjFC,QAAQ,CAAC,2BAA2B,SAASC,cACzCA,aAAaf,KAAKK,IAAKJ,UAAWU,gBAjBtCK,CAAuBf,oBA0BEA,iBACvBgB,SAAWlB,EAAE,2BAEnBkB,SAASd,MAAK,SAASC,SACbC,IAAMY,SAASb,GACfE,KAAOC,KAAKC,MAAMH,IAAII,SAAS,GAAGC,aAClCC,SAAW,MAAQL,KAAKY,GAC1B,+BAAiCZ,KAAKM,OAAS,UAAYN,KAAKO,MAAQ,YAAcP,KAAKa,QAC/FL,QAAQ,CAAC,2BAA2B,SAASC,cACzCA,aAAaf,KAAKK,IAAKJ,UAAWU,gBAlCtCS,CAAwBnB"} \ No newline at end of file diff --git a/amd/src/duedate_form.js b/amd/src/duedate_form.js index 80316221..09e4ff49 100644 --- a/amd/src/duedate_form.js +++ b/amd/src/duedate_form.js @@ -1,5 +1,12 @@ -define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax', 'core/yui'], - function($, Str, ModalFactory, ModalEvents, Fragment, Ajax, Y) { +define([ + 'jquery', + 'core/str', + 'core/modal_save_cancel', + 'core/modal_events', + 'core/fragment', + 'core/ajax', + 'core_form/changechecker', +], function($, Str, ModalSaveCancel, ModalEvents, Fragment, Ajax, FormChangeChecker) { var MumieDueDate = function(selector, contextid, formdata) { this.contextid = contextid; @@ -11,34 +18,27 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f MumieDueDate.prototype.contextid = -1; MumieDueDate.prototype.init = function(selector, formdata) { - const triggers = $(selector); - return Str.get_string('mumie_duedate_form', 'mod_mumie').then(function(title) { - // Create the modal. - return ModalFactory.create({ - type: ModalFactory.types.SAVE_CANCEL, - title: title, - body: this.getBody(formdata) - }, triggers); - }.bind(this)).then(function(modal) { - // Keep a reference to the modal. - this.modal = modal; - - // Forms are big, we want a big modal. - this.modal.setLarge(); - - // We want to reset the form every time it is opened. - this.modal.getRoot().on(ModalEvents.hidden, function() { - this.modal.setBody(this.getBody(formdata)); + $(selector).on('click', function(e) { + e.preventDefault(); + return Str.get_string('mumie_duedate_form', 'mod_mumie').then(function(title) { + return ModalSaveCancel.create({ + title: title, + body: this.getBody(formdata), + large: true, + removeOnClose: true, + }); + }.bind(this)).then(function(modal) { + this.modal = modal; + + // We catch the modal save event, and use it to submit the form inside the modal. + // Triggering a form submission will give JS validation scripts a chance to check for errors. + modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this)); + // We also catch the form submit event and use it to submit the form with ajax. + modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this)); + + modal.show(); + return modal; }.bind(this)); - - // We catch the modal save event, and use it to submit the form inside the modal. - // Triggering a form submission will give JS validation scripts a chance to check for errors. - this.modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this)); - // We also catch the form submit event and use it to submit the form with ajax. - this.modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this)); - - return this.modal; - }.bind(this)); }; @@ -65,12 +65,7 @@ define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/f */ MumieDueDate.prototype.handleFormSubmissionResponse = function() { this.modal.hide(); - // We could trigger an event instead. - // Yuk. - Y.use('moodle-core-formchangechecker', function() { - M.core_formchangechecker.reset_form_dirty_state(); - }); - + FormChangeChecker.resetAllFormDirtyStates(); document.location.reload(); }; diff --git a/amd/src/mod_form.js b/amd/src/mod_form.js index 041124cc..0839a900 100644 --- a/amd/src/mod_form.js +++ b/amd/src/mod_form.js @@ -1,10 +1,11 @@ -define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_server_config', 'core/ajax'], +define(['jquery', 'core/templates', 'auth_mumie/mumie_server_config', 'core/ajax'], function() { const addServerButton = document.getElementById("id_add_server_button"); const missingConfig = document.getElementsByName("mumie_missing_config")[0]; let lmsSelectorUrl; let systemLanguage; let contextId; + let section; const durationController = (function() { @@ -55,6 +56,21 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv document.getElementById('fitem_id_duedate_info').style.display = displayNone; document.getElementById('fitem_id_unlimited_info').style.display = displayNone; } + + const duedatePropRow = document + .querySelector('[name="mumie_multi_edit_property"][value="duedate"]') + ?.closest('tr'); + if (duedatePropRow) { + const show = durationSelector.value === 'duedate'; + duedatePropRow.style.display = show ? '' : displayNone; + if (!show) { + const duedatePropertyCheckbox = duedatePropRow.querySelector('[name="mumie_multi_edit_property"]'); + if (duedatePropertyCheckbox && duedatePropertyCheckbox.checked) { + duedatePropertyCheckbox.checked = false; + duedatePropertyCheckbox.dispatchEvent(new Event('change')); + } + } + } } return { @@ -65,6 +81,7 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv window.addEventListener("load", () => { updateDurationElements(); }); + updateDurationElements(); }, setDurationElements: updateDurationElements, isUngraded: isUngraded, @@ -75,7 +92,7 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv return gradedElem.value; } }; - }); + })(); const serverController = (function() { let serverStructure; @@ -145,18 +162,16 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv if (event.origin !== lmsSelectorUrl) { return; } - const importObj = JSON.parse(event.data); - const isGraded = importObj.isGraded !== false; - const worksheet = importObj.worksheet ?? null; try { - courseController.setCourse(importObj.path_to_coursefile); - langController.setLanguage(importObj.language); - taskController.setSelection(importObj.link, importObj.language, importObj.name); - taskController.setIsGraded(isGraded); - worksheetController.setWorksheet(worksheet); + const importObj = JSON.parse(event.data); + if (Array.isArray(importObj)) { + taskController.setMultiSelection(importObj); + } else { + applyPickerPayloadToForm(importObj); + displayProblemSelectedMessage(); + } sendSuccess(); window.focus(); - displayProblemSelectedMessage(); } catch (error) { sendFailure(error.message); } @@ -182,40 +197,37 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv /** * Builds the URL to the Problem Selector + * @param {boolean} multiSelect Whether to build a multi-select URL * @returns {string} URL to the Problem Selector */ - function buildURL() { - const gradingType = taskController.getGradingType(); - const selection = taskController.getDelocalizedTaskLink(); + function buildURL(multiSelect = false) { + const gradingType = multiSelect ? 'all' : taskController.getGradingType(); const selectedServer = serverController.getSelectedServer().urlprefix; const useSSO = shouldUseSSO(lmsSelectorUrl, selectedServer); - if (useSSO) { - return '/auth/mumie/problem_selector.php?' + - 'org=' + - mumieOrg + - '&serverurl=' + - encodeURIComponent(selectedServer) + - '&problemlang=' + - langController.getSelectedLanguage() + - '&origin=' + encodeURIComponent(window.location.origin) + - '&gradingtype=' + gradingType + - '&contextid=' + contextId + - (selection ? '&selection=' + selection : ''); - } - return lmsSelectorUrl + - '/lms-problem-selector?' + - 'org=' + - mumieOrg + - '&serverUrl=' + - encodeURIComponent(selectedServer) + - '&problemLang=' + - langController.getSelectedLanguage() + + + const base = useSSO + ? '/auth/mumie/problem_selector.php?' + + 'org=' + mumieOrg + + '&serverurl=' + encodeURIComponent(selectedServer) + + '&problemlang=' + langController.getSelectedLanguage() + + '&origin=' + encodeURIComponent(window.location.origin) + + '&gradingtype=' + gradingType + + '&contextid=' + contextId + : lmsSelectorUrl + '/lms-problem-selector?' + + 'org=' + mumieOrg + + '&serverUrl=' + encodeURIComponent(selectedServer) + + '&problemLang=' + langController.getSelectedLanguage() + '&origin=' + encodeURIComponent(window.location.origin) + '&uiLang=' + systemLanguage + '&gradingType=' + gradingType + '&multiCourse=true' + - '&worksheet=true' + - (selection ? '&selection=' + selection : ''); + '&worksheet=true'; + + if (multiSelect) { + return base + (useSSO ? '&multiselect=true' : '&multiSelect=true'); + } + const selection = taskController.getDelocalizedTaskLink(); + return selection ? base + '&selection=' + selection : base; } /** @@ -249,23 +261,34 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv multiProblemSelectorButton.onclick = function(e) { e.preventDefault(); - problemSelectorWindow = window.open( - lmsSelectorUrl + - '/lms-problem-selector?' + - "serverUrl=" + - encodeURIComponent(serverController.getSelectedServer().urlprefix) + - '&gradingType=all', - "_blank", - 'toolbar=0,location=0,menubar=0' - ); + problemSelectorWindow = window.open(buildURL(true), '_blank', 'toolbar=0,location=0,menubar=0'); }; }, disable: function() { problemSelectorButton.disabled = true; - } + }, }; })(); + /** + * Apply a picker payload to the form's per-task fields. + * + * Single source of truth for picker-payload → form-field mapping. + * Used by both the single-task postMessage handler and the multi-task + * submit loop, so new picker fields only need to be wired here. + * + * @param {Object} payload picker payload for one task + */ + function applyPickerPayloadToForm(payload) { + const isGraded = payload.isGraded !== false; + const worksheet = payload.worksheet ?? null; + courseController.setCourse(payload.path_to_coursefile); + langController.setLanguage(payload.language); + taskController.setSelection(payload.link, payload.language, payload.name); + taskController.setIsGraded(isGraded); + worksheetController.setWorksheet(worksheet); + } + const courseController = (function() { const courseNameElem = document.getElementById("id_mumie_course"); const courseFileElem = document.getElementsByName("mumie_coursefile")[0]; @@ -378,11 +401,129 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv * Form inputs related to grades should be disabled if the MUMIE Task is not graded. */ function updateGradeEditability() { - const disabled = durationController().isUngraded(); + const disabled = durationController.isUngraded(); document.getElementById('id_points').disabled = disabled; document.getElementById('id_gradepass').disabled = disabled; document.getElementById('id_gradecat').disabled = disabled; - durationController().setDurationElements(); + durationController.setDurationElements(); + } + + /** + * Store selected tasks and update UI for multi-task creation. + * @param {Array} tasks array of task objects from the selector + */ + function setMultiSelection(tasks) { + const tasksField = document.getElementsByName('mumie_multi_tasks')[0]; + const summary = document.getElementById('mumie_multi_tasks_summary'); + if (tasksField) { + tasksField.value = JSON.stringify(tasks); + } + if (summary) { + const taskListItems = tasks.map(task => { + const taskListItem = document.createElement('li'); + taskListItem.textContent = task.name; + return taskListItem; + }); + summary.innerHTML = ''; + const summaryHeading = document.createElement('div'); + const taskList = document.createElement('ul'); + require(['core/str'], function(Str) { + Str.get_string('mumie_multi_tasks_selected', 'mod_mumie', tasks.length) + .then(function(headingText) { + summaryHeading.textContent = headingText; + }); + }); + taskList.classList.add('mumie_multi_tasks_list'); + taskListItems.forEach(taskListItem => taskList.appendChild(taskListItem)); + summary.appendChild(summaryHeading); + summary.appendChild(taskList); + summary.style.display = 'block'; + } + const nameField = document.getElementById('id_name'); + nameField.disabled = true; + nameField.value = ''; + nameField.removeAttribute('required'); + const taskDisplayElem = document.getElementById('id_task_display_element'); + if (taskDisplayElem) { taskDisplayElem.value = ''; } + const courseNameElem = document.getElementById('id_mumie_course'); + if (courseNameElem) { courseNameElem.value = ''; } + const nameFieldContainer = document.getElementById('fitem_id_name'); + if (nameFieldContainer) { + nameFieldContainer.querySelectorAll('.req, .text-danger, [title="Required field"]') + .forEach(requiredIndicator => { requiredIndicator.style.display = 'none'; }); + } + const requiredLegend = document.querySelector('.fdescription.required'); + if (requiredLegend) { + requiredLegend.style.display = 'none'; + } + } + + /** + * Serialize a form into a URL-encoded POST string, omitting form mechanics + * the webservice does not need (sesskey, qf form marker). + * + * Disabled form controls are temporarily enabled so FormData includes them: + * the picker writes values to display-only inputs (e.g. mumie_course) that + * the server-side validator still expects to receive. + * + * @param {HTMLFormElement} form + * @returns {string} + */ + function serializeFormFields(form) { + const disabledFields = Array.from(form.querySelectorAll(':disabled')); + disabledFields.forEach(field => { field.disabled = false; }); + try { + const omit = ['sesskey', '_qf__mod_mumie_mod_form']; + return Array.from(new FormData(form)) + .filter(([key]) => !omit.includes(key)) + .map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value)) + .join('&'); + } finally { + disabledFields.forEach(field => { field.disabled = true; }); + } + } + + /** + * Submit multi-tasks via AJAX, then redirect to course. + * + * Builds one full form payload per task by applying each picker payload + * to the live form and serializing it. The server treats each entry as + * an independent single-task submission, so picker-payload → form-field + * mapping lives in applyPickerPayloadToForm and is shared with the + * single-task path. Validation runs server-side via the shared form + * pipeline; errors surface as a modal alert with the validator's + * localized message. + */ + function submitMultiTasks() { + const tasksField = document.getElementsByName('mumie_multi_tasks')[0]; + const courseId = document.getElementsByName('course')[0]?.value; + const submitButton = document.getElementById('id_submitbutton'); + const form = submitButton && submitButton.closest('form'); + const tasks = JSON.parse(tasksField.value); + + const tasksFormData = tasks.map(task => { + applyPickerPayloadToForm(task); + return serializeFormFields(form); + }); + + // applyPickerPayloadToForm leaves the form in single-task mode of the last task; + // restore the multi-task UI so the form is coherent if the AJAX call fails. + setMultiSelection(tasks); + + require(['core/ajax', 'core/notification'], function(Ajax, notification) { + Ajax.call([{ + methodname: 'mod_mumie_create_multiple_mumie_tasks', + args: { + contextid: parseInt(contextId), + section: section, + tasksformdata: tasksFormData, + }, + }])[0].done(function() { + window.location.href = M.cfg.wwwroot + '/course/view.php?id=' + courseId; + }).fail(function(error) { + notification.alert('', error.message); + }); + }); } return { @@ -390,15 +531,38 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv updateTaskDisplayElement(taskSelectionInput.value); }, setSelection: function(link, language, name) { + const tasksField = document.getElementsByName('mumie_multi_tasks')[0]; + if (tasksField) { + tasksField.value = ''; + } + const summary = document.getElementById('mumie_multi_tasks_summary'); + if (summary) { + summary.style.display = 'none'; + summary.innerHTML = ''; + } + const nameField = document.getElementById('id_name'); + if (nameField) { + nameField.disabled = false; + nameField.setAttribute('required', 'required'); + } + const nameFieldContainer = document.getElementById('fitem_id_name'); + if (nameFieldContainer) { + nameFieldContainer.querySelectorAll('.req, .text-danger, [title="Required field"]') + .forEach(requiredIndicator => { requiredIndicator.style.display = ''; }); + } + const requiredLegend = document.querySelector('.fdescription.required'); + if (requiredLegend) { + requiredLegend.style.display = ''; + } updateTaskUri(link, language); updateName(name); }, setIsGraded: function(isGraded) { - durationController().setGradedElemValue(isGraded); + durationController.setGradedElemValue(isGraded); updateGradeEditability(); }, getGradingType: function() { - const isGraded = durationController().getGradedElemValue(); + const isGraded = durationController.getGradedElemValue(); if (isGraded === '1') { return 'graded'; } else if (isGraded === '0') { @@ -408,7 +572,13 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv }, getDelocalizedTaskLink: function() { return delocalizeLink(taskSelectionInput.value); - } + }, + setMultiSelection: setMultiSelection, + hasMultiTasks: function() { + const tasksField = document.getElementsByName('mumie_multi_tasks')[0]; + return tasksField && !!tasksField.value; + }, + submitMultiTasks: submitMultiTasks, }; })(); @@ -450,6 +620,17 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv }); } + /** + * Show or hide per-task "Working period: not Deadline" warnings based on whether the duedate property is selected. + */ + function updateNoDuedateWarnings() { + const duedateSelected = Array.from(propertySelectionInputs) + .some(checkbox => checkbox.value === 'duedate' && checkbox.checked); + document.querySelectorAll('.mumie-form-working-period-not-duedate-warning').forEach(function(elem) { + elem.style.display = duedateSelected ? '' : 'none'; + }); + } + /** * Set selection listeners for properties to apply to MUMIE Tasks in the course. */ @@ -462,6 +643,7 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv selectedTaskProp.push(checkbox.value); } selectedTaskProperties.value = JSON.stringify(selectedTaskProp); + updateNoDuedateWarnings(); }; }); } @@ -535,10 +717,11 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv } return { - init: function(contextIdParam, prbSelectorUrl, lang) { + init: function(contextIdParam, prbSelectorUrl, lang, sectionParam) { lmsSelectorUrl = prbSelectorUrl; systemLanguage = lang; contextId = contextIdParam; + section = sectionParam; const isEdit = document.getElementById("id_name").getAttribute('value'); const serverStructure = JSON.parse(document.getElementsByName('mumie_server_structure')[0].value); if (isEdit && !serverConfigExists()) { @@ -551,14 +734,35 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv taskController.init(); multiTaskEditController.init(); problemSelectorController.init(); - durationController().init(); + durationController.init(); } - multiTaskEditController.init(); if (addServerButton) { require(['auth_mumie/mumie_server_config'], function(MumieServer) { MumieServer.init(addServerButton, contextId); }); } + + const submitButton = document.getElementById('id_submitbutton'); + const form = submitButton && submitButton.closest('form'); + if (form) { + let cancelClicked = false; + const cancelButton = document.getElementById('id_cancel'); + if (cancelButton) { + cancelButton.addEventListener('click', function() { + cancelClicked = true; + }); + } + form.addEventListener('submit', function(e) { + if (cancelClicked) { + cancelClicked = false; + return; + } + if (taskController.hasMultiTasks()) { + e.preventDefault(); + taskController.submitMultiTasks(); + } + }); + } } }; @@ -579,4 +783,4 @@ define(['jquery', 'core/templates', 'core/modal_factory', 'auth_mumie/mumie_serv function serverConfigExists() { return document.getElementsByName("mumie_missing_config")[0].getAttribute("value") === ""; } - }); \ No newline at end of file + }); diff --git a/amd/src/view.js b/amd/src/view.js index 32d1db10..22f36665 100644 --- a/amd/src/view.js +++ b/amd/src/view.js @@ -1,4 +1,4 @@ -define(['jquery', 'core/templates', 'core/modal_factory', 'mod_mumie/duedate_form'], +define(['jquery', 'core/templates', 'mod_mumie/duedate_form'], function($) { return { init: function(contextid) { diff --git a/backup/moodle2/backup_mumie_stepslib.php b/backup/moodle2/backup_mumie_stepslib.php index 5bc9ccc7..60b73ff4 100644 --- a/backup/moodle2/backup_mumie_stepslib.php +++ b/backup/moodle2/backup_mumie_stepslib.php @@ -31,7 +31,7 @@ protected function define_structure() { 'name', 'intro', 'introformat', 'timecreated', 'timemodified', 'taskurl', 'launchcontainer', 'mumie_course', 'language', 'server', 'mumie_coursefile', - 'lastsync', 'points', 'completionpass', 'privategradepool', 'duedate', 'isgraded']); + 'lastsync', 'points', 'privategradepool', 'duedate', 'isgraded']); $serverconfig = new backup_nested_element("serverconfig", ['id'], ['name', 'url_prefix']); $mumie->add_child($serverconfig); diff --git a/changelog.md b/changelog.md index d1457474..26edf98b 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,29 @@ All important changes to this plugin will be documented in this file. +## [1.10.0] - 2026-06-29 +### Added +- Teachers can now apply the deadline setting to multiple MUMIE Tasks at once using the multi-edit feature +- Tasks without a 'Deadline' working period show a warning when deadline transfer is selected +- Validation prevents transferring a deadline to tasks that were not created with working period type 'Deadline' +- SSO when opening the Multi Problem Selector for the Problem Pool + +### Changed +- Plugin now requires Moodle 4.3 or higher +- Support Moodle 5.2 +- Multi Problem Selector now creates MUMIE Tasks via form submission instead of drag-and-drop +- Settings configured on the form (points, deadline, container, etc.) are now applied to all tasks created via the Multi Problem Selector +- Multi Problem Selector button is unavailable when editing an existing MUMIE Task + +### Removed +- Drag-and-drop creation of MUMIE Tasks is no longer supported +- The "Require passing grade" completion option in activity settings; use Moodle's built-in "Receive a grade" → "Passing grade" option instead. + +### Fixed +- MUMIE Task no longer shows working period descriptions for all options simultaneously +- Inconsistent working period description texts corrected +- Deleting a due date extension no longer fails with a server error + ## [v1.9.1] - 2025-07-03 ### Changed - Refactored code to comply with PHP 8.4 diff --git a/classes/mumie_dndupload_processor.php b/classes/mumie_dndupload_processor.php deleted file mode 100644 index bd55925c..00000000 --- a/classes/mumie_dndupload_processor.php +++ /dev/null @@ -1,335 +0,0 @@ -. - -/** - * This file describes a class used to create MUMIE Tasks via drag & drop. - * - * @package mod_mumie - * @copyright 2017-2025 integral-learning GmbH (https://www.integral-learning.de/) - * @author Tobias Goltz (tobias.goltz@integral-learning.de) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace mod_mumie; - -defined('MOODLE_INTERNAL') || die; - -require_once($CFG->dirroot . '/auth/mumie/classes/mumie_server.php'); - -/** - * This processor is responsible for the creation of MUMIE Task that are being imported via dnd_handler. - * - * Some functions are taken and adapted from course/dnduploadlib.php. - * - * @package mod_mumie - * @copyright 2017-2020 integral-learning GmbH (https://www.integral-learning.de/) - * @author Tobias Goltz (tobias.goltz@integral-learning.de) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class mumie_dndupload_processor { - /** - * The number of the sections we want to add MUMIE Tasks to. - * @var int - */ - private $section; - - /** - * The data type of the uploaded file. - * @var string - */ - private $type; - - /** - * The uploaded file. - * @var \stdClass - */ - private $upload; - - /** - * The context of the current course. - * @var \context_course - */ - private $coursecontext; - - /** - * The course we want to create the MUMIE Tasks in. - * @var \stdClass - */ - private $course; - - /** - * The gradepool settings we're going to use for the new MUMIE Tasks. - * @var int - */ - private $gradepoolsettings; - - /** - * Constructor - * @param int $courseid Id of course we want to create Tasks in - * @param int $section The section number we are uploading to - * @param string $type The type identifier of the uploaded data - * @param stdClass $upload The object that has been uploaded - */ - public function __construct($courseid, $section, $type, $upload) { - $this->section = $section; - $this->type = $type; - $this->upload = $upload; - $this->gradepoolsettings = $this->get_course_gradepool_setting(); - $this->coursecontext = \context_course::instance($courseid); - global $COURSE; - $this->course = $COURSE; - } - - /** - * Start the creation of MUMIE Tasks for the uploaded data. - * - * We're hooking into the regular dnd_upload process. - * That's why the first uploaded problem needs to be handled differently form any further instances. - * @return int id of newly added grade item or false - */ - public function process() { - require_capability('moodle/course:manageactivities', $this->coursecontext); - require_sesskey(); - - if ($this->type == 'mumie/json') { - return $this->handle_single_upload(); - } - if ($this->type == 'mumie/jsonArray') { - return $this->handle_multi_upload(); - } - return false; - } - - /** - * Handle uploads of type mumie/json. Only a single Mumie Task will be created. - * This format is used by MUMIE servers. - * @return int id of newly added grade item - */ - private function handle_single_upload() { - global $CFG; - require_once($CFG->dirroot . '/auth/mumie/classes/mumie_server.php'); - $server = new \auth_mumie\mumie_server(); - $server->set_urlprefix($this->upload->server); - - $this->validate_upload_params($this->upload); - $this->validate_servers([$server]); - $this->create_missing_servers([$server]); - - $mumie = $this->create_mumie_from_uploadinstance($this->upload, $server); - $mumie->server = $server->get_urlprefix(); - - return mumie_add_instance($mumie, null); - } - - /** - * Handle uploads of type mumie/jsonArray. This function creates one or more instances of MUMIE Task. - * This format is used by LEMON servers. - * @return int id of newly added grade item - */ - private function handle_multi_upload() { - // This array holds unique server objects. - $servers = []; - $this->upload = (array) $this->upload; - foreach ($this->upload as $uploadinstance) { - $uploadinstance = json_decode($uploadinstance); - $this->validate_upload_params($uploadinstance); - $server = new \auth_mumie\mumie_server(); - $server->set_urlprefix($uploadinstance->server); - $servers[$uploadinstance->server] = $server; - } - - $this->validate_servers(array_values($servers)); - $this->create_missing_servers(array_values($servers)); - - $result; - $uploadlength = count($this->upload); - for ($i = 0; $i < $uploadlength; $i++) { - $uploadinstance = json_decode($this->upload[$i]); - $mumie = $this->create_mumie_from_uploadinstance($uploadinstance, $servers[$uploadinstance->server]); - $mumie->server = $servers[$uploadinstance->server]->get_urlprefix(); - if ($i == 0) { - $result = mumie_add_instance($mumie, null); - } else { - $module = $this->create_mumie_course_module($mumie); - } - } - - // Rebuild the course cache after update action. - rebuild_course_cache($this->course->id, true); - - // Return id of first mumie instance created. - return $result; - } - - /** - * Check whether all servers are valid MUMIE/Lemon servers. - * - * If a URL is not valid, an exception is thrown. - * - * @param \auth_mumie\mumie_server[] $servers The list of server we want to validate - */ - private function validate_servers($servers) { - foreach ($servers as $server) { - if (!$server->is_valid_mumie_server()) { - throw new \moodle_exception('mumie_form_server_not_existing', 'auth_mumie'); - } - } - } - - /** - * Make sure that there is a server configuration for every server used. If there aren't, create them. - * - * @param \auth_mumie\mumie_server[] $servers The list of servers for which we need server configurations. - */ - private function create_missing_servers($servers) { - $missingservers = array_filter( - $servers, - function ($server) { - return !$server->config_exists_for_url(); - } - ); - - if (empty($missingservers)) { - return; - } - - if (!has_capability("auth/mumie:addserver", \context_course::instance($this->course->id), $USER)) { - throw new \moodle_exception(get_string('server_config_missing', 'mod_mumie', $server->get_urlprefix())); - } - foreach ($missingservers as $server) { - $server->set_name($server->get_urlprefix()); - $server->upsert(); - } - } - - /** - * Get the value for MUMIE Task's gradepool setting in this course. - * - * @return int gradepool value for the imported MUMIE Tasks. - */ - private function get_course_gradepool_setting() { - global $DB, $COURSE; - $exitingtasks = array_values( - $DB->get_records(MUMIE_TASK_TABLE, ["course" => $COURSE->id]) - ); - $adminsetting = get_config('auth_mumie', 'defaultgradepool'); - if (count($exitingtasks) > 0) { - return $exitingtasks[0]->privategradepool; - } - if ($adminsetting != -1) { - return $adminsetting; - } - return null; - } - - /** - * Make sure that there are all necessary parameters set in the uploaded problem instance. - * - * @param \stdClass $uploadinstance a problem instance that's being imported - */ - private function validate_upload_params($uploadinstance) { - if ( - empty($uploadinstance->link) || empty($uploadinstance->path_to_coursefile) - || empty($uploadinstance->language) || empty($uploadinstance->name) - || empty($uploadinstance->server) || empty($uploadinstance->course) - ) { - throw new \moodle_exception('parameter_missing', 'mod_mumie'); - } - } - - /** - * Get a mumie object from the uploaded problem instance. - * - * @param \stdClass $uploadinstance The uploaded problem. - * @return \stdClass The mumie object that's going to be created. - */ - private function create_mumie_from_uploadinstance($uploadinstance) { - $mumie = new \stdClass(); - $mumie->taskurl = $uploadinstance->link . '?lang=' . $uploadinstance->language; - $mumie->mumie_coursefile = $uploadinstance->path_to_coursefile; - $mumie->language = $uploadinstance->language; - $mumie->course = $this->course->id; - $mumie->mumie_course = $uploadinstance->course; - $mumie->intro = ''; - $mumie->points = 100; - $mumie->name = \mod_mumie\locallib::get_default_name($uploadinstance) ?? $uploadinstance->name; - $mumie->privategradepool = $this->gradepoolsettings; - $mumie->isgraded = $uploadinstance->isGraded; - return $mumie; - } - - /** - * Create database entries for the new mumie instance and the linked course module. - * - * @param \stdClass $mumie The mumie object we want to add to the course. - */ - private function create_mumie_course_module($mumie) { - $mumieinstance = mumie_add_instance($mumie, null); - $cm = $this->create_course_module(); - $this->finish_setup_course_module($mumieinstance, $cm); - } - - /** - * Create a new course module in the database. - */ - private function create_course_module() { - global $CFG; - require_once($CFG->dirroot . '/course/modlib.php'); - [$module, $context, $cw, $cm, $data] = prepare_new_moduleinfo_data($this->course, 'mumie', $this->section); - $data->coursemodule = $data->id = add_course_module($data); - return $data; - } - - /** - * Set missing values for course_module and finish the setup. - * - * @param int $instanceid id of newly created mumie instance - * @param \stdClass $cm Newly created coursemodule instance - */ - protected function finish_setup_course_module($instanceid, $cm) { - global $DB; - - if (!$instanceid) { - // Something has gone wrong - undo everything we can. - course_delete_module($cm->id); - throw new \moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name); - } - - // Note the section visibility. - $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible; - - $DB->set_field('course_modules', 'instance', $instanceid, ['id' => $cm->id]); - - course_add_cm_to_section($this->course, $cm->id, $this->section); - - set_coursemodule_visible($cm->id, $visible); - if (!$visible) { - $DB->set_field('course_modules', 'visibleold', 1, ['id' => $cm->id]); - } - - // Retrieve the final info about this module. - $info = get_fast_modinfo($this->course); - if (!isset($info->cms[$cm->id])) { - // The course module has not been properly created in the course - undo everything. - course_delete_module($cm->id); - throw new \moodle_exception('errorcreatingactivity', 'moodle', '', "mod_mumie"); - } - $mod = $info->get_cm($cm->id); - - // Trigger course module created event. - $event = \core\event\course_module_created::create_from_cm($mod); - $event->trigger(); - } -} diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 48a7db41..1c0bda61 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -39,7 +39,10 @@ * @author Tobias Goltz (tobias.goltz@integral-learning.de) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\request\core_userlist_provider, \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider { +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\core_userlist_provider, + \core_privacy\local\request\plugin\provider { /** * Returns meta data about this system. * diff --git a/db/install.xml b/db/install.xml index e0b9d56d..587d8bdb 100644 --- a/db/install.xml +++ b/db/install.xml @@ -21,7 +21,6 @@ - diff --git a/db/services.php b/db/services.php index 03c4f555..b4510d25 100644 --- a/db/services.php +++ b/db/services.php @@ -34,4 +34,12 @@ 'ajax' => true, 'type' => 'write', ], + 'mod_mumie_create_multiple_mumie_tasks' => [ + 'classname' => 'mod_mumie_external', + 'methodname' => 'create_multiple_mumie_tasks', + 'classpath' => 'mod/mumie/externallib.php', + 'description' => 'Creates multiple MUMIE Tasks at once with shared settings', + 'ajax' => true, + 'type' => 'write', + ], ]; diff --git a/db/upgrade.php b/db/upgrade.php index cf8cbd60..36d1bca5 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -91,6 +91,10 @@ function xmldb_mumie_upgrade($oldversion) { addfieldifmissing('mumie', 'timelimit', XMLDB_TYPE_INTEGER, '10', null, false, null, null, null); upgrade_plugin_savepoint(true, 2025031200, 'mod', 'mumie'); } + if ($oldversion < 2026062900) { + dropfieldifexists('mumie', 'completionpass'); + upgrade_plugin_savepoint(true, 2026062900, 'mod', 'mumie'); + } return true; } @@ -143,3 +147,20 @@ function addfieldifmissing( $dbman->add_field($table, $field); } } + +/** + * Drops a field from a table if it exists. + * + * @param string $tablename + * @param string $fieldname + * @return void + */ +function dropfieldifexists(string $tablename, string $fieldname): void { + global $DB; + $dbman = $DB->get_manager(); + $table = new xmldb_table($tablename); + $field = new xmldb_field($fieldname); + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } +} diff --git a/delete_duedate_extension.php b/delete_duedate_extension.php index a01497fa..9906aa42 100644 --- a/delete_duedate_extension.php +++ b/delete_duedate_extension.php @@ -33,10 +33,10 @@ $cmid = required_param('cmid', PARAM_INT); $returnurl = new \moodle_url('/mod/mumie/view.php', ["id" => $cmid, "action" => "grading"]); -require_capability('mod/mumie:revokeduedateextension', context_system::instance()); -$extension = mod_mumie\mumie_duedate_extension::load_by_id($duedateid); +require_capability('mod/mumie:revokeduedateextension', \core\context\system::instance()); +$extension = mumie_duedate_extension::load_by_id($duedateid); $extension->delete(); -$calendarservice = new mod_mumie\mumie_individual_calendar_service($extension->get_mumie(), $extension->get_userid()); +$calendarservice = new mumie_individual_calendar_service($extension->get_mumie(), $extension->get_userid()); $calendarservice->update(); redirect($returnurl); diff --git a/externallib.php b/externallib.php index 3c0ea6b0..9648b122 100644 --- a/externallib.php +++ b/externallib.php @@ -25,6 +25,10 @@ defined('MOODLE_INTERNAL') || die; +use mod_mumie\locallib; +use mod_mumie\form_field_decoder; +use mod_mumie\mumie_task_validator; + require_once($CFG->libdir . "/externallib.php"); /** @@ -125,4 +129,104 @@ public static function submit_mumieduedate_form($contextid, $jsonformdata) { public static function submit_mumieduedate_form_returns() { return new external_value(PARAM_INT, 'duedate id'); } + + /** + * Describes the parameters for create_multiple_mumie_tasks webservice. + * @return external_function_parameters + */ + public static function create_multiple_mumie_tasks_parameters() { + return new external_function_parameters([ + 'contextid' => new external_value(PARAM_INT, 'Context id of the course'), + 'section' => new external_value(PARAM_INT, 'Section number to add tasks to'), + 'tasksformdata' => new external_multiple_structure( + new external_value(PARAM_RAW, 'URL-encoded form POST for one task'), + 'One serialized form POST per task to create' + ), + ]); + } + + /** + * Create multiple MUMIE tasks at once. + * + * Each entry of tasksformdata is the same payload the form would submit + * for a single task. The picker-payload → form-field mapping lives in JS + * (applyPickerPayloadToForm) and is shared with the single-task path, so + * adding a new picker field doesn't require parallel PHP changes. + * + * @param int $contextid Course context id + * @param int $section Section number + * @param string[] $tasksformdata One URL-encoded form POST per task + * @return array Array of created course module ids + */ + public static function create_multiple_mumie_tasks($contextid, $section, $tasksformdata) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/course/modlib.php'); + require_once($CFG->dirroot . '/mod/mumie/locallib.php'); + require_once($CFG->dirroot . '/mod/mumie/forms/form_field_decoder.php'); + require_once($CFG->dirroot . '/mod/mumie/forms/mumie_task_validator.php'); + + $params = self::validate_parameters( + self::create_multiple_mumie_tasks_parameters(), + [ + 'contextid' => $contextid, + 'section' => $section, + 'tasksformdata' => $tasksformdata, + ] + ); + + $context = context::instance_by_id($params['contextid'], MUST_EXIST); + self::validate_context($context); + require_capability('mod/mumie:addinstance', $context); + + if (empty($params['tasksformdata'])) { + throw new invalid_parameter_exception('tasksformdata must be a non-empty array'); + } + if (count($params['tasksformdata']) > 50) { + throw new invalid_parameter_exception('Cannot create more than 50 tasks at once'); + } + + $course = $DB->get_record('course', ['id' => $context->instanceid], '*', MUST_EXIST); + $mumiemodule = $DB->get_record('modules', ['name' => 'mumie'], '*', MUST_EXIST); + + $createdids = []; + foreach ($params['tasksformdata'] as $taskpost) { + $formfields = []; + parse_str($taskpost, $formfields); + + $instancedata = form_field_decoder::from_form_fields($formfields); + locallib::clean_up_duration_values($instancedata); + + $instancedata->modulename = 'mumie'; + $instancedata->module = $mumiemodule->id; + $instancedata->course = $course->id; + $instancedata->section = $params['section']; + $instancedata->visible = 1; + $instancedata->intro = ''; + $instancedata->introformat = FORMAT_HTML; + if (isset($instancedata->points)) { + $instancedata->grade = (int)$instancedata->points; + } + + $errors = mumie_task_validator::get_errors((array)$instancedata, new stdClass()); + if (!empty($errors)) { + throw new moodle_exception('mumie_multi_task_validation_error', 'mod_mumie', '', reset($errors)); + } + + $created = add_moduleinfo($instancedata, $course); + $createdids[] = $created->coursemodule; + } + + return $createdids; + } + + /** + * Describes the return value for create_multiple_mumie_tasks webservice. + * @return external_multiple_structure + */ + public static function create_multiple_mumie_tasks_returns() { + return new external_multiple_structure( + new external_value(PARAM_INT, 'course module id') + ); + } } diff --git a/forms/form_field_decoder.php b/forms/form_field_decoder.php new file mode 100644 index 00000000..bb83b7b3 --- /dev/null +++ b/forms/form_field_decoder.php @@ -0,0 +1,152 @@ +. + +/** + * Decode raw mod_form POST fields into a flat $data object. + * + * @package mod_mumie + * @copyright 2017-2026 integral-learning GmbH (https://www.integral-learning.de/) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_mumie; + +use stdClass; + +/** + * Decode raw mod_form POST fields into a flat $data object. + * + * Mimics the per-element conversion that moodleform::get_data() does + * (date_selector → timestamp, duration → seconds), without instantiating + * the form. Used by the multi-task creation external function so it can + * share validation/postprocessing/persistence with the single-task path. + */ +class form_field_decoder { + /** + * Form-internals and form-side helper fields that are never instance data. + * Submission protocol markers, hidden JSON holders, display-only labels, config echoes. + */ + private const FORM_MECHANIC_FIELDS = [ + '_qf__mod_mumie_mod_form', + 'sesskey', + 'submitbutton', + 'cancel', + 'mumie_multi_tasks', + 'mumie_server_structure', + 'task_display_element', + 'mumie_org', + ]; + + /** + * Real form fields that the multi-create flow must NOT carry into per-task $instancedata. + * + * mumie_selected_tasks / mumie_selected_task_properties drive the single-task "apply + * settings to other MUMIE tasks" sidebar. If they leak into a per-task moduleinfo, + * mumie_add_instance → mumie_update_multiple_tasks would cascade the per-task settings + * onto the listed tasks once per created instance. + */ + private const MULTI_CREATE_SUPPRESSED_FIELDS = [ + 'mumie_selected_tasks', + 'mumie_selected_task_properties', + ]; + + /** + * Build a $data stdClass from raw form fields (post-parse_str). + * + * Scalar fields are copied as-is. The date_selector group `duedate` is converted to a + * unix timestamp and the duration group `timelimit` is converted to seconds. Other array + * fields (e.g. tags, competencies — multi-select autocompletes) are passed through, with + * the '_qf__force_multiselect_submission' sentinel normalised to an empty array, matching + * what MoodleQuickForm_autocomplete::exportValue() does for empty multi-selects. + * + * @param array $formfields Result of parse_str on the serialized form. + * @return stdClass + */ + public static function from_form_fields(array $formfields): stdClass { + $excluded = array_merge(self::FORM_MECHANIC_FIELDS, self::MULTI_CREATE_SUPPRESSED_FIELDS); + $data = new stdClass(); + foreach ($formfields as $key => $value) { + if (in_array($key, $excluded, true)) { + continue; + } + if ($key === 'duedate' || $key === 'timelimit' || $key === 'completionexpected') { + continue; + } + $data->$key = self::normalize_multiselect_sentinel($value); + } + $data->duedate = self::decode_date($formfields, 'duedate'); + $data->completionexpected = self::decode_date($formfields, 'completionexpected'); + $data->timelimit = self::decode_timelimit($formfields); + return $data; + } + + /** + * Mimic what MoodleQuickForm_autocomplete::exportValue() does for empty multi-selects: + * the form element emits '_qf__force_multiselect_submission' as a marker that the field + * was rendered but nothing was picked. moodleform normalizes that to an empty array + * before validation; we have to do the same since we parse the raw POST. + * + * @param mixed $value + * @return mixed + */ + private static function normalize_multiselect_sentinel($value) { + if ($value === '_qf__force_multiselect_submission') { + return []; + } + if (is_array($value)) { + return array_values(array_filter( + $value, + static fn($v) => $v !== '_qf__force_multiselect_submission' + )); + } + return $value; + } + + /** + * Convert a date_selector group `$field[year|month|day|hour|minute]` to a unix timestamp. + * + * @param array $formfields + * @param string $field + * @return int + */ + private static function decode_date(array $formfields, string $field): int { + $dategroup = $formfields[$field] ?? null; + if (!is_array($dategroup) || empty($dategroup['year'])) { + return 0; + } + return make_timestamp( + $dategroup['year'], + $dategroup['month'], + $dategroup['day'], + $dategroup['hour'] ?? 0, + $dategroup['minute'] ?? 0 + ); + } + + /** + * Convert a duration group `timelimit[number|timeunit]` to seconds. + * + * @param array $formfields + * @return int + */ + private static function decode_timelimit(array $formfields): int { + $timelimit = $formfields['timelimit'] ?? null; + if (!is_array($timelimit) || empty($timelimit['timeunit'])) { + return 0; + } + return (int)$timelimit['number'] * (int)$timelimit['timeunit']; + } +} diff --git a/forms/mumie_task_validator.php b/forms/mumie_task_validator.php index 9addee74..ed3dfcab 100644 --- a/forms/mumie_task_validator.php +++ b/forms/mumie_task_validator.php @@ -46,10 +46,10 @@ class mumie_task_validator { public static function get_errors(array $data, \stdClass $current): array { $errors = []; $errors = array_merge($errors, self::check_required($data)); - $errors = array_merge($errors, self::check_completion($data, $current)); $errors = array_merge($errors, self::check_isgraded($data)); $errors = array_merge($errors, self::check_duration($data)); $errors = array_merge($errors, self::check_worksheet($data)); + $errors = array_merge($errors, self::check_multi_edit_duedate($data)); return $errors; } @@ -80,32 +80,6 @@ private static function check_required(array $data): array { return $errors; } - /** - * Validates the completion settings of a MUMIE activity. - * - * Ensures that if automatic completion tracking is enabled and completion requires passing, - * then a valid passing grade must be set. Adds appropriate errors if this condition is not met. - * - * @param array $data Form data submitted by the user. - * @param \stdClass $current The current MUMIE activity instance for fallback values. - * @return array Associative array of validation errors. - */ - private static function check_completion(array $data, \stdClass $current): array { - $errors = []; - if (($data['completion'] ?? null) == COMPLETION_TRACKING_AUTOMATIC) { - $completionpass = $data['completionpass'] ?? $current->completionpass; - $gradepass = grade_floatval($data['gradepass'] ?? 0); - - if ($completionpass && $gradepass == 0) { - $completionpassisset = isset($data['completionpass']); - $key = $completionpassisset ? 'completionpassgroup' : 'gradepass'; - $stringkey = $completionpassisset ? 'gradetopassnotset' : 'gradetopassmustbeset'; - $errors[$key] = get_string($stringkey, 'mumie'); - } - } - return $errors; - } - /** * Validates that the grading status of an existing MUMIE task has not been changed. * @@ -162,7 +136,6 @@ private static function check_duration(array $data): array { * @return array Associative array of validation errors. */ private static function check_worksheet(array $data): array { - $isworksheet = self::is_worksheet($data); if (!$isworksheet) { return []; @@ -181,6 +154,30 @@ private static function check_worksheet(array $data): array { return $errors; } + /** + * Validates that the duedate property is only applied to tasks that already have a duedate set. + * + * @param array $data Form data. + * @return array Associative array of validation errors. + */ + private static function check_multi_edit_duedate(array $data): array { + if (!self::is_duedate_property_selected($data)) { + return []; + } + + $errors = []; + $taskids = json_decode($data['mumie_selected_tasks'] ?? '[]', true); + foreach ($taskids as $taskid) { + $task = locallib::get_mumie_task((int)$taskid); + if ($task && ($task->duedate <= 0)) { + $errors['mumie_multi_edit_deadline_error'] = + get_string('mumie_form_deadline_transfer_prohibited_for_tasks_without_deadline', 'mod_mumie'); + break; + } + } + return $errors; + } + /** * Check whether a duedate was set * @param array $data POST data @@ -218,4 +215,14 @@ private static function is_worksheet(array $data): bool { private static function is_correction_trigger_after_deadline(string $worksheet): bool { return json_decode($worksheet, true)['configuration']['correction']['correctorType'] === "AFTER_DEADLINE"; } + + /** + * Check whether the duedate property is selected for multi-edit transfer. + * @param array $data POST data + * @return bool + */ + private static function is_duedate_property_selected(array $data): bool { + $props = json_decode($data['mumie_selected_task_properties'] ?? '[]', true); + return in_array('duedate', $props); + } } diff --git a/lang/de/mumie.php b/lang/de/mumie.php index e9b15eba..b202e8b9 100644 --- a/lang/de/mumie.php +++ b/lang/de/mumie.php @@ -24,20 +24,9 @@ */ defined('MOODLE_INTERNAL') || die; -$string['completionpass'] = ' Bewertung für Bestehen notwendig'; -$string['completionpass_help'] = 'Wenn diese Option aktiviert ist, wird die Aktivität als abgeschlossen betrachtet, sobald eine Bewertung vorliegt. Icons für die erfolgreiche oder erfolglose Bearbeitung können angezeigt werden, -wenn dafür eine Bewertungsgrenze festgelegt wurde. -

Bitte beachten Sie:
-Noten für MUMIE Tasks werden nur aktualisiert, wenn sie im Moodle-Gradebook angezeigt werden. Wenn Sie aktuelle Informationen über den Aktivitätsabschluss aller Studierenden sehen möchten, -sollten zu daher zuvor die Bewerterübersicht öffnen.'; -$string['dnd_addmessage'] = 'MUMIE-Task hinzufügen'; -$string['dnd_addmessage_multiple'] = 'MUMIE-Tasks hinzufügen'; -$string['dndupload_message'] = 'Als neue MUMIE Task hinzufügen'; -$string['gradetopassmustbeset'] = 'Die Bestehensgrenze kann nicht 0 sein, da der Aktivitätsabschluss vom Erreichen dieser Grenze abhängig ist. Bitte geben Sie daher einen Wert über 0 ein.'; -$string['gradetopassnotset'] = 'Dieser Test hat keine Bestehensgrenze.'; $string['modulename'] = 'MUMIE Task'; $string['modulename_help'] = '

Dieses Aktivität-Modul ermöglicht die Nutzung von Inhalten der MUMIE-Plattform auf Moodle und eine automatische Synchronisierung der dort erzielten Noten. -Ein MUMIE Task steht hierbei für eine einzelne benote Aufgabe eines MUMIE-Kurses.

+Ein MUMIE Task steht hierbei für eine einzelne benotete Aufgabe eines MUMIE-Kurses.

Was ist MUMIE?

@@ -66,14 +55,14 @@ $string['mumie:viewgrades'] = 'Alle Noten einer MUMIE Task in einem Kurs sehen'; $string['mumie_calendar_duedate_desc'] = 'Später Abgaben werden nicht in die Moodle-Bewertungen übernommen'; $string['mumie_calendar_duedate_extension'] = 'Verlängerte Abgabefrist: {$a}'; -$string['mumie_calendar_duedate_name'] = 'Abgabefirst: {$a}'; +$string['mumie_calendar_duedate_name'] = 'Abgabefrist: {$a}'; $string['mumie_due_date'] = 'Abgabefrist'; -$string['mumie_due_date_help'] = 'Die Noten werden erst nach Ablauf der Frist mit Moodle synchronisiert.'; +$string['mumie_due_date_help'] = 'Noten, die nach der ausgewählten Frist erzielt wurden, werden nicht mit Moodle synchronisiert.'; $string['mumie_duedate_extension'] = 'Abfabefristverlängerung'; $string['mumie_duedate_form'] = 'Abgabefrist verlängern'; $string['mumie_duedate_not_set'] = 'Keine allgemeine Abgabefrist für diese MUMIE-Task festgelegt!'; $string['mumie_duration_selector'] = 'Bearbeitungszeitraum'; -$string['mumie_duration_selector_help'] = 'Bei einem begrenzten Bearbeitungszeitraum werden die Noten erst nach Ablauf der Fristen mit Moodle synchronisiert.'; +$string['mumie_duration_selector_help'] = 'Bei einem begrenzten Bearbeitungszeitraum werden nur die in diesem Zeitraum erzielten Noten mit Moodle synchronisiert.'; $string['mumie_form_activity_container'] = 'Startcontainer'; $string['mumie_form_activity_container_embedded'] = 'Eingebunden'; $string['mumie_form_activity_container_help'] = 'Bitte wählen Sie, ob diese Aktivität in die Moodle-Umgebung eingebunden oder in einem neuen Browser-Tab geöffnet werden soll.

Bitte beachten Sie, dass eingebettete MUMIE Tasks aus technischen Gründen nicht mit Safari bearbeitet werden können. Für diese User wird diese MUMIE Task daher im gesamten Tab dargestellt.'; @@ -93,7 +82,9 @@ $string['mumie_form_complete_course_help'] = 'Nutzer werden nur eingeloggt und dann zur Kursübersicht weitergeleitet. Leistungsdaten werden für diese MUMIE-Task nicht synchronisiert.'; $string['mumie_form_coursefile'] = 'Path to MUMIE course meta file'; $string['mumie_form_deadline_prohibited_for_worksheet_without_trigger_after_deadline'] = "Das ausgewählte Worksheet darf keinen begrenzten Bearbeitungszeitraum haben.

Wählen Sie ein Worksheet mit entsprechender Konfiguration aus, wenn Sie eine Frist setzen möchten."; +$string['mumie_form_deadline_transfer_prohibited_for_tasks_without_deadline'] = 'Abgabefrist kann nur für MUMIE-Tasks übernommen werden, die bereits den Bearbeitungszeitraum Typ \'Abgabefrist\' haben.'; $string['mumie_form_deadline_required_for_trigger_after_deadline'] = "Das ausgewählte Worksheet setzt einen festgelegten Bearbeitungszeitraum voraus"; +$string['mumie_form_due_date_multi_edit_hint'] = 'Abgabefrist kann nur für MUMIE-Tasks übernommen werden, welche vorher bereits mit dem Bearbeitungszeitraum Typ \'Abgabefrist\' erstellt wurden.'; $string['mumie_form_due_date_must_be_future'] = 'Das Datum der Abgabefrist kann nicht in der Vergangenheit liegen!'; $string['mumie_form_filter'] = 'MUMIE-Tasks filtern'; $string['mumie_form_grade_pool'] = 'Punkte mit anderen Kursen teilen'; @@ -120,13 +111,16 @@ $string['mumie_form_topic'] = 'Thema: {$a}'; $string['mumie_form_updated_selection'] = 'Aufgabe erfolgreich ausgewählt'; $string['mumie_form_wiki_link'] = 'Bitte besuchen Sie unser Wiki für weiterführende Informationen!'; +$string['mumie_form_working_period_not_duedate_warning'] = 'Bearbeitungszeitraum: keine Abgabefrist'; $string['mumie_general_duedate'] = 'Allgemeine Abgabefrist:'; $string['mumie_general_timelimit'] = 'Zeitlimit: '; $string['mumie_grade_invalid'] = 'Bewertung ungültig!'; $string['mumie_grade_overridden'] = 'Bewertung erfolgreich gespeichert!'; $string['mumie_grade_percentage'] = 'Bewerung in %'; $string['mumie_grading_settings'] = "Individuelle Bewertungseinstellungen"; -$string['mumie_multi_problem_selector'] = 'Alternativ können Sie auch mehrere MUMIE Tasks gleichzeitig mit der Mehrfach-Aufgabenauswahl erstellen. Ziehen Sie dazu einfach die gewünschten Aufgaben in ihren Kurs.

Für eine Kurzanleitung und weitere Informationen besuchen Sie unser Wiki'; +$string['mumie_multi_problem_selector'] = 'Alternativ können Sie mit der Mehrfach-Aufgabenauswahl auch mehrere MUMIE-Aufgaben auf einmal auswählen. Alle Aufgaben werden mit den eingestellten Einstellungen angelegt (Punkte, Frist, Startcontainer etc.). Jede Aufgabe behält ihren eigenen Namen — anders als bei der Einzelauswahl kann kein eigener Name vergeben werden.

Weitere Informationen finden Sie in unserem Wiki'; +$string['mumie_multi_task_validation_error'] = '{$a}'; +$string['mumie_multi_tasks_selected'] = 'Sie haben folgende {$a} Aufgaben ausgewählt:'; $string['mumie_no_other_task_found'] = 'Keine andere MUMIE Tasks in Kurs gefunden'; $string['mumie_no_submissions'] = 'Keine Antworten abgegeben'; $string['mumie_open_task'] = 'MUMIE-Task öffnen'; @@ -142,9 +136,9 @@ $string['mumie_task_updated'] = 'Es wurde eine weitere MUMIE-Task aktualisiert'; $string['mumie_tasks_updated'] = 'Es wurden {$a} weitere MUMIE-Tasks aktualisiert'; $string['mumie_timelimit'] = 'Zeitlimit'; -$string['mumie_timelimit_help'] = 'Die Note eines Studierenden wird erst mit Moodle synchronisiert, nachdem seine individuelle Frist abgelaufen ist. Das Zeitlimit beginnt in dem Moment, in dem ein Studierender eine MUMIE-Aufgabe zum ersten Mal öffnet.'; +$string['mumie_timelimit_help'] = 'Noten, die nach Ablauf des Zeitlimits erzielt wurden, werden nicht mit Moodle synchronisiert. Das Zeitlimit beginnt in dem Moment, in dem ein Studierender eine MUMIE-Aufgabe zum ersten Mal öffnet.'; $string['mumie_unlimited'] = 'Uneingeschränkt'; -$string['mumie_unlimited_help'] = 'Die Noten können zu jeder Zeit mit Moodle synchronisiert werden.'; +$string['mumie_unlimited_help'] = 'Es gibt keine zeitliche Beschränkung. Alle erzielten Noten werden mit Moodle synchronisiert.'; $string['mumieintro'] = 'Aktivitätsbeschreibung'; $string['name'] = 'Name'; $string['nomumieinstance'] = 'In diesem Kurs gibt es keine MUMIE-Instanzen'; diff --git a/lang/en/mumie.php b/lang/en/mumie.php index ac863de7..bc9bacc8 100644 --- a/lang/en/mumie.php +++ b/lang/en/mumie.php @@ -24,15 +24,6 @@ */ defined('MOODLE_INTERNAL') || die; -$string['completionpass'] = 'Require passing grade'; -$string['completionpass_help'] = 'If enabled, this activity is considered completed when the student receives a passing grade, with the pass grade set in the gradebook. -

Please Note:
-Grades for MUMIE Tasks are only updated, when the gradebook is opened. If you want the current completion status of all students, remember to open the gradebook overview page first'; -$string['dnd_addmessage'] = 'Add MUMIE Task here'; -$string['dnd_addmessage_multiple'] = 'Add MUMIE Tasks here'; -$string['dndupload_message'] = 'Add as a new MUMIE Task'; -$string['gradetopassmustbeset'] = 'Grade to pass cannot be zero as this activity has its completion method set to require passing grade. Please set a non-zero value.'; -$string['gradetopassnotset'] = 'This MUMIE task does not have a grade to pass set so you cannot use this option. Please use the require grade setting instead.'; $string['modulename'] = 'MUMIE Task'; $string['modulename_help'] = '

This activity module enables the usage of content from the MUMIE e-learning platform and automatic grade synchronization. A MUMIE tasks represents an individual graded problem from a MUMIE course.

What is MUMIE?

@@ -60,7 +51,7 @@ $string['mumie_duedate_form'] = 'Grant a due date extension'; $string['mumie_duedate_not_set'] = 'No general due date set for this MUMIE Task!'; $string['mumie_duration_selector'] = 'Working period'; -$string['mumie_duration_selector_help'] = 'If the working period is limited, grades are only synchronized with Moodle after the period ends.'; +$string['mumie_duration_selector_help'] = 'If the working period is limited, only grades achieved within that period are synchronized with Moodle.'; $string['mumie_form_activity_container'] = 'Launch container'; $string['mumie_form_activity_container_embedded'] = 'embedded'; $string['mumie_form_activity_container_help'] = 'Please select whether the activity should be opened in a new browser tab or embedded into moodle.

Please note that embedded MUMIE Tasks cannot be opened with Safari due to technical limitations. This MUMIE Task will be opened in a tab instead for Safari users'; @@ -82,7 +73,9 @@ $string['mumie_form_complete_course_help'] = 'The user will only be logged in and redirected to the course overview page. Grades will not be synchronized for this MUMIE Task.'; $string['mumie_form_coursefile'] = 'Path to MUMIE course meta file'; $string['mumie_form_deadline_prohibited_for_worksheet_without_trigger_after_deadline'] = "The selected worksheet cannot have a limited working period.

If you want to limit the working period, please select a worksheet with an appropriate configuration."; +$string['mumie_form_deadline_transfer_prohibited_for_tasks_without_deadline'] = 'Deadline can only be applied to tasks that already have the working period type \'Deadline\' set.'; $string['mumie_form_deadline_required_for_trigger_after_deadline'] = "The selected worksheet requires a limited working period."; +$string['mumie_form_due_date_multi_edit_hint'] = 'Deadline can only be applied to tasks that were previously created with the working period type \'Deadline\'.'; $string['mumie_form_due_date_must_be_future'] = 'You must select a date in the future!'; $string['mumie_form_filter'] = 'Filter MUMIE problems'; $string['mumie_form_grade_pool'] = 'Share grades with other courses'; @@ -115,13 +108,16 @@ $string['mumie_form_topic'] = 'Topic: {$a}'; $string['mumie_form_updated_selection'] = 'Successfully selected problem'; $string['mumie_form_wiki_link'] = 'For help and advanced features, please visit our Wiki!'; +$string['mumie_form_working_period_not_duedate_warning'] = 'Working period: not Deadline'; $string['mumie_general_duedate'] = 'General due date:'; $string['mumie_general_timelimit'] = 'Timelimit: '; $string['mumie_grade_invalid'] = 'Grade is invalid!'; $string['mumie_grade_overridden'] = 'Updated grade successfully!'; $string['mumie_grade_percentage'] = 'Grade in %'; $string['mumie_grading_settings'] = "Individual grading settings"; -$string['mumie_multi_problem_selector'] = 'Alternatively, you can also create multiple MUMIE Tasks at once simply via Multi Problem Selector by dragging them into your course.

For information on how to use this, have a look at our wiki'; +$string['mumie_multi_problem_selector'] = 'Alternatively, you can also create multiple MUMIE Tasks at once using the Multi Problem Selector. All tasks will be created with the settings configured (points, deadline, container type, etc.). Each task keeps its own name — unlike single task creation, no custom name can be assigned.

For more information, have a look at our wiki'; +$string['mumie_multi_task_validation_error'] = '{$a}'; +$string['mumie_multi_tasks_selected'] = 'You have selected the following {$a} problems:'; $string['mumie_no_other_task_found'] = 'No other MUMIE Tasks found in course'; $string['mumie_no_submissions'] = 'No answers submitted'; $string['mumie_open_task'] = 'Open MUMIE Task'; @@ -139,9 +135,9 @@ $string['mumie_task_updated'] = 'One additional MUMIE Task has been updated'; $string['mumie_tasks_updated'] = '{$a} additional MUMIE Tasks have been updated'; $string['mumie_timelimit'] = 'Timelimit'; -$string['mumie_timelimit_help'] = 'Each student\'s grade is only synchronized with Moodle after their individual deadline has passed. The timelimit will start the moment a student first opens a MUMIE task.'; +$string['mumie_timelimit_help'] = 'Grades that were earned after the time limit will not be synchronized with Moodle. The time limit will start the moment a student first opens a MUMIE task.'; $string['mumie_unlimited'] = 'Unlimited'; -$string['mumie_unlimited_help'] = 'Grades can be synchronized with Moodle at any time.'; +$string['mumie_unlimited_help'] = 'There is no time-based restriction. All achieved grades will be synchronized with Moodle.'; $string['mumie_url_prefix'] = 'MUMIE URL Prefix'; $string['mumie_url_prefix_help'] = 'Specify the MUMIE URL prefix

e.g. https://www.ombplus.de/ombplus

There can only be a single configuration for any URL prefix.'; $string['mumieintro'] = 'Activity description'; diff --git a/lib.php b/lib.php index 911e3037..1b083c65 100644 --- a/lib.php +++ b/lib.php @@ -25,7 +25,6 @@ use mod_mumie\locallib; use mod_mumie\mumie_calendar_service; -use mod_mumie\mumie_dndupload_processor; use mod_mumie\mumie_duedate_extension; defined('MOODLE_INTERNAL') || die; @@ -48,7 +47,6 @@ function mumie_add_instance($mumie, $mform) { $mumie->timemodified = $mumie->timecreated; $mumie->use_hashed_id = 1; locallib::update_pending_gradepool($mumie); - $mumie = locallib::clean_up_duration_values($mumie); $mumie->id = $DB->insert_record("mumie", $mumie); mumie_grade_item_update($mumie); $calendarservice = new mumie_calendar_service($mumie); @@ -78,7 +76,6 @@ function mumie_update_instance($mumie, $mform) { $grades = locallib::has_problem_changed($mumie) ? "reset" : null; mumie_grade_item_update($mumie, $grades); - $mumie = locallib::clean_up_duration_values($mumie); $calendarservice = new mumie_calendar_service($mumie); $calendarservice->update(); @@ -205,8 +202,6 @@ function mumie_supports($feature) { switch ($feature) { case FEATURE_GRADE_HAS_GRADE: return true; - case FEATURE_COMPLETION_HAS_RULES: - return true; case FEATURE_BACKUP_MOODLE2: return true; case FEATURE_SHOW_DESCRIPTION: @@ -286,87 +281,6 @@ function mumie_before_standard_top_of_body_html() { return locallib::callbackimpl_before_standard_top_of_body_html(); } -/** - * Obtains the automatic completion state for this MUMIE task - * - * This is a code fragment copied from mod_quiz version 2018051400 - * @param object $course Course - * @param object $cm Course-module - * @param int $userid User ID - * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) - * @return bool True if completed, false if not. (If no conditions, then return - * value depends on comparison type) - */ -function mumie_get_completion_state($course, $cm, $userid, $type) { - global $DB, $CFG; - $mumie = $DB->get_record('mumie', ['id' => $cm->instance], '*', MUST_EXIST); - - if ($mumie->completionpass) { - require_once($CFG->libdir . '/gradelib.php'); - $item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod', - 'itemmodule' => 'mumie', 'iteminstance' => $cm->instance, 'outcomeid' => null]); - - if ($item) { - $grades = grade_grade::fetch_users_grades($item, [$userid], false); - if (!empty($grades[$userid])) { - return $grades[$userid]->is_passed($item); - } - } - } - return false; -} - -/** - * Register the ability to handle drag and drop of datatransfertype mumie/json - * @return array containing details of the files / types the mod can handle - */ -function mumie_dndupload_register() { - return [ - 'addtypes' => [ - [ - 'identifier' => 'mumie/json', 'datatransfertypes' => ['mumie/json', 'mumie/json'], - 'addmessage' => get_string('dnd_addmessage', 'mod_mumie'), - 'namemessage' => '', - 'priority' => 1], - [ - 'identifier' => 'mumie/jsonArray', 'datatransfertypes' => ['mumie/jsonArray', 'mumie/jsonArray'], - 'addmessage' => get_string('dnd_addmessage_multiple', 'mod_mumie'), - 'namemessage' => '', - 'priority' => 1], - ], - 'types' => [ - [ - 'identifier' => 'mumie/json', - 'message' => get_string('dndupload_message', 'mod_mumie'), - 'noname' => true], - [ - 'identifier' => 'mumie/jsonArray', - 'message' => get_string('dndupload_message', 'mod_mumie'), - 'noname' => true], - ], - ]; -} - -/** - * Handle content that has been uploaded - * @param object $uploadinfo details of the content that has been uploaded - * @return int instance id of the newly created mod - */ -function mumie_dndupload_handle($uploadinfo) { - global $CFG, $COURSE, $USER; - - $courseid = required_param('course', PARAM_INT); - $section = required_param('section', PARAM_INT); - $type = required_param('type', PARAM_TEXT); - - $context = context_module::instance($uploadinfo->coursemodule); - $upload = json_decode(clean_param($uploadinfo->content, PARAM_RAW)); - require_once($CFG->dirroot . '/mod/mumie/classes/mumie_dndupload_processor.php'); - $processor = new mumie_dndupload_processor($courseid, $section, $type, $upload); - $result = $processor->process(); - return $result; -} - /** * Get mumieserver_form as a fragment * diff --git a/locallib.php b/locallib.php index c64cbbc4..fcfabdbe 100644 --- a/locallib.php +++ b/locallib.php @@ -103,25 +103,6 @@ public static function update_pending_gradepool($mumietask) { } } - /** - * The function is called whenever a MUMIE task is created or updated. - * Cleans up the submitted duedate and timelimit if not selected in the duration selector. - * @param stdClass $mumietask the submitted MUMIE task that is supposed to be created or updated - * @return stdClass the MUMIE task with cleaned duration values - */ - public static function clean_up_duration_values(stdClass $mumietask): stdClass { - $workingperiod = $mumietask->duration_selector; - if (isset($workingperiod)) { - if ($workingperiod != 'duedate') { - $mumietask->duedate = 0; - } - if ($workingperiod != 'timelimit') { - $mumietask->timelimit = 0; - } - } - return $mumietask; - } - /** * Get a default name for the uploaded MumieTask, if available. * @@ -275,4 +256,22 @@ public static function callbackimpl_before_standard_top_of_body_html(): string { return ""; } + + /** + * Zero the duration column that does not match duration_selector. + * + * The form exposes duedate and timelimit side by side with duration_selector choosing + * which one is active. This enforces the invariant "only the active column is non-zero" + * before the data is handed to the persistence layer. + * + * @param stdClass $data Submitted form data (mutated in place). + */ + public static function clean_up_duration_values(\stdClass $data): void { + if ($data->duration_selector !== 'duedate') { + $data->duedate = 0; + } + if ($data->duration_selector !== 'timelimit') { + $data->timelimit = 0; + } + } } diff --git a/mod_form.php b/mod_form.php index d05b9792..26174c94 100644 --- a/mod_form.php +++ b/mod_form.php @@ -72,10 +72,22 @@ public function definition(): void { $context = context_course::instance($COURSE->id); $this->disable_grade_rules(); + // Resolve the target section number the same way modedit.php does. + // The multi-problem button is only enabled when adding (not editing), + // so URL params are populated; on Moodle 5.1+ the URL prefers sectionid, + // on older versions it uses section. + $sectionid = optional_param('sectionid', null, PARAM_INT); + $sectionnum = optional_param('section', 0, PARAM_INT); + if (empty($sectionnum) && !empty($sectionid)) { + $sectionnum = (int) get_fast_modinfo($COURSE) + ->get_section_info_by_id($sectionid, MUST_EXIST)->sectionnum; + } + $jsparams = [ json_encode($context->id), get_config('auth_mumie', 'mumie_problem_selector_url'), $USER->lang, + $sectionnum, ]; $PAGE->requires->js_call_amd('mod_mumie/mod_form', 'init', $jsparams); } @@ -149,6 +161,9 @@ private function add_general_fields(MoodleQuickForm $mform, $serveroptions): voi ) ); $mform->addElement('button', 'multi_problem_selector_btn', get_string('mumie_form_multi_prb_selector_btn', 'mod_mumie')); + $mform->addElement('html', ''); + $mform->addElement('hidden', 'mumie_multi_tasks', ''); + $mform->setType('mumie_multi_tasks', PARAM_RAW); $launchoptions = []; $launchoptions[MUMIE_LAUNCH_CONTAINER_EMBEDDED] = get_string("mumie_form_activity_container_embedded", "mod_mumie"); @@ -260,6 +275,16 @@ public function validation($data, $files): array { return \mod_mumie\mumie_task_validator::get_errors($data, $this->current); } + /** + * Post-process submitted form data before it is handed to mumie_(add|update)_instance. + * + * @param stdClass $data Submitted form data (mutated in place). + */ + public function data_postprocessing($data): void { + parent::data_postprocessing($data); + locallib::clean_up_duration_values($data); + } + /** * Get all options for server drop-down menu * @@ -273,52 +298,6 @@ private function get_server_options(): array { return $serveroptions; } - /** - * Provide option to mark an activity automatically as completed once a passing grade was archived - * - * This function is copied from mod_quiz version 2018051400 - * @return array containing the name of the mform group that has been added to the form - */ - public function add_completion_rules(): array { - $mform = $this->_form; - $items = []; - - $group = []; - $completionpasselement = $this->get_completion_rule_element_name('completionpass'); - $group[] = $mform->createElement( - 'advcheckbox', - $completionpasselement, - null, - get_string('completionpass', 'mumie'), - ['group' => 'cpass'] - ); - $completionusegradeelement = $this->get_completion_rule_element_name('completionusegrade'); - $mform->disabledIf($completionpasselement, $completionusegradeelement, 'notchecked'); - $completionpassgroupelement = $this->get_completion_rule_element_name('completionpassgroup'); - $mform->addGroup($group, $completionpassgroupelement, get_string('completionpass', 'mumie'), '   ', false); - $mform->addHelpButton($completionpassgroupelement, 'completionpass', 'mumie'); - $items[] = $completionpassgroupelement; - return $items; - } - - /** - * Get the completion rule's element name. - * - * Conditionally add suffix for Moodle >= 4.3. - * - * @param string $rawname The raw name of the completion rule. - * @return string The properly suffixed element name. - */ - private function get_completion_rule_element_name($rawname): string { - global $CFG; - if ($CFG->branch < 403) { - $suffix = ''; - } else { - $suffix = $this->get_suffix(); - } - return $rawname . $suffix; - } - /** * Disable all options for grades if the user has chosen to link a course instead of a problem. */ @@ -343,6 +322,15 @@ private function add_property_selection(): void { $taskproperties = [ [get_string('mumie_form_activity_container', 'mod_mumie'), "launchcontainer"], [get_string('mumie_form_points', 'mod_mumie'), "points"], + [ + get_string('mumie_due_date', 'mod_mumie') + . html_writer::tag( + 'div', + get_string('mumie_form_due_date_multi_edit_hint', 'mod_mumie'), + ['class' => 'form-text text-muted small'] + ), + "duedate", + ], ]; $table = new \html_table(); $table->attributes['class'] = 'generaltable mumie_table'; @@ -433,7 +421,15 @@ private function add_task_selection(): void { "section" => $section, ] ); - $table->data[] = [$module->name, $checkboxhtml]; + $label = $module->name; + if (!($module->duedate > 0)) { + $label .= html_writer::tag( + 'small', + ' — ' . get_string('mumie_form_working_period_not_duedate_warning', 'mod_mumie'), + ['class' => 'text-warning mumie-form-working-period-not-duedate-warning', 'style' => 'display:none'] + ); + } + $table->data[] = [$label, $checkboxhtml]; } $htmltables = ""; @@ -448,6 +444,13 @@ private function add_task_selection(): void { . $htmltables . '' ); + $mform->addElement('html', ' + + '); + $mform->addElement('static', 'mumie_multi_edit_deadline_error', ''); } /** @@ -483,6 +486,7 @@ public function set_data($data): void { $this->set_general_server_data($data, $mform); // This option must not be changed to avoid messing with grades in the database. $mform->updateElementAttr("mumie_complete_course", ["disabled" => "disabled"]); + $mform->updateElementAttr("multi_problem_selector_btn", ["disabled" => "disabled"]); $this->set_grade_data($data, $mform); parent::set_data($data); } @@ -580,18 +584,6 @@ private function set_general_data($data): void { } } - /** - * Called during validation. Indicates whether a module-specific completion rule is selected. - * - * @param array $data Input data (not yet validated) - * @return bool True if one or more rules is enabled, false if none are. - */ - public function completion_rule_enabled($data): bool { - $completionpasselement = $this->get_completion_rule_element_name('completionpass'); - return !empty($data[$completionpasselement]); - } - - /** * The decision regarding gradepools is final. We need to know whether we should disable the selection boxes. * diff --git a/styles.css b/styles.css index 2d404650..bacdc1f6 100644 --- a/styles.css +++ b/styles.css @@ -24,3 +24,14 @@ .mumie_table thead tr th { border-bottom: 1px solid black; } + +.mumie_multi_tasks_list { + margin: 0.3em 0 0 1.2em; +} + +#mumie_multi_tasks_summary { + margin: 0.5em 0; + padding: 0.5em; + background: #f0f0f0; + border-radius: 4px; +} diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 2e045503..b469529a 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -43,7 +43,6 @@ public function create_instance($record = null, ?array $options = null) { 'mumie_course' => '', 'lastsync' => 0, 'points' => 100, - 'completionpass' => 0, 'use_hashed_id' => 1, 'duedate' => 0, 'privategradepool' => 1, diff --git a/version.php b/version.php index d7e9063f..c46f952d 100644 --- a/version.php +++ b/version.php @@ -25,11 +25,11 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2025070300; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2026062900; // The current module version (Date: YYYYMMDDXX). $plugin->component = 'mod_mumie'; // Full name of the plugin (used for diagnostics). -$plugin->requires = 2022112800; // 4.1 LTS -$plugin->release = "v1.9.1"; +$plugin->requires = 2023100900; // 4.3 +$plugin->release = "v1.10.0"; $plugin->maturity = MATURITY_STABLE; $plugin->dependencies = [ - 'auth_mumie' => 2025051500, + 'auth_mumie' => 2026062900, ];