From 70b0b7e41b3d2a0e3613fc9c40b63c586aa49751 Mon Sep 17 00:00:00 2001 From: PhMemmel <65113153+PhMemmel@users.noreply.github.com> Date: Tue, 7 May 2024 14:44:33 +0200 Subject: [PATCH] Allow other plugins to restrict sections with Hooks Using Moodle 4.4+ Hooks API. --- amd/build/checkboxmanager.min.js | 2 +- amd/build/checkboxmanager.min.js.map | 2 +- amd/build/massactionblock.min.js | 2 +- amd/build/massactionblock.min.js.map | 2 +- amd/src/checkboxmanager.js | 41 ++++----- amd/src/massactionblock.js | 6 +- block_massaction.php | 12 ++- classes/actions.php | 57 +++++++++--- classes/form/section_select_form.php | 13 ++- .../hook/filter_sections_different_course.php | 77 ++++++++++++++++ classes/hook/filter_sections_handler.php | 92 +++++++++++++++++++ classes/hook/filter_sections_same_course.php | 37 ++++++++ classes/massactionutils.php | 17 ---- classes/privacy/provider.php | 5 +- lang/en/block_massaction.php | 73 +++++++-------- templates/block_massaction.mustache | 4 +- tests/massaction_test.php | 2 +- version.php | 2 +- 18 files changed, 333 insertions(+), 113 deletions(-) create mode 100644 classes/hook/filter_sections_different_course.php create mode 100644 classes/hook/filter_sections_handler.php create mode 100644 classes/hook/filter_sections_same_course.php diff --git a/amd/build/checkboxmanager.min.js b/amd/build/checkboxmanager.min.js index b5b94e5..f4d96c5 100644 --- a/amd/build/checkboxmanager.min.js +++ b/amd/build/checkboxmanager.min.js @@ -7,6 +7,6 @@ define("block_massaction/checkboxmanager",["exports","core/templates","core/noti * @copyright 2022 ISB Bayern * @author Philipp Memmel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setSectionSelection=_exports.initCheckboxManager=_exports.getSelectedModIds=void 0,_templates=_interopRequireDefault(_templates),_events=_interopRequireDefault(_events);let localStateUpdating=!1,sectionsChanged=!1,sections=[],moduleNames=[];const sectionBoxes={};_exports.initCheckboxManager=sectionsRestricted=>{const courseEditor=(0,_courseeditor.getCurrentCourseEditor)(),eventsToListen_SECTION_UPDATED="section:updated",eventsToListen_CHANGE_FINISHED="transaction:end";courseEditor.stateManager.target.addEventListener(_events.default.stateChanged,(event=>{event.detail.action===eventsToListen_SECTION_UPDATED&&(sectionsChanged=!0),event.detail.action===eventsToListen_CHANGE_FINISHED&&rebuildLocalState(sectionsRestricted)})),sectionsChanged=!0,rebuildLocalState(sectionsRestricted)};const rebuildLocalState=sectionsRestricted=>{if(localStateUpdating)return;localStateUpdating=!0;const courseEditor=(0,_courseeditor.getCurrentCourseEditor)();sections=[];for(const prop of Object.getOwnPropertyNames(sectionBoxes))delete sectionBoxes[prop];sections=[...courseEditor.stateManager.state.section.values()].sort(((a,b)=>a.number>b.number?1:-1)),moduleNames=[...courseEditor.stateManager.state.cm.values()];const sectionsUnfiltered=sections;sections=filterVisibleSections(sections),updateSelectionAndMoveToDropdowns(sections,sectionsUnfiltered,sectionsRestricted),addCheckboxesToDataStructure(),localStateUpdating=!1};_exports.getSelectedModIds=()=>{const moduleIds=[];for(let sectionNumber in sectionBoxes)for(let i=0;i{const boxIds=[];if(void 0===sectionNumber||sectionNumber!==_massactionblock.constants.SECTION_SELECT_DESCRIPTION_VALUE){if(void 0!==sectionNumber&§ionNumber===_massactionblock.constants.SECTION_NUMBER_ALL_PLACEHOLDER)for(const sectionId in sectionBoxes)for(let j=0;jboxIds.push(box.boxId)));for(let i=0;i{sections.forEach((section=>{sectionBoxes[section.number]=[];const moduleIds=section.cmlist;if(moduleIds&&moduleIds.length>0&&""!==moduleIds[0]){moduleNames.filter((modinfo=>moduleIds.includes(modinfo.id.toString()))).forEach((modinfo=>{const boxId=_massactionblock.usedMoodleCssClasses.BOX_ID_PREFIX+modinfo.id.toString();sectionBoxes[section.number].push({moduleId:modinfo.id.toString(),boxId:boxId})}))}}))},filterVisibleSections=sections=>sections.filter((section=>0!==section.cmlist.length)).filter((section=>section.cmlist.every((moduleid=>null!==document.getElementById(_massactionblock.usedMoodleCssClasses.MODULE_ID_PREFIX+moduleid))))),updateSelectionAndMoveToDropdowns=(sections,sectionsUnfiltered,sectionsRestricted)=>{sectionsChanged?(_templates.default.renderForPromise("block_massaction/section_select",{sections:sectionsUnfiltered}).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode("#"+_massactionblock.cssIds.SECTION_SELECT,html,js),disableInvisibleAndEmptySections(sections),document.getElementById(_massactionblock.cssIds.SECTION_SELECT).addEventListener("change",(event=>setSectionSelection(!0,event.target.value)),!1),!0})).catch((ex=>(0,_notification.exception)(ex))),_templates.default.renderForPromise("block_massaction/moveto_select",{sections:sectionsUnfiltered}).then((_ref2=>{let{html:html,js:js}=_ref2;return _templates.default.replaceNode("#"+_massactionblock.cssIds.MOVETO_SELECT,html,js),disableRestrictedSections(_massactionblock.cssIds.MOVETO_SELECT,sectionsRestricted),!0})).catch((ex=>(0,_notification.exception)(ex))),_templates.default.renderForPromise("block_massaction/duplicateto_select",{sections:sectionsUnfiltered}).then((_ref3=>{let{html:html,js:js}=_ref3;return _templates.default.replaceNode("#"+_massactionblock.cssIds.DUPLICATETO_SELECT,html,js),disableRestrictedSections(_massactionblock.cssIds.DUPLICATETO_SELECT,sectionsRestricted),!0})).catch((ex=>(0,_notification.exception)(ex)))):disableInvisibleAndEmptySections(sections),sectionsChanged=!1},disableInvisibleAndEmptySections=sections=>{Array.prototype.forEach.call(document.getElementById(_massactionblock.cssIds.SECTION_SELECT).options,(option=>{option.value===_massactionblock.constants.SECTION_SELECT_DESCRIPTION_VALUE||sections.some((section=>parseInt(option.value)===section.number))?option.disabled=!1:option.disabled=!0}))},disableRestrictedSections=(elementId,sectionsRestricted)=>{null!==document.getElementById(elementId)&&Array.prototype.forEach.call(document.getElementById(elementId).options,(option=>{sectionsRestricted.includes(parseInt(option.value))?option.disabled=!0:option.disabled=!1}))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setSectionSelection=_exports.initCheckboxManager=_exports.getSelectedModIds=void 0,_templates=_interopRequireDefault(_templates),_events=_interopRequireDefault(_events);let localStateUpdating=!1,sectionsChanged=!1,sections=[],moduleNames=[];const sectionBoxes={};_exports.initCheckboxManager=()=>{const courseEditor=(0,_courseeditor.getCurrentCourseEditor)(),eventsToListen_SECTION_UPDATED="section:updated",eventsToListen_CHANGE_FINISHED="transaction:end";courseEditor.stateManager.target.addEventListener(_events.default.stateChanged,(event=>{event.detail.action===eventsToListen_SECTION_UPDATED&&(sectionsChanged=!0),event.detail.action===eventsToListen_CHANGE_FINISHED&&rebuildLocalState()})),sectionsChanged=!0,rebuildLocalState()};const rebuildLocalState=()=>{if(localStateUpdating)return;localStateUpdating=!0;const courseEditor=(0,_courseeditor.getCurrentCourseEditor)();sections=[];for(const prop of Object.getOwnPropertyNames(sectionBoxes))delete sectionBoxes[prop];sections=[...courseEditor.stateManager.state.section.values()].sort(((a,b)=>a.number>b.number?1:-1)),moduleNames=[...courseEditor.stateManager.state.cm.values()];const sectionsUnfiltered=sections;sections=filterVisibleSections(sections),updateSelectionAndMoveToDropdowns(sections,sectionsUnfiltered),addCheckboxesToDataStructure(),localStateUpdating=!1};_exports.getSelectedModIds=()=>{const moduleIds=[];for(let sectionNumber in sectionBoxes)for(let i=0;i{const boxIds=[];if(void 0===sectionNumber||sectionNumber!==_massactionblock.constants.SECTION_SELECT_DESCRIPTION_VALUE){if(void 0!==sectionNumber&§ionNumber===_massactionblock.constants.SECTION_NUMBER_ALL_PLACEHOLDER)for(const sectionId in sectionBoxes)for(let j=0;jboxIds.push(box.boxId)));for(let i=0;i{sections.forEach((section=>{sectionBoxes[section.number]=[];const moduleIds=section.cmlist;if(moduleIds&&moduleIds.length>0&&""!==moduleIds[0]){moduleNames.filter((modinfo=>moduleIds.includes(modinfo.id.toString()))).forEach((modinfo=>{const boxId=_massactionblock.usedMoodleCssClasses.BOX_ID_PREFIX+modinfo.id.toString();sectionBoxes[section.number].push({moduleId:modinfo.id.toString(),boxId:boxId})}))}}))},filterVisibleSections=sections=>sections.filter((section=>0!==section.cmlist.length)).filter((section=>section.cmlist.every((moduleid=>null!==document.getElementById(_massactionblock.usedMoodleCssClasses.MODULE_ID_PREFIX+moduleid))))),updateSelectionAndMoveToDropdowns=(sections,sectionsUnfiltered)=>{sectionsChanged?(_templates.default.renderForPromise("block_massaction/section_select",{sections:sectionsUnfiltered}).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode("#"+_massactionblock.cssIds.SECTION_SELECT,html,js),disableInvisibleAndEmptySections(sections),document.getElementById(_massactionblock.cssIds.SECTION_SELECT).addEventListener("change",(event=>setSectionSelection(!0,event.target.value)),!1),!0})).catch((ex=>(0,_notification.exception)(ex))),_templates.default.renderForPromise("block_massaction/moveto_select",{sections:sectionsUnfiltered}).then((_ref2=>{let{html:html,js:js}=_ref2;return _templates.default.replaceNode("#"+_massactionblock.cssIds.MOVETO_SELECT,html,js),disableUnavailableSections(_massactionblock.cssIds.MOVETO_SELECT),!0})).catch((ex=>(0,_notification.exception)(ex))),_templates.default.renderForPromise("block_massaction/duplicateto_select",{sections:sectionsUnfiltered}).then((_ref3=>{let{html:html,js:js}=_ref3;return _templates.default.replaceNode("#"+_massactionblock.cssIds.DUPLICATETO_SELECT,html,js),disableUnavailableSections(_massactionblock.cssIds.DUPLICATETO_SELECT),!0})).catch((ex=>(0,_notification.exception)(ex)))):disableInvisibleAndEmptySections(sections),sectionsChanged=!1},disableInvisibleAndEmptySections=sections=>{Array.prototype.forEach.call(document.getElementById(_massactionblock.cssIds.SECTION_SELECT).options,(option=>{option.value===_massactionblock.constants.SECTION_SELECT_DESCRIPTION_VALUE||sections.some((section=>parseInt(option.value)===section.number))?option.disabled=!1:option.disabled=!0}))},disableUnavailableSections=elementId=>{if(null!==document.getElementById(elementId)){const sectionsAvailableInfo=document.querySelector(_massactionblock.cssIds.SECTION_FILTER_DATA).dataset.availabletargetsections,sectionsAvailable=Array.prototype.map.call(sectionsAvailableInfo.split(","),(sectionnum=>parseInt(sectionnum)));Array.prototype.forEach.call(document.getElementById(elementId).options,(option=>{sectionsAvailable.includes(parseInt(option.value))?option.disabled=!1:option.disabled=!0}))}}})); //# sourceMappingURL=checkboxmanager.min.js.map \ No newline at end of file diff --git a/amd/build/checkboxmanager.min.js.map b/amd/build/checkboxmanager.min.js.map index a252db0..f4e4a96 100644 --- a/amd/build/checkboxmanager.min.js.map +++ b/amd/build/checkboxmanager.min.js.map @@ -1 +1 @@ -{"version":3,"file":"checkboxmanager.min.js","sources":["../src/checkboxmanager.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Checkbox manager amd module: Adds checkboxes to the activities for selecting and\n * generates a data structure of the activities and checkboxes.\n *\n * @module block_massaction/checkboxmanager\n * @copyright 2022 ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {exception as displayException} from 'core/notification';\nimport {cssIds, constants, usedMoodleCssClasses} from './massactionblock';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport events from 'core_course/events';\n\nlet localStateUpdating = false;\nlet sectionsChanged = false;\nlet sections = [];\nlet moduleNames = [];\n\n/* A registry of checkbox IDs, of the format:\n * 'section_number' => [{'moduleId' : ,\n * 'boxId' : }]\n */\nconst sectionBoxes = {};\n\n/**\n * The checkbox manager takes a given 'sections' data structure object and inserts a checkbox for each of the given\n * course modules in this data object into the DOM.\n * The checkbox manager returns another data object containing the ids of the added checkboxes.\n * @param {[]} sectionsRestricted the sections which are restrected for the course format\n */\nexport const initCheckboxManager = sectionsRestricted => {\n const courseEditor = getCurrentCourseEditor();\n\n const eventsToListen = {\n SECTION_UPDATED: 'section:updated',\n CHANGE_FINISHED: 'transaction:end'\n };\n\n courseEditor.stateManager.target.addEventListener(events.stateChanged, (event) => {\n if (event.detail.action === eventsToListen.SECTION_UPDATED) {\n // Listen to section updated events. We do not want to immediately react to the event, but wait for\n // everything to finish updating.\n sectionsChanged = true;\n }\n if (event.detail.action === eventsToListen.CHANGE_FINISHED) {\n // Before every change to the state there is a transaction:start event. After the change is being commited,\n // we receive an transaction:end event. That is the point we want to react to changes of the state.\n rebuildLocalState(sectionsRestricted);\n }\n });\n // Trigger rendering of sections dropdowns a first time.\n sectionsChanged = true;\n // Get initial state.\n rebuildLocalState(sectionsRestricted);\n};\n\n/**\n * This method rebuilds the local state maintained in this module based on the course editor state.\n *\n * It will be called whenever a change to the courseeditor state is being detected.\n * @param {[]} sectionsRestricted the sections which are restrected for the course format\n */\nconst rebuildLocalState = sectionsRestricted => {\n if (localStateUpdating) {\n return;\n }\n localStateUpdating = true;\n const courseEditor = getCurrentCourseEditor();\n\n // First we rebuild our data structures depending on the course editor state.\n sections = [];\n for (const prop of Object.getOwnPropertyNames(sectionBoxes)) {\n delete sectionBoxes[prop];\n }\n // The section map object is being sorted by section id. We have to sort after order in this course.\n sections = [...courseEditor.stateManager.state.section.values()].sort((a, b) => a.number > b.number ? 1 : -1);\n moduleNames = [...courseEditor.stateManager.state.cm.values()];\n\n // Now we use the new information to rebuild dropdowns and re-apply checkboxes.\n const sectionsUnfiltered = sections;\n sections = filterVisibleSections(sections);\n updateSelectionAndMoveToDropdowns(sections, sectionsUnfiltered, sectionsRestricted);\n addCheckboxesToDataStructure();\n localStateUpdating = false;\n};\n\n/**\n * Returns the currently selected module ids.\n *\n * @returns {[]} Array of module ids currently being selected\n */\nexport const getSelectedModIds = () => {\n const moduleIds = [];\n for (let sectionNumber in sectionBoxes) {\n for (let i = 0; i < sectionBoxes[sectionNumber].length; i++) {\n const checkbox = document.getElementById(sectionBoxes[sectionNumber][i].boxId);\n if (checkbox.checked) {\n moduleIds.push(sectionBoxes[sectionNumber][i].moduleId);\n }\n }\n }\n return moduleIds;\n};\n\n/**\n * Select all module checkboxes in section(s).\n *\n * @param {boolean} value the checked value to set the checkboxes to\n * @param {string} sectionNumber the section number of the section which all modules should be checked/unchecked. Use \"all\" to\n * select/deselect modules in all sections.\n */\nexport const setSectionSelection = (value, sectionNumber) => {\n const boxIds = [];\n\n if (typeof sectionNumber !== 'undefined' && sectionNumber === constants.SECTION_SELECT_DESCRIPTION_VALUE) {\n // Description placeholder has been selected, do nothing.\n return;\n } else if (typeof sectionNumber !== 'undefined' && sectionNumber === constants.SECTION_NUMBER_ALL_PLACEHOLDER) {\n // See if we are toggling all sections.\n for (const sectionId in sectionBoxes) {\n for (let j = 0; j < sectionBoxes[sectionId].length; j++) {\n boxIds.push(sectionBoxes[sectionId][j].boxId);\n }\n }\n } else {\n // We select all boxes of the given section.\n sectionBoxes[sectionNumber].forEach(box => boxIds.push(box.boxId));\n }\n // Un/check the boxes.\n for (let i = 0; i < boxIds.length; i++) {\n document.getElementById(boxIds[i]).checked = value;\n }\n // Reset dropdown to standard placeholder so we trigger a change event when selecting a section, then deselecting\n // everything and again select the same section.\n document.getElementById(cssIds.SECTION_SELECT).value = constants.SECTION_SELECT_DESCRIPTION_VALUE;\n};\n\n/**\n * Scan all available checkboxes and add them to the data structure.\n */\nconst addCheckboxesToDataStructure = () => {\n sections.forEach(section => {\n sectionBoxes[section.number] = [];\n const moduleIds = section.cmlist;\n if (moduleIds && moduleIds.length > 0 && moduleIds[0] !== '') {\n const moduleNamesFiltered = moduleNames.filter(modinfo => moduleIds.includes(modinfo.id.toString()));\n moduleNamesFiltered.forEach(modinfo => {\n // Checkbox should already be created by moodle massactions. Just add it to our data structure.\n const boxId = usedMoodleCssClasses.BOX_ID_PREFIX + modinfo.id.toString();\n sectionBoxes[section.number].push({\n 'moduleId': modinfo.id.toString(),\n 'boxId': boxId,\n });\n });\n }\n });\n};\n\n/**\n * Filter the sections data object depending on the visibility of the course modules contained in\n * the data object. This is neccessary, because some course formats only show specific section(s)\n * in editing mode.\n *\n * @param {[]} sections the sections data object\n * @returns {[]} the filtered sections object\n */\nconst filterVisibleSections = (sections) => {\n // Filter all sections with modules which no checkboxes have been created for.\n // This case should only occur in course formats where some sections are hidden.\n return sections.filter(section => section.cmlist.length !== 0)\n .filter(section => section.cmlist\n .every(moduleid => document.getElementById(usedMoodleCssClasses.MODULE_ID_PREFIX + moduleid) !== null));\n};\n\n/**\n * Update the selection, moveto and duplicateto dropdowns of the massaction block according to the\n * previously filtered sections.\n *\n * This method also has to be called whenever there is a module change event (moving around, adding file by Drag&Drop etc.).\n *\n * @param {[]} sections the sections object filtered before by {@link filterVisibleSections}\n * @param {[]} sectionsUnfiltered the same data object as 'sections', but still containing all sections\n * @param {[]} sectionsRestricted the sections which are restrected for the course format\n * no matter if containing modules or are visible in the current course format or not\n */\nconst updateSelectionAndMoveToDropdowns = (sections, sectionsUnfiltered, sectionsRestricted) => {\n if (sectionsChanged) {\n Templates.renderForPromise('block_massaction/section_select', {'sections': sectionsUnfiltered})\n .then(({html, js}) => {\n Templates.replaceNode('#' + cssIds.SECTION_SELECT, html, js);\n disableInvisibleAndEmptySections(sections);\n // Re-register event listener.\n document.getElementById(cssIds.SECTION_SELECT).addEventListener('change',\n (event) => setSectionSelection(true, event.target.value), false);\n return true;\n })\n .catch(ex => displayException(ex));\n\n Templates.renderForPromise('block_massaction/moveto_select', {'sections': sectionsUnfiltered})\n .then(({html, js}) => {\n Templates.replaceNode('#' + cssIds.MOVETO_SELECT, html, js);\n disableRestrictedSections(cssIds.MOVETO_SELECT, sectionsRestricted);\n return true;\n })\n .catch(ex => displayException(ex));\n\n Templates.renderForPromise('block_massaction/duplicateto_select', {'sections': sectionsUnfiltered})\n .then(({html, js}) => {\n Templates.replaceNode('#' + cssIds.DUPLICATETO_SELECT, html, js);\n disableRestrictedSections(cssIds.DUPLICATETO_SELECT, sectionsRestricted);\n return true;\n })\n .catch(ex => displayException(ex));\n } else {\n // If there has not been an event about a section change we do not have to rebuild the sections dropdowns.\n // However there is a chance an section is being emptied or not empty anymore due to drag&dropping of modules.\n // So we have to recalculate if we have to enable/disable the sections.\n disableInvisibleAndEmptySections(sections);\n }\n // Reset the flag.\n sectionsChanged = false;\n};\n\n/**\n * Sets the disabled/enabled status of sections in the section select dropdown:\n * Enabled if section is visible and contains modules.\n * Disabled if section is not visible or doesn't contain any modules.\n *\n * @param {[]} sections the section data structure\n */\nconst disableInvisibleAndEmptySections = (sections) => {\n Array.prototype.forEach.call(document.getElementById(cssIds.SECTION_SELECT).options, option => {\n // Disable every element which doesn't have a visible section, except the placeholder ('description').\n if (option.value !== constants.SECTION_SELECT_DESCRIPTION_VALUE\n && !sections.some(section => parseInt(option.value) === section.number)) {\n option.disabled = true;\n } else {\n option.disabled = false;\n }\n });\n};\n\n/**\n * Sets the disabled/enabled status of sections in the section select dropdown\n * by sectionsRestricted param\n *\n * @param {string} elementId elementId to apply the restriction\n * @param {[]} sectionsRestricted the sections which are restrected for the course format\n */\nconst disableRestrictedSections = (elementId, sectionsRestricted) => {\n if (document.getElementById(elementId) !== null) {\n Array.prototype.forEach.call(document.getElementById(elementId).options, option => {\n // Disable every element which is in the sectionsRestricted list.\n if (sectionsRestricted.includes(parseInt(option.value))) {\n option.disabled = true;\n } else {\n option.disabled = false;\n }\n });\n }\n};\n"],"names":["localStateUpdating","sectionsChanged","sections","moduleNames","sectionBoxes","sectionsRestricted","courseEditor","eventsToListen","stateManager","target","addEventListener","events","stateChanged","event","detail","action","rebuildLocalState","prop","Object","getOwnPropertyNames","state","section","values","sort","a","b","number","cm","sectionsUnfiltered","filterVisibleSections","updateSelectionAndMoveToDropdowns","addCheckboxesToDataStructure","moduleIds","sectionNumber","i","length","document","getElementById","boxId","checked","push","moduleId","setSectionSelection","value","boxIds","constants","SECTION_SELECT_DESCRIPTION_VALUE","SECTION_NUMBER_ALL_PLACEHOLDER","sectionId","j","forEach","box","cssIds","SECTION_SELECT","cmlist","filter","modinfo","includes","id","toString","usedMoodleCssClasses","BOX_ID_PREFIX","every","moduleid","MODULE_ID_PREFIX","renderForPromise","then","_ref","html","js","replaceNode","disableInvisibleAndEmptySections","catch","ex","_ref2","MOVETO_SELECT","disableRestrictedSections","_ref3","DUPLICATETO_SELECT","Array","prototype","call","options","option","some","parseInt","disabled","elementId"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"checkboxmanager.min.js","sources":["../src/checkboxmanager.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Checkbox manager amd module: Adds checkboxes to the activities for selecting and\n * generates a data structure of the activities and checkboxes.\n *\n * @module block_massaction/checkboxmanager\n * @copyright 2022 ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {exception as displayException} from 'core/notification';\nimport {cssIds, constants, usedMoodleCssClasses} from './massactionblock';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport events from 'core_course/events';\n\nlet localStateUpdating = false;\nlet sectionsChanged = false;\nlet sections = [];\nlet moduleNames = [];\n\n/* A registry of checkbox IDs, of the format:\n * 'section_number' => [{'moduleId' : ,\n * 'boxId' : }]\n */\nconst sectionBoxes = {};\n\n/**\n * The checkbox manager takes a given 'sections' data structure object and inserts a checkbox for each of the given\n * course modules in this data object into the DOM.\n * The checkbox manager returns another data object containing the ids of the added checkboxes.\n */\nexport const initCheckboxManager = () => {\n const courseEditor = getCurrentCourseEditor();\n\n const eventsToListen = {\n SECTION_UPDATED: 'section:updated',\n CHANGE_FINISHED: 'transaction:end'\n };\n\n courseEditor.stateManager.target.addEventListener(events.stateChanged, (event) => {\n if (event.detail.action === eventsToListen.SECTION_UPDATED) {\n // Listen to section updated events. We do not want to immediately react to the event, but wait for\n // everything to finish updating.\n sectionsChanged = true;\n }\n if (event.detail.action === eventsToListen.CHANGE_FINISHED) {\n // Before every change to the state there is a transaction:start event. After the change is being commited,\n // we receive an transaction:end event. That is the point we want to react to changes of the state.\n rebuildLocalState();\n }\n });\n // Trigger rendering of sections dropdowns a first time.\n sectionsChanged = true;\n // Get initial state.\n rebuildLocalState();\n};\n\n/**\n * This method rebuilds the local state maintained in this module based on the course editor state.\n *\n * It will be called whenever a change to the courseeditor state is being detected.\n */\nconst rebuildLocalState = () => {\n if (localStateUpdating) {\n return;\n }\n localStateUpdating = true;\n const courseEditor = getCurrentCourseEditor();\n\n // First we rebuild our data structures depending on the course editor state.\n sections = [];\n for (const prop of Object.getOwnPropertyNames(sectionBoxes)) {\n delete sectionBoxes[prop];\n }\n // The section map object is being sorted by section id. We have to sort after order in this course.\n sections = [...courseEditor.stateManager.state.section.values()].sort((a, b) => a.number > b.number ? 1 : -1);\n moduleNames = [...courseEditor.stateManager.state.cm.values()];\n\n // Now we use the new information to rebuild dropdowns and re-apply checkboxes.\n const sectionsUnfiltered = sections;\n sections = filterVisibleSections(sections);\n updateSelectionAndMoveToDropdowns(sections, sectionsUnfiltered);\n addCheckboxesToDataStructure();\n localStateUpdating = false;\n};\n\n/**\n * Returns the currently selected module ids.\n *\n * @returns {[]} Array of module ids currently being selected\n */\nexport const getSelectedModIds = () => {\n const moduleIds = [];\n for (let sectionNumber in sectionBoxes) {\n for (let i = 0; i < sectionBoxes[sectionNumber].length; i++) {\n const checkbox = document.getElementById(sectionBoxes[sectionNumber][i].boxId);\n if (checkbox.checked) {\n moduleIds.push(sectionBoxes[sectionNumber][i].moduleId);\n }\n }\n }\n return moduleIds;\n};\n\n/**\n * Select all module checkboxes in section(s).\n *\n * @param {boolean} value the checked value to set the checkboxes to\n * @param {string} sectionNumber the section number of the section which all modules should be checked/unchecked. Use \"all\" to\n * select/deselect modules in all sections.\n */\nexport const setSectionSelection = (value, sectionNumber) => {\n const boxIds = [];\n\n if (typeof sectionNumber !== 'undefined' && sectionNumber === constants.SECTION_SELECT_DESCRIPTION_VALUE) {\n // Description placeholder has been selected, do nothing.\n return;\n } else if (typeof sectionNumber !== 'undefined' && sectionNumber === constants.SECTION_NUMBER_ALL_PLACEHOLDER) {\n // See if we are toggling all sections.\n for (const sectionId in sectionBoxes) {\n for (let j = 0; j < sectionBoxes[sectionId].length; j++) {\n boxIds.push(sectionBoxes[sectionId][j].boxId);\n }\n }\n } else {\n // We select all boxes of the given section.\n sectionBoxes[sectionNumber].forEach(box => boxIds.push(box.boxId));\n }\n // Un/check the boxes.\n for (let i = 0; i < boxIds.length; i++) {\n document.getElementById(boxIds[i]).checked = value;\n }\n // Reset dropdown to standard placeholder so we trigger a change event when selecting a section, then deselecting\n // everything and again select the same section.\n document.getElementById(cssIds.SECTION_SELECT).value = constants.SECTION_SELECT_DESCRIPTION_VALUE;\n};\n\n/**\n * Scan all available checkboxes and add them to the data structure.\n */\nconst addCheckboxesToDataStructure = () => {\n sections.forEach(section => {\n sectionBoxes[section.number] = [];\n const moduleIds = section.cmlist;\n if (moduleIds && moduleIds.length > 0 && moduleIds[0] !== '') {\n const moduleNamesFiltered = moduleNames.filter(modinfo => moduleIds.includes(modinfo.id.toString()));\n moduleNamesFiltered.forEach(modinfo => {\n // Checkbox should already be created by moodle massactions. Just add it to our data structure.\n const boxId = usedMoodleCssClasses.BOX_ID_PREFIX + modinfo.id.toString();\n sectionBoxes[section.number].push({\n 'moduleId': modinfo.id.toString(),\n 'boxId': boxId,\n });\n });\n }\n });\n};\n\n/**\n * Filter the sections data object depending on the visibility of the course modules contained in\n * the data object. This is necessary, because some course formats only show specific section(s)\n * in editing mode.\n *\n * @param {[]} sections the sections data object\n * @returns {[]} the filtered sections object\n */\nconst filterVisibleSections = (sections) => {\n // Filter all sections with modules which no checkboxes have been created for.\n // This case should only occur in course formats where some sections are hidden.\n return sections.filter(section => section.cmlist.length !== 0)\n .filter(section => section.cmlist\n .every(moduleid => document.getElementById(usedMoodleCssClasses.MODULE_ID_PREFIX + moduleid) !== null));\n};\n\n/**\n * Update the selection, moveto and duplicateto dropdowns of the massaction block according to the\n * previously filtered sections.\n *\n * This method also has to be called whenever there is a module change event (moving around, adding file by Drag&Drop etc.).\n *\n * @param {[]} sections the sections object filtered before by {@link filterVisibleSections}\n * @param {[]} sectionsUnfiltered the same data object as 'sections', but still containing all sections\n */\nconst updateSelectionAndMoveToDropdowns = (sections, sectionsUnfiltered) => {\n if (sectionsChanged) {\n Templates.renderForPromise('block_massaction/section_select', {'sections': sectionsUnfiltered})\n .then(({html, js}) => {\n Templates.replaceNode('#' + cssIds.SECTION_SELECT, html, js);\n disableInvisibleAndEmptySections(sections);\n // Re-register event listener.\n document.getElementById(cssIds.SECTION_SELECT).addEventListener('change',\n (event) => setSectionSelection(true, event.target.value), false);\n return true;\n })\n .catch(ex => displayException(ex));\n\n Templates.renderForPromise('block_massaction/moveto_select', {'sections': sectionsUnfiltered})\n .then(({html, js}) => {\n Templates.replaceNode('#' + cssIds.MOVETO_SELECT, html, js);\n disableUnavailableSections(cssIds.MOVETO_SELECT);\n return true;\n })\n .catch(ex => displayException(ex));\n\n Templates.renderForPromise('block_massaction/duplicateto_select', {'sections': sectionsUnfiltered})\n .then(({html, js}) => {\n Templates.replaceNode('#' + cssIds.DUPLICATETO_SELECT, html, js);\n disableUnavailableSections(cssIds.DUPLICATETO_SELECT);\n return true;\n })\n .catch(ex => displayException(ex));\n } else {\n // If there has not been an event about a section change we do not have to rebuild the sections dropdowns.\n // However, there is a chance a section is being emptied or not empty anymore due to drag&dropping of modules.\n // So we have to recalculate if we have to enable/disable the sections.\n disableInvisibleAndEmptySections(sections);\n }\n // Reset the flag.\n sectionsChanged = false;\n};\n\n/**\n * Sets the disabled/enabled status of sections in the section select dropdown:\n * Enabled if section is visible and contains modules.\n * Disabled if section is not visible or doesn't contain any modules.\n *\n * @param {[]} sections the section data structure\n */\nconst disableInvisibleAndEmptySections = (sections) => {\n Array.prototype.forEach.call(document.getElementById(cssIds.SECTION_SELECT).options, option => {\n // Disable every element which doesn't have a visible section, except the placeholder ('description').\n if (option.value !== constants.SECTION_SELECT_DESCRIPTION_VALUE\n && !sections.some(section => parseInt(option.value) === section.number)) {\n option.disabled = true;\n } else {\n option.disabled = false;\n }\n });\n};\n\n/**\n * Sets the disabled/enabled status of sections in the section select dropdown:\n * Disabled if the section is not available due to some restrictions in block_massaction itself (provided by hooks).\n *\n * @param {string} elementId elementId to apply the restriction\n */\nconst disableUnavailableSections = (elementId) => {\n if (document.getElementById(elementId) !== null) {\n const sectionsAvailableInfo = document.querySelector(cssIds.SECTION_FILTER_DATA).dataset.availabletargetsections;\n const sectionsAvailable = Array.prototype.map.call(sectionsAvailableInfo.split(','), (sectionnum) => parseInt(sectionnum));\n Array.prototype.forEach.call(document.getElementById(elementId).options, option => {\n // Disable every element which is not in the sectionsAvailable list.\n if (sectionsAvailable.includes(parseInt(option.value))) {\n option.disabled = false;\n } else {\n option.disabled = true;\n }\n });\n }\n};\n"],"names":["localStateUpdating","sectionsChanged","sections","moduleNames","sectionBoxes","courseEditor","eventsToListen","stateManager","target","addEventListener","events","stateChanged","event","detail","action","rebuildLocalState","prop","Object","getOwnPropertyNames","state","section","values","sort","a","b","number","cm","sectionsUnfiltered","filterVisibleSections","updateSelectionAndMoveToDropdowns","addCheckboxesToDataStructure","moduleIds","sectionNumber","i","length","document","getElementById","boxId","checked","push","moduleId","setSectionSelection","value","boxIds","constants","SECTION_SELECT_DESCRIPTION_VALUE","SECTION_NUMBER_ALL_PLACEHOLDER","sectionId","j","forEach","box","cssIds","SECTION_SELECT","cmlist","filter","modinfo","includes","id","toString","usedMoodleCssClasses","BOX_ID_PREFIX","every","moduleid","MODULE_ID_PREFIX","renderForPromise","then","_ref","html","js","replaceNode","disableInvisibleAndEmptySections","catch","ex","_ref2","MOVETO_SELECT","disableUnavailableSections","_ref3","DUPLICATETO_SELECT","Array","prototype","call","options","option","some","parseInt","disabled","elementId","sectionsAvailableInfo","querySelector","SECTION_FILTER_DATA","dataset","availabletargetsections","sectionsAvailable","map","split","sectionnum"],"mappings":";;;;;;;;;mPA+BIA,oBAAqB,EACrBC,iBAAkB,EAClBC,SAAW,GACXC,YAAc,SAMZC,aAAe,gCAOc,WACzBC,cAAe,0CAEfC,+BACe,kBADfA,+BAEe,kBAGrBD,aAAaE,aAAaC,OAAOC,iBAAiBC,gBAAOC,cAAeC,QAChEA,MAAMC,OAAOC,SAAWR,iCAGxBL,iBAAkB,GAElBW,MAAMC,OAAOC,SAAWR,gCAGxBS,uBAIRd,iBAAkB,EAElBc,2BAQEA,kBAAoB,QAClBf,0BAGJA,oBAAqB,QACfK,cAAe,0CAGrBH,SAAW,OACN,MAAMc,QAAQC,OAAOC,oBAAoBd,qBACnCA,aAAaY,MAGxBd,SAAW,IAAIG,aAAaE,aAAaY,MAAMC,QAAQC,UAAUC,MAAK,CAACC,EAAGC,IAAMD,EAAEE,OAASD,EAAEC,OAAS,GAAK,IAC3GtB,YAAc,IAAIE,aAAaE,aAAaY,MAAMO,GAAGL,gBAG/CM,mBAAqBzB,SAC3BA,SAAW0B,sBAAsB1B,UACjC2B,kCAAkC3B,SAAUyB,oBAC5CG,+BACA9B,oBAAqB,8BAQQ,WACvB+B,UAAY,OACb,IAAIC,iBAAiB5B,iBACjB,IAAI6B,EAAI,EAAGA,EAAI7B,aAAa4B,eAAeE,OAAQD,IAAK,CACxCE,SAASC,eAAehC,aAAa4B,eAAeC,GAAGI,OAC3DC,SACTP,UAAUQ,KAAKnC,aAAa4B,eAAeC,GAAGO,iBAInDT,iBAUEU,oBAAsB,CAACC,MAAOV,uBACjCW,OAAS,WAEc,IAAlBX,eAAiCA,gBAAkBY,2BAAUC,kCAGjE,QAA6B,IAAlBb,eAAiCA,gBAAkBY,2BAAUE,mCAEtE,MAAMC,aAAa3C,iBACf,IAAI4C,EAAI,EAAGA,EAAI5C,aAAa2C,WAAWb,OAAQc,IAChDL,OAAOJ,KAAKnC,aAAa2C,WAAWC,GAAGX,YAK/CjC,aAAa4B,eAAeiB,SAAQC,KAAOP,OAAOJ,KAAKW,IAAIb,aAG1D,IAAIJ,EAAI,EAAGA,EAAIU,OAAOT,OAAQD,IAC/BE,SAASC,eAAeO,OAAOV,IAAIK,QAAUI,MAIjDP,SAASC,eAAee,wBAAOC,gBAAgBV,MAAQE,2BAAUC,0FAM/Df,6BAA+B,KACjC5B,SAAS+C,SAAQ7B,UACbhB,aAAagB,QAAQK,QAAU,SACzBM,UAAYX,QAAQiC,UACtBtB,WAAaA,UAAUG,OAAS,GAAsB,KAAjBH,UAAU,GAAW,CAC9B5B,YAAYmD,QAAOC,SAAWxB,UAAUyB,SAASD,QAAQE,GAAGC,cACpET,SAAQM,gBAElBlB,MAAQsB,sCAAqBC,cAAgBL,QAAQE,GAAGC,WAC9DtD,aAAagB,QAAQK,QAAQc,KAAK,UAClBgB,QAAQE,GAAGC,iBACdrB,gBAevBT,sBAAyB1B,UAGpBA,SAASoD,QAAOlC,SAAqC,IAA1BA,QAAQiC,OAAOnB,SAC5CoB,QAAOlC,SAAWA,QAAQiC,OACtBQ,OAAMC,UAA0F,OAA9E3B,SAASC,eAAeuB,sCAAqBI,iBAAmBD,cAYzFjC,kCAAoC,CAAC3B,SAAUyB,sBAC7C1B,oCACU+D,iBAAiB,kCAAmC,UAAarC,qBACtEsC,MAAKC,WAACC,KAACA,KAADC,GAAOA,mCACAC,YAAY,IAAMlB,wBAAOC,eAAgBe,KAAMC,IACzDE,iCAAiCpE,UAEjCiC,SAASC,eAAee,wBAAOC,gBAAgB3C,iBAAiB,UAC3DG,OAAU6B,qBAAoB,EAAM7B,MAAMJ,OAAOkC,SAAQ,IACvD,KAEV6B,OAAMC,KAAM,2BAAiBA,yBAExBR,iBAAiB,iCAAkC,UAAarC,qBACrEsC,MAAKQ,YAACN,KAACA,KAADC,GAAOA,oCACAC,YAAY,IAAMlB,wBAAOuB,cAAeP,KAAMC,IACxDO,2BAA2BxB,wBAAOuB,gBAC3B,KAEVH,OAAMC,KAAM,2BAAiBA,yBAExBR,iBAAiB,sCAAuC,UAAarC,qBAC1EsC,MAAKW,YAACT,KAACA,KAADC,GAAOA,oCACAC,YAAY,IAAMlB,wBAAO0B,mBAAoBV,KAAMC,IAC7DO,2BAA2BxB,wBAAO0B,qBAC3B,KAEVN,OAAMC,KAAM,2BAAiBA,OAKlCF,iCAAiCpE,UAGrCD,iBAAkB,GAUhBqE,iCAAoCpE,WACtC4E,MAAMC,UAAU9B,QAAQ+B,KAAK7C,SAASC,eAAee,wBAAOC,gBAAgB6B,SAASC,SAE7EA,OAAOxC,QAAUE,2BAAUC,kCACnB3C,SAASiF,MAAK/D,SAAWgE,SAASF,OAAOxC,SAAWtB,QAAQK,SAGpEyD,OAAOG,UAAW,EAFlBH,OAAOG,UAAW,MAaxBV,2BAA8BW,eACW,OAAvCnD,SAASC,eAAekD,WAAqB,OACvCC,sBAAwBpD,SAASqD,cAAcrC,wBAAOsC,qBAAqBC,QAAQC,wBACnFC,kBAAoBd,MAAMC,UAAUc,IAAIb,KAAKO,sBAAsBO,MAAM,MAAOC,YAAeX,SAASW,cAC9GjB,MAAMC,UAAU9B,QAAQ+B,KAAK7C,SAASC,eAAekD,WAAWL,SAASC,SAEjEU,kBAAkBpC,SAAS4B,SAASF,OAAOxC,QAC3CwC,OAAOG,UAAW,EAElBH,OAAOG,UAAW"} \ No newline at end of file diff --git a/amd/build/massactionblock.min.js b/amd/build/massactionblock.min.js index 665fde2..d430460 100644 --- a/amd/build/massactionblock.min.js +++ b/amd/build/massactionblock.min.js @@ -6,6 +6,6 @@ define("block_massaction/massactionblock",["exports","block_massaction/checkboxm * @copyright 2022 ISB Bayern * @author Philipp Memmel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.usedMoodleCssClasses=_exports.init=_exports.cssIds=_exports.constants=void 0,checkboxmanager=_interopRequireWildcard(checkboxmanager),Str=_interopRequireWildcard(Str),_log=_interopRequireDefault(_log),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_events=_interopRequireDefault(_events);_exports.usedMoodleCssClasses={ACTIVITY_ITEM:".activity-item",SECTION_NAME:"sectionname",MODULE_ID_PREFIX:"module-",BOX_ID_PREFIX:"cmCheckbox"};const cssIds={BLOCK_CONTENT:"block-massaction",BULK_EDITING_DISABLED:"block-massaction-bulk-editing-disabled",SELECT_ALL_LINK:"block-massaction-control-selectall",DESELECT_ALL_LINK:"block-massaction-control-deselectall",HIDE_LINK:"block-massaction-action-hide",SHOW_LINK:"block-massaction-action-show",MAKE_AVAILABLE_LINK:"block-massaction-action-makeavailable",DUPLICATE_LINK:"block-massaction-action-duplicate",DELETE_LINK:"block-massaction-action-delete",SHOW_DESCRIPTION_LINK:"block-massaction-action-showdescription",HIDE_DESCRIPTION_LINK:"block-massaction-action-hidedescription",CONTENT_CHANGED_NOTIFICATION_LINK:"block-massaction-action-contentchangednotification",MOVELEFT_LINK:"block-massaction-action-moveleft",MOVERIGHT_LINK:"block-massaction-action-moveright",MOVETO_ICON_LINK:"block-massaction-action-moveto",DUPLICATETO_ICON_LINK:"block-massaction-action-duplicateto",DUPLICATE_TO_COURSE_ICON_LINK:"block-massaction-action-duplicatetocourse",SECTION_SELECT:"block-massaction-control-section-list-select",MOVETO_SELECT:"block-massaction-control-section-list-moveto",DUPLICATETO_SELECT:"block-massaction-control-section-list-duplicateto",HIDDEN_FIELD_REQUEST_INFORMATION:"block-massaction-control-request",ACTION_FORM:"block-massaction-control-form"};_exports.cssIds=cssIds;const constants={SECTION_SELECT_DESCRIPTION_VALUE:"description",SECTION_NUMBER_ALL_PLACEHOLDER:"all"};_exports.constants=constants;const actions_HIDE="hide",actions_SHOW="show",actions_MAKE_AVAILABLE="makeavailable",actions_DUPLICATE="duplicate",actions_DELETE="delete",actions_SHOW_DESCRIPTION="showdescription",actions_HIDE_DESCRIPTION="hidedescription",actions_MOVE_LEFT="moveleft",actions_MOVE_RIGHT="moveright",actions_CONTENT_CHANGED_NOTIFICATION="contentchangednotification",actions_MOVE_TO="moveto",actions_DUPLICATE_TO="duplicateto",actions_DUPLICATE_TO_COURSE="duplicatetocourse";_exports.init=async sectionsRestricted=>{var _document$getElementB3,_document$getElementB4,_document$getElementB5,_document$getElementB6,_document$getElementB7,_document$getElementB8,_document$getElementB9,_document$getElementB10,_document$getElementB11,_document$getElementB12,_document$getElementB13,_document$getElementB14,_document$getElementB15,_document$getElementB16,_document$getElementB17;const pendingPromise=new _pending.default("block_massaction/init"),editor=(0,_courseeditor.getCurrentCourseEditor)();editor.stateManager.getInitialPromise().then((()=>{checkboxmanager.initCheckboxManager(sectionsRestricted),editor.stateManager.target.addEventListener(_events.default.stateChanged,(event=>{var _document$getElementB,_document$getElementB2;"bulk.enabled:updated"===event.detail.action&&(null===(_document$getElementB=document.getElementById(cssIds.BLOCK_CONTENT))||void 0===_document$getElementB||_document$getElementB.classList.toggle("d-none"),null===(_document$getElementB2=document.getElementById(cssIds.BULK_EDITING_DISABLED))||void 0===_document$getElementB2||_document$getElementB2.classList.toggle("d-none"))}));const enableBulkButton=document.getElementById("block-massaction-enable-bulk-editing");return enableBulkButton.disabled=!1,null==enableBulkButton||enableBulkButton.addEventListener("click",(()=>editor.dispatch("bulkEnable",!0))),!0})).catch((error=>_log.default.debug(error))),null===(_document$getElementB3=document.getElementById(cssIds.SELECT_ALL_LINK))||void 0===_document$getElementB3||_document$getElementB3.addEventListener("click",(()=>checkboxmanager.setSectionSelection(!0,constants.SECTION_NUMBER_ALL_PLACEHOLDER)),!1),null===(_document$getElementB4=document.getElementById(cssIds.DESELECT_ALL_LINK))||void 0===_document$getElementB4||_document$getElementB4.addEventListener("click",(()=>checkboxmanager.setSectionSelection(!1,constants.SECTION_NUMBER_ALL_PLACEHOLDER)),!1),null===(_document$getElementB5=document.getElementById(cssIds.HIDE_LINK))||void 0===_document$getElementB5||_document$getElementB5.addEventListener("click",(()=>submitAction(actions_HIDE)),!1),null===(_document$getElementB6=document.getElementById(cssIds.SHOW_LINK))||void 0===_document$getElementB6||_document$getElementB6.addEventListener("click",(()=>submitAction(actions_SHOW)),!1),null===(_document$getElementB7=document.getElementById(cssIds.MAKE_AVAILABLE_LINK))||void 0===_document$getElementB7||_document$getElementB7.addEventListener("click",(()=>submitAction(actions_MAKE_AVAILABLE)),!1),null===(_document$getElementB8=document.getElementById(cssIds.DUPLICATE_LINK))||void 0===_document$getElementB8||_document$getElementB8.addEventListener("click",(()=>submitAction(actions_DUPLICATE)),!1),null===(_document$getElementB9=document.getElementById(cssIds.DELETE_LINK))||void 0===_document$getElementB9||_document$getElementB9.addEventListener("click",(()=>submitAction(actions_DELETE)),!1),null===(_document$getElementB10=document.getElementById(cssIds.SHOW_DESCRIPTION_LINK))||void 0===_document$getElementB10||_document$getElementB10.addEventListener("click",(()=>submitAction(actions_SHOW_DESCRIPTION)),!1),null===(_document$getElementB11=document.getElementById(cssIds.HIDE_DESCRIPTION_LINK))||void 0===_document$getElementB11||_document$getElementB11.addEventListener("click",(()=>submitAction(actions_HIDE_DESCRIPTION)),!1),null===(_document$getElementB12=document.getElementById(cssIds.CONTENT_CHANGED_NOTIFICATION_LINK))||void 0===_document$getElementB12||_document$getElementB12.addEventListener("click",(()=>submitAction(actions_CONTENT_CHANGED_NOTIFICATION)),!1),null===(_document$getElementB13=document.getElementById(cssIds.MOVELEFT_LINK))||void 0===_document$getElementB13||_document$getElementB13.addEventListener("click",(()=>submitAction(actions_MOVE_LEFT)),!1),null===(_document$getElementB14=document.getElementById(cssIds.MOVERIGHT_LINK))||void 0===_document$getElementB14||_document$getElementB14.addEventListener("click",(()=>submitAction(actions_MOVE_RIGHT)),!1),null===(_document$getElementB15=document.getElementById(cssIds.MOVETO_ICON_LINK))||void 0===_document$getElementB15||_document$getElementB15.addEventListener("click",(()=>submitAction(actions_MOVE_TO)),!1),null===(_document$getElementB16=document.getElementById(cssIds.DUPLICATETO_ICON_LINK))||void 0===_document$getElementB16||_document$getElementB16.addEventListener("click",(()=>submitAction(actions_DUPLICATE_TO)),!1),null===(_document$getElementB17=document.getElementById(cssIds.DUPLICATE_TO_COURSE_ICON_LINK))||void 0===_document$getElementB17||_document$getElementB17.addEventListener("click",(()=>submitAction(actions_DUPLICATE_TO_COURSE)),!1),pendingPromise.resolve()};const submitAction=action=>{const submitData={action:action,moduleIds:[]};if(submitData.moduleIds=checkboxmanager.getSelectedModIds(),0===submitData.moduleIds.length)return displayError(Str.get_string("noitemselected","block_massaction")),!1;switch(action){case actions_HIDE:case actions_SHOW:case actions_MAKE_AVAILABLE:case actions_DUPLICATE:case actions_DUPLICATE_TO_COURSE:case actions_CONTENT_CHANGED_NOTIFICATION:case actions_MOVE_LEFT:case actions_MOVE_RIGHT:case actions_DELETE:case actions_SHOW_DESCRIPTION:case actions_HIDE_DESCRIPTION:break;case actions_MOVE_TO:if(submitData.moveToTarget=document.getElementById(cssIds.MOVETO_SELECT).value,""===submitData.moveToTarget.trim())return displayError(Str.get_string("nomovingtargetselected","block_massaction")),!1;break;case actions_DUPLICATE_TO:if(submitData.duplicateToTarget=document.getElementById(cssIds.DUPLICATETO_SELECT).value,""===submitData.duplicateToTarget.trim())return displayError(Str.get_string("nomovingtargetselected","block_massaction")),!1;break;default:return displayError("Unknown action: "+action+". Coding error."),!1}return document.getElementById(cssIds.HIDDEN_FIELD_REQUEST_INFORMATION).value=JSON.stringify(submitData),document.getElementById(cssIds.ACTION_FORM).submit(),!0},displayError=errorText=>{Promise.resolve([Str.get_string("error","core"),errorText,Str.get_string("back","core")]).then((text=>_notification.default.alert(text[0],text[1],text[2]))).catch((error=>_log.default.debug(error)))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.usedMoodleCssClasses=_exports.init=_exports.cssIds=_exports.constants=void 0,checkboxmanager=_interopRequireWildcard(checkboxmanager),Str=_interopRequireWildcard(Str),_log=_interopRequireDefault(_log),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_events=_interopRequireDefault(_events);_exports.usedMoodleCssClasses={ACTIVITY_ITEM:".activity-item",SECTION_NAME:"sectionname",MODULE_ID_PREFIX:"module-",BOX_ID_PREFIX:"cmCheckbox"};const cssIds={BLOCK_CONTENT:"block-massaction",BULK_EDITING_DISABLED:"block-massaction-bulk-editing-disabled",SELECT_ALL_LINK:"block-massaction-control-selectall",DESELECT_ALL_LINK:"block-massaction-control-deselectall",HIDE_LINK:"block-massaction-action-hide",SHOW_LINK:"block-massaction-action-show",MAKE_AVAILABLE_LINK:"block-massaction-action-makeavailable",DUPLICATE_LINK:"block-massaction-action-duplicate",DELETE_LINK:"block-massaction-action-delete",SHOW_DESCRIPTION_LINK:"block-massaction-action-showdescription",HIDE_DESCRIPTION_LINK:"block-massaction-action-hidedescription",CONTENT_CHANGED_NOTIFICATION_LINK:"block-massaction-action-contentchangednotification",MOVELEFT_LINK:"block-massaction-action-moveleft",MOVERIGHT_LINK:"block-massaction-action-moveright",MOVETO_ICON_LINK:"block-massaction-action-moveto",DUPLICATETO_ICON_LINK:"block-massaction-action-duplicateto",DUPLICATE_TO_COURSE_ICON_LINK:"block-massaction-action-duplicatetocourse",SECTION_SELECT:"block-massaction-control-section-list-select",MOVETO_SELECT:"block-massaction-control-section-list-moveto",DUPLICATETO_SELECT:"block-massaction-control-section-list-duplicateto",HIDDEN_FIELD_REQUEST_INFORMATION:"block-massaction-control-request",ACTION_FORM:"block-massaction-control-form",SECTION_FILTER_DATA:'[data-block-massaction-data="availabletargetsections"]'};_exports.cssIds=cssIds;const constants={SECTION_SELECT_DESCRIPTION_VALUE:"description",SECTION_NUMBER_ALL_PLACEHOLDER:"all"};_exports.constants=constants;const actions_HIDE="hide",actions_SHOW="show",actions_MAKE_AVAILABLE="makeavailable",actions_DUPLICATE="duplicate",actions_DELETE="delete",actions_SHOW_DESCRIPTION="showdescription",actions_HIDE_DESCRIPTION="hidedescription",actions_MOVE_LEFT="moveleft",actions_MOVE_RIGHT="moveright",actions_CONTENT_CHANGED_NOTIFICATION="contentchangednotification",actions_MOVE_TO="moveto",actions_DUPLICATE_TO="duplicateto",actions_DUPLICATE_TO_COURSE="duplicatetocourse";_exports.init=async()=>{var _document$getElementB3,_document$getElementB4,_document$getElementB5,_document$getElementB6,_document$getElementB7,_document$getElementB8,_document$getElementB9,_document$getElementB10,_document$getElementB11,_document$getElementB12,_document$getElementB13,_document$getElementB14,_document$getElementB15,_document$getElementB16,_document$getElementB17;const pendingPromise=new _pending.default("block_massaction/init"),editor=(0,_courseeditor.getCurrentCourseEditor)();editor.stateManager.getInitialPromise().then((()=>{checkboxmanager.initCheckboxManager(),editor.stateManager.target.addEventListener(_events.default.stateChanged,(event=>{var _document$getElementB,_document$getElementB2;"bulk.enabled:updated"===event.detail.action&&(null===(_document$getElementB=document.getElementById(cssIds.BLOCK_CONTENT))||void 0===_document$getElementB||_document$getElementB.classList.toggle("d-none"),null===(_document$getElementB2=document.getElementById(cssIds.BULK_EDITING_DISABLED))||void 0===_document$getElementB2||_document$getElementB2.classList.toggle("d-none"))}));const enableBulkButton=document.getElementById("block-massaction-enable-bulk-editing");return enableBulkButton.disabled=!1,null==enableBulkButton||enableBulkButton.addEventListener("click",(()=>editor.dispatch("bulkEnable",!0))),!0})).catch((error=>_log.default.debug(error))),null===(_document$getElementB3=document.getElementById(cssIds.SELECT_ALL_LINK))||void 0===_document$getElementB3||_document$getElementB3.addEventListener("click",(()=>checkboxmanager.setSectionSelection(!0,constants.SECTION_NUMBER_ALL_PLACEHOLDER)),!1),null===(_document$getElementB4=document.getElementById(cssIds.DESELECT_ALL_LINK))||void 0===_document$getElementB4||_document$getElementB4.addEventListener("click",(()=>checkboxmanager.setSectionSelection(!1,constants.SECTION_NUMBER_ALL_PLACEHOLDER)),!1),null===(_document$getElementB5=document.getElementById(cssIds.HIDE_LINK))||void 0===_document$getElementB5||_document$getElementB5.addEventListener("click",(()=>submitAction(actions_HIDE)),!1),null===(_document$getElementB6=document.getElementById(cssIds.SHOW_LINK))||void 0===_document$getElementB6||_document$getElementB6.addEventListener("click",(()=>submitAction(actions_SHOW)),!1),null===(_document$getElementB7=document.getElementById(cssIds.MAKE_AVAILABLE_LINK))||void 0===_document$getElementB7||_document$getElementB7.addEventListener("click",(()=>submitAction(actions_MAKE_AVAILABLE)),!1),null===(_document$getElementB8=document.getElementById(cssIds.DUPLICATE_LINK))||void 0===_document$getElementB8||_document$getElementB8.addEventListener("click",(()=>submitAction(actions_DUPLICATE)),!1),null===(_document$getElementB9=document.getElementById(cssIds.DELETE_LINK))||void 0===_document$getElementB9||_document$getElementB9.addEventListener("click",(()=>submitAction(actions_DELETE)),!1),null===(_document$getElementB10=document.getElementById(cssIds.SHOW_DESCRIPTION_LINK))||void 0===_document$getElementB10||_document$getElementB10.addEventListener("click",(()=>submitAction(actions_SHOW_DESCRIPTION)),!1),null===(_document$getElementB11=document.getElementById(cssIds.HIDE_DESCRIPTION_LINK))||void 0===_document$getElementB11||_document$getElementB11.addEventListener("click",(()=>submitAction(actions_HIDE_DESCRIPTION)),!1),null===(_document$getElementB12=document.getElementById(cssIds.CONTENT_CHANGED_NOTIFICATION_LINK))||void 0===_document$getElementB12||_document$getElementB12.addEventListener("click",(()=>submitAction(actions_CONTENT_CHANGED_NOTIFICATION)),!1),null===(_document$getElementB13=document.getElementById(cssIds.MOVELEFT_LINK))||void 0===_document$getElementB13||_document$getElementB13.addEventListener("click",(()=>submitAction(actions_MOVE_LEFT)),!1),null===(_document$getElementB14=document.getElementById(cssIds.MOVERIGHT_LINK))||void 0===_document$getElementB14||_document$getElementB14.addEventListener("click",(()=>submitAction(actions_MOVE_RIGHT)),!1),null===(_document$getElementB15=document.getElementById(cssIds.MOVETO_ICON_LINK))||void 0===_document$getElementB15||_document$getElementB15.addEventListener("click",(()=>submitAction(actions_MOVE_TO)),!1),null===(_document$getElementB16=document.getElementById(cssIds.DUPLICATETO_ICON_LINK))||void 0===_document$getElementB16||_document$getElementB16.addEventListener("click",(()=>submitAction(actions_DUPLICATE_TO)),!1),null===(_document$getElementB17=document.getElementById(cssIds.DUPLICATE_TO_COURSE_ICON_LINK))||void 0===_document$getElementB17||_document$getElementB17.addEventListener("click",(()=>submitAction(actions_DUPLICATE_TO_COURSE)),!1),pendingPromise.resolve()};const submitAction=action=>{const submitData={action:action,moduleIds:[]};if(submitData.moduleIds=checkboxmanager.getSelectedModIds(),0===submitData.moduleIds.length)return displayError(Str.get_string("noitemselected","block_massaction")),!1;switch(action){case actions_HIDE:case actions_SHOW:case actions_MAKE_AVAILABLE:case actions_DUPLICATE:case actions_DUPLICATE_TO_COURSE:case actions_CONTENT_CHANGED_NOTIFICATION:case actions_MOVE_LEFT:case actions_MOVE_RIGHT:case actions_DELETE:case actions_SHOW_DESCRIPTION:case actions_HIDE_DESCRIPTION:break;case actions_MOVE_TO:if(submitData.moveToTarget=document.getElementById(cssIds.MOVETO_SELECT).value,""===submitData.moveToTarget.trim())return displayError(Str.get_string("nomovingtargetselected","block_massaction")),!1;break;case actions_DUPLICATE_TO:if(submitData.duplicateToTarget=document.getElementById(cssIds.DUPLICATETO_SELECT).value,""===submitData.duplicateToTarget.trim())return displayError(Str.get_string("nomovingtargetselected","block_massaction")),!1;break;default:return displayError("Unknown action: "+action+". Coding error."),!1}return document.getElementById(cssIds.HIDDEN_FIELD_REQUEST_INFORMATION).value=JSON.stringify(submitData),document.getElementById(cssIds.ACTION_FORM).submit(),!0},displayError=errorText=>{Promise.resolve([Str.get_string("error","core"),errorText,Str.get_string("back","core")]).then((text=>_notification.default.alert(text[0],text[1],text[2]))).catch((error=>_log.default.debug(error)))}})); //# sourceMappingURL=massactionblock.min.js.map \ No newline at end of file diff --git a/amd/build/massactionblock.min.js.map b/amd/build/massactionblock.min.js.map index 5631201..7ff8e5a 100644 --- a/amd/build/massactionblock.min.js.map +++ b/amd/build/massactionblock.min.js.map @@ -1 +1 @@ -{"version":3,"file":"massactionblock.min.js","sources":["../src/massactionblock.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main module for the massaction block.\n *\n * @module block_massaction/massactionblock\n * @copyright 2022 ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as checkboxmanager from 'block_massaction/checkboxmanager';\nimport * as Str from 'core/str';\nimport Log from 'core/log';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport events from \"core_course/events\";\n\nexport const usedMoodleCssClasses = {\n ACTIVITY_ITEM: '.activity-item',\n SECTION_NAME: 'sectionname',\n MODULE_ID_PREFIX: 'module-',\n BOX_ID_PREFIX: 'cmCheckbox'\n};\n\nexport const cssIds = {\n BLOCK_CONTENT: 'block-massaction',\n BULK_EDITING_DISABLED: 'block-massaction-bulk-editing-disabled',\n SELECT_ALL_LINK: 'block-massaction-control-selectall',\n DESELECT_ALL_LINK: 'block-massaction-control-deselectall',\n HIDE_LINK: 'block-massaction-action-hide',\n SHOW_LINK: 'block-massaction-action-show',\n MAKE_AVAILABLE_LINK: 'block-massaction-action-makeavailable',\n DUPLICATE_LINK: 'block-massaction-action-duplicate',\n DELETE_LINK: 'block-massaction-action-delete',\n SHOW_DESCRIPTION_LINK: 'block-massaction-action-showdescription',\n HIDE_DESCRIPTION_LINK: 'block-massaction-action-hidedescription',\n CONTENT_CHANGED_NOTIFICATION_LINK: 'block-massaction-action-contentchangednotification',\n MOVELEFT_LINK: 'block-massaction-action-moveleft',\n MOVERIGHT_LINK: 'block-massaction-action-moveright',\n MOVETO_ICON_LINK: 'block-massaction-action-moveto',\n DUPLICATETO_ICON_LINK: 'block-massaction-action-duplicateto',\n DUPLICATE_TO_COURSE_ICON_LINK: 'block-massaction-action-duplicatetocourse',\n SECTION_SELECT: 'block-massaction-control-section-list-select',\n MOVETO_SELECT: 'block-massaction-control-section-list-moveto',\n DUPLICATETO_SELECT: 'block-massaction-control-section-list-duplicateto',\n HIDDEN_FIELD_REQUEST_INFORMATION: 'block-massaction-control-request',\n ACTION_FORM: 'block-massaction-control-form',\n};\n\nexport const constants = {\n SECTION_SELECT_DESCRIPTION_VALUE: 'description',\n SECTION_NUMBER_ALL_PLACEHOLDER: 'all',\n};\n\nconst actions = {\n HIDE: 'hide',\n SHOW: 'show',\n MAKE_AVAILABLE: 'makeavailable',\n DUPLICATE: 'duplicate',\n DELETE: 'delete',\n SHOW_DESCRIPTION: 'showdescription',\n HIDE_DESCRIPTION: 'hidedescription',\n MOVE_LEFT: 'moveleft',\n MOVE_RIGHT: 'moveright',\n CONTENT_CHANGED_NOTIFICATION: 'contentchangednotification',\n MOVE_TO: 'moveto',\n DUPLICATE_TO: 'duplicateto',\n DUPLICATE_TO_COURSE: 'duplicatetocourse',\n};\n\n/**\n * Initialize the mass-action block.\n * @param {[]} sectionsRestricted the sections which are restrected for the course format\n */\nexport const init = async(sectionsRestricted) => {\n const pendingPromise = new Pending('block_massaction/init');\n\n const editor = getCurrentCourseEditor();\n // As soon as courseeditor is available, do some initial setup.\n editor.stateManager.getInitialPromise()\n .then(() => {\n // Initialize the checkbox manager.\n checkboxmanager.initCheckboxManager(sectionsRestricted);\n\n // Show block depending on if the moodle bulk editing util has been activated.\n editor.stateManager.target.addEventListener(events.stateChanged, (event) => {\n // Listen to the event that bulk editing mode has been enabled/disabled.\n if (event.detail.action === 'bulk.enabled:updated') {\n // Hide/show block content depending on the bulk editing enabled state.\n document.getElementById(cssIds.BLOCK_CONTENT)?.classList.toggle('d-none');\n document.getElementById(cssIds.BULK_EDITING_DISABLED)?.classList.toggle('d-none');\n }\n });\n\n // Register click handler for the button in the placeholder text if bulk editing is still disabled.\n const enableBulkButton = document.getElementById('block-massaction-enable-bulk-editing');\n // Remove the initial disabled attribute which is there to avoid too early clicks by users.\n enableBulkButton.disabled = false;\n enableBulkButton?.addEventListener('click', () => editor.dispatch('bulkEnable', true));\n return true;\n })\n .catch(error => Log.debug(error));\n\n document.getElementById(cssIds.SELECT_ALL_LINK)?.addEventListener('click',\n () => checkboxmanager.setSectionSelection(true, constants.SECTION_NUMBER_ALL_PLACEHOLDER), false);\n\n document.getElementById(cssIds.DESELECT_ALL_LINK)?.addEventListener('click',\n () => checkboxmanager.setSectionSelection(false, constants.SECTION_NUMBER_ALL_PLACEHOLDER), false);\n\n document.getElementById(cssIds.HIDE_LINK)?.addEventListener('click',\n () => submitAction(actions.HIDE), false);\n\n document.getElementById(cssIds.SHOW_LINK)?.addEventListener('click',\n () => submitAction(actions.SHOW), false);\n\n document.getElementById(cssIds.MAKE_AVAILABLE_LINK)?.addEventListener('click',\n () => submitAction(actions.MAKE_AVAILABLE), false);\n\n document.getElementById(cssIds.DUPLICATE_LINK)?.addEventListener('click',\n () => submitAction(actions.DUPLICATE), false);\n\n document.getElementById(cssIds.DELETE_LINK)?.addEventListener('click',\n () => submitAction(actions.DELETE), false);\n\n document.getElementById(cssIds.SHOW_DESCRIPTION_LINK)?.addEventListener('click',\n () => submitAction(actions.SHOW_DESCRIPTION), false);\n\n document.getElementById(cssIds.HIDE_DESCRIPTION_LINK)?.addEventListener('click',\n () => submitAction(actions.HIDE_DESCRIPTION), false);\n\n document.getElementById(cssIds.CONTENT_CHANGED_NOTIFICATION_LINK)?.addEventListener('click',\n () => submitAction(actions.CONTENT_CHANGED_NOTIFICATION), false);\n\n document.getElementById(cssIds.MOVELEFT_LINK)?.addEventListener('click',\n () => submitAction(actions.MOVE_LEFT), false);\n\n document.getElementById(cssIds.MOVERIGHT_LINK)?.addEventListener('click',\n () => submitAction(actions.MOVE_RIGHT), false);\n\n document.getElementById(cssIds.MOVETO_ICON_LINK)?.addEventListener('click',\n () => submitAction(actions.MOVE_TO), false);\n\n document.getElementById(cssIds.DUPLICATETO_ICON_LINK)?.addEventListener('click',\n () => submitAction(actions.DUPLICATE_TO), false);\n\n document.getElementById(cssIds.DUPLICATE_TO_COURSE_ICON_LINK)?.addEventListener('click',\n () => submitAction(actions.DUPLICATE_TO_COURSE), false);\n\n pendingPromise.resolve();\n};\n\n/**\n * Submit the selected action to server.\n *\n * @param {string} action\n * @return {boolean} true if action was successful, false otherwise\n */\nconst submitAction = (action) => {\n const submitData = {\n 'action': action,\n 'moduleIds': []\n };\n\n submitData.moduleIds = checkboxmanager.getSelectedModIds();\n\n // Verify that at least one checkbox is checked.\n if (submitData.moduleIds.length === 0) {\n displayError(Str.get_string('noitemselected', 'block_massaction'));\n return false;\n }\n\n // Prep the submission.\n switch (action) {\n case actions.HIDE:\n case actions.SHOW:\n case actions.MAKE_AVAILABLE:\n case actions.DUPLICATE:\n case actions.DUPLICATE_TO_COURSE:\n case actions.CONTENT_CHANGED_NOTIFICATION:\n case actions.MOVE_LEFT:\n case actions.MOVE_RIGHT:\n case actions.DELETE:\n case actions.SHOW_DESCRIPTION:\n case actions.HIDE_DESCRIPTION:\n break;\n\n case actions.MOVE_TO:\n // Get the target section.\n submitData.moveToTarget = document.getElementById(cssIds.MOVETO_SELECT).value;\n if (submitData.moveToTarget.trim() === '') {\n displayError(Str.get_string('nomovingtargetselected', 'block_massaction'));\n return false;\n }\n break;\n\n case actions.DUPLICATE_TO:\n // Get the target section.\n submitData.duplicateToTarget = document.getElementById(cssIds.DUPLICATETO_SELECT).value;\n if (submitData.duplicateToTarget.trim() === '') {\n displayError(Str.get_string('nomovingtargetselected', 'block_massaction'));\n return false;\n }\n break;\n default:\n displayError('Unknown action: ' + action + '. Coding error.');\n return false;\n }\n // Set the form value and submit.\n document.getElementById(cssIds.HIDDEN_FIELD_REQUEST_INFORMATION).value = JSON.stringify(submitData);\n document.getElementById(cssIds.ACTION_FORM).submit();\n return true;\n};\n\nconst displayError = (errorText) => {\n Promise.resolve([Str.get_string('error', 'core'), errorText, Str.get_string('back', 'core')])\n .then(text => Notification.alert(text[0], text[1], text[2]))\n .catch(error => Log.debug(error));\n};\n"],"names":["ACTIVITY_ITEM","SECTION_NAME","MODULE_ID_PREFIX","BOX_ID_PREFIX","cssIds","BLOCK_CONTENT","BULK_EDITING_DISABLED","SELECT_ALL_LINK","DESELECT_ALL_LINK","HIDE_LINK","SHOW_LINK","MAKE_AVAILABLE_LINK","DUPLICATE_LINK","DELETE_LINK","SHOW_DESCRIPTION_LINK","HIDE_DESCRIPTION_LINK","CONTENT_CHANGED_NOTIFICATION_LINK","MOVELEFT_LINK","MOVERIGHT_LINK","MOVETO_ICON_LINK","DUPLICATETO_ICON_LINK","DUPLICATE_TO_COURSE_ICON_LINK","SECTION_SELECT","MOVETO_SELECT","DUPLICATETO_SELECT","HIDDEN_FIELD_REQUEST_INFORMATION","ACTION_FORM","constants","SECTION_SELECT_DESCRIPTION_VALUE","SECTION_NUMBER_ALL_PLACEHOLDER","actions","async","pendingPromise","Pending","editor","stateManager","getInitialPromise","then","checkboxmanager","initCheckboxManager","sectionsRestricted","target","addEventListener","events","stateChanged","event","detail","action","document","getElementById","classList","toggle","enableBulkButton","disabled","dispatch","catch","error","Log","debug","setSectionSelection","submitAction","resolve","submitData","moduleIds","getSelectedModIds","length","displayError","Str","get_string","moveToTarget","value","trim","duplicateToTarget","JSON","stringify","submit","errorText","Promise","text","Notification","alert"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"massactionblock.min.js","sources":["../src/massactionblock.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main module for the massaction block.\n *\n * @module block_massaction/massactionblock\n * @copyright 2022 ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as checkboxmanager from 'block_massaction/checkboxmanager';\nimport * as Str from 'core/str';\nimport Log from 'core/log';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport events from \"core_course/events\";\n\nexport const usedMoodleCssClasses = {\n ACTIVITY_ITEM: '.activity-item',\n SECTION_NAME: 'sectionname',\n MODULE_ID_PREFIX: 'module-',\n BOX_ID_PREFIX: 'cmCheckbox'\n};\n\nexport const cssIds = {\n BLOCK_CONTENT: 'block-massaction',\n BULK_EDITING_DISABLED: 'block-massaction-bulk-editing-disabled',\n SELECT_ALL_LINK: 'block-massaction-control-selectall',\n DESELECT_ALL_LINK: 'block-massaction-control-deselectall',\n HIDE_LINK: 'block-massaction-action-hide',\n SHOW_LINK: 'block-massaction-action-show',\n MAKE_AVAILABLE_LINK: 'block-massaction-action-makeavailable',\n DUPLICATE_LINK: 'block-massaction-action-duplicate',\n DELETE_LINK: 'block-massaction-action-delete',\n SHOW_DESCRIPTION_LINK: 'block-massaction-action-showdescription',\n HIDE_DESCRIPTION_LINK: 'block-massaction-action-hidedescription',\n CONTENT_CHANGED_NOTIFICATION_LINK: 'block-massaction-action-contentchangednotification',\n MOVELEFT_LINK: 'block-massaction-action-moveleft',\n MOVERIGHT_LINK: 'block-massaction-action-moveright',\n MOVETO_ICON_LINK: 'block-massaction-action-moveto',\n DUPLICATETO_ICON_LINK: 'block-massaction-action-duplicateto',\n DUPLICATE_TO_COURSE_ICON_LINK: 'block-massaction-action-duplicatetocourse',\n SECTION_SELECT: 'block-massaction-control-section-list-select',\n MOVETO_SELECT: 'block-massaction-control-section-list-moveto',\n DUPLICATETO_SELECT: 'block-massaction-control-section-list-duplicateto',\n HIDDEN_FIELD_REQUEST_INFORMATION: 'block-massaction-control-request',\n ACTION_FORM: 'block-massaction-control-form',\n SECTION_FILTER_DATA: `[data-block-massaction-data=\"availabletargetsections\"]`\n};\n\nexport const constants = {\n SECTION_SELECT_DESCRIPTION_VALUE: 'description',\n SECTION_NUMBER_ALL_PLACEHOLDER: 'all',\n};\n\nconst actions = {\n HIDE: 'hide',\n SHOW: 'show',\n MAKE_AVAILABLE: 'makeavailable',\n DUPLICATE: 'duplicate',\n DELETE: 'delete',\n SHOW_DESCRIPTION: 'showdescription',\n HIDE_DESCRIPTION: 'hidedescription',\n MOVE_LEFT: 'moveleft',\n MOVE_RIGHT: 'moveright',\n CONTENT_CHANGED_NOTIFICATION: 'contentchangednotification',\n MOVE_TO: 'moveto',\n DUPLICATE_TO: 'duplicateto',\n DUPLICATE_TO_COURSE: 'duplicatetocourse',\n};\n\n/**\n * Initialize the mass-action block.\n */\nexport const init = async() => {\n const pendingPromise = new Pending('block_massaction/init');\n\n const editor = getCurrentCourseEditor();\n // As soon as courseeditor is available, do some initial setup.\n editor.stateManager.getInitialPromise()\n .then(() => {\n // Initialize the checkbox manager.\n checkboxmanager.initCheckboxManager();\n\n // Show block depending on if the moodle bulk editing util has been activated.\n editor.stateManager.target.addEventListener(events.stateChanged, (event) => {\n // Listen to the event that bulk editing mode has been enabled/disabled.\n if (event.detail.action === 'bulk.enabled:updated') {\n // Hide/show block content depending on the bulk editing enabled state.\n document.getElementById(cssIds.BLOCK_CONTENT)?.classList.toggle('d-none');\n document.getElementById(cssIds.BULK_EDITING_DISABLED)?.classList.toggle('d-none');\n }\n });\n\n // Register click handler for the button in the placeholder text if bulk editing is still disabled.\n const enableBulkButton = document.getElementById('block-massaction-enable-bulk-editing');\n // Remove the initial disabled attribute which is there to avoid too early clicks by users.\n enableBulkButton.disabled = false;\n enableBulkButton?.addEventListener('click', () => editor.dispatch('bulkEnable', true));\n return true;\n })\n .catch(error => Log.debug(error));\n\n document.getElementById(cssIds.SELECT_ALL_LINK)?.addEventListener('click',\n () => checkboxmanager.setSectionSelection(true, constants.SECTION_NUMBER_ALL_PLACEHOLDER), false);\n\n document.getElementById(cssIds.DESELECT_ALL_LINK)?.addEventListener('click',\n () => checkboxmanager.setSectionSelection(false, constants.SECTION_NUMBER_ALL_PLACEHOLDER), false);\n\n document.getElementById(cssIds.HIDE_LINK)?.addEventListener('click',\n () => submitAction(actions.HIDE), false);\n\n document.getElementById(cssIds.SHOW_LINK)?.addEventListener('click',\n () => submitAction(actions.SHOW), false);\n\n document.getElementById(cssIds.MAKE_AVAILABLE_LINK)?.addEventListener('click',\n () => submitAction(actions.MAKE_AVAILABLE), false);\n\n document.getElementById(cssIds.DUPLICATE_LINK)?.addEventListener('click',\n () => submitAction(actions.DUPLICATE), false);\n\n document.getElementById(cssIds.DELETE_LINK)?.addEventListener('click',\n () => submitAction(actions.DELETE), false);\n\n document.getElementById(cssIds.SHOW_DESCRIPTION_LINK)?.addEventListener('click',\n () => submitAction(actions.SHOW_DESCRIPTION), false);\n\n document.getElementById(cssIds.HIDE_DESCRIPTION_LINK)?.addEventListener('click',\n () => submitAction(actions.HIDE_DESCRIPTION), false);\n\n document.getElementById(cssIds.CONTENT_CHANGED_NOTIFICATION_LINK)?.addEventListener('click',\n () => submitAction(actions.CONTENT_CHANGED_NOTIFICATION), false);\n\n document.getElementById(cssIds.MOVELEFT_LINK)?.addEventListener('click',\n () => submitAction(actions.MOVE_LEFT), false);\n\n document.getElementById(cssIds.MOVERIGHT_LINK)?.addEventListener('click',\n () => submitAction(actions.MOVE_RIGHT), false);\n\n document.getElementById(cssIds.MOVETO_ICON_LINK)?.addEventListener('click',\n () => submitAction(actions.MOVE_TO), false);\n\n document.getElementById(cssIds.DUPLICATETO_ICON_LINK)?.addEventListener('click',\n () => submitAction(actions.DUPLICATE_TO), false);\n\n document.getElementById(cssIds.DUPLICATE_TO_COURSE_ICON_LINK)?.addEventListener('click',\n () => submitAction(actions.DUPLICATE_TO_COURSE), false);\n\n pendingPromise.resolve();\n};\n\n/**\n * Submit the selected action to server.\n *\n * @param {string} action\n * @return {boolean} true if action was successful, false otherwise\n */\nconst submitAction = (action) => {\n const submitData = {\n 'action': action,\n 'moduleIds': []\n };\n\n submitData.moduleIds = checkboxmanager.getSelectedModIds();\n\n // Verify that at least one checkbox is checked.\n if (submitData.moduleIds.length === 0) {\n displayError(Str.get_string('noitemselected', 'block_massaction'));\n return false;\n }\n\n // Prep the submission.\n switch (action) {\n case actions.HIDE:\n case actions.SHOW:\n case actions.MAKE_AVAILABLE:\n case actions.DUPLICATE:\n case actions.DUPLICATE_TO_COURSE:\n case actions.CONTENT_CHANGED_NOTIFICATION:\n case actions.MOVE_LEFT:\n case actions.MOVE_RIGHT:\n case actions.DELETE:\n case actions.SHOW_DESCRIPTION:\n case actions.HIDE_DESCRIPTION:\n break;\n\n case actions.MOVE_TO:\n // Get the target section.\n submitData.moveToTarget = document.getElementById(cssIds.MOVETO_SELECT).value;\n if (submitData.moveToTarget.trim() === '') {\n displayError(Str.get_string('nomovingtargetselected', 'block_massaction'));\n return false;\n }\n break;\n\n case actions.DUPLICATE_TO:\n // Get the target section.\n submitData.duplicateToTarget = document.getElementById(cssIds.DUPLICATETO_SELECT).value;\n if (submitData.duplicateToTarget.trim() === '') {\n displayError(Str.get_string('nomovingtargetselected', 'block_massaction'));\n return false;\n }\n break;\n default:\n displayError('Unknown action: ' + action + '. Coding error.');\n return false;\n }\n // Set the form value and submit.\n document.getElementById(cssIds.HIDDEN_FIELD_REQUEST_INFORMATION).value = JSON.stringify(submitData);\n document.getElementById(cssIds.ACTION_FORM).submit();\n return true;\n};\n\nconst displayError = (errorText) => {\n Promise.resolve([Str.get_string('error', 'core'), errorText, Str.get_string('back', 'core')])\n .then(text => Notification.alert(text[0], text[1], text[2]))\n .catch(error => Log.debug(error));\n};\n"],"names":["ACTIVITY_ITEM","SECTION_NAME","MODULE_ID_PREFIX","BOX_ID_PREFIX","cssIds","BLOCK_CONTENT","BULK_EDITING_DISABLED","SELECT_ALL_LINK","DESELECT_ALL_LINK","HIDE_LINK","SHOW_LINK","MAKE_AVAILABLE_LINK","DUPLICATE_LINK","DELETE_LINK","SHOW_DESCRIPTION_LINK","HIDE_DESCRIPTION_LINK","CONTENT_CHANGED_NOTIFICATION_LINK","MOVELEFT_LINK","MOVERIGHT_LINK","MOVETO_ICON_LINK","DUPLICATETO_ICON_LINK","DUPLICATE_TO_COURSE_ICON_LINK","SECTION_SELECT","MOVETO_SELECT","DUPLICATETO_SELECT","HIDDEN_FIELD_REQUEST_INFORMATION","ACTION_FORM","SECTION_FILTER_DATA","constants","SECTION_SELECT_DESCRIPTION_VALUE","SECTION_NUMBER_ALL_PLACEHOLDER","actions","async","pendingPromise","Pending","editor","stateManager","getInitialPromise","then","checkboxmanager","initCheckboxManager","target","addEventListener","events","stateChanged","event","detail","action","document","getElementById","classList","toggle","enableBulkButton","disabled","dispatch","catch","error","Log","debug","setSectionSelection","submitAction","resolve","submitData","moduleIds","getSelectedModIds","length","displayError","Str","get_string","moveToTarget","value","trim","duplicateToTarget","JSON","stringify","submit","errorText","Promise","text","Notification","alert"],"mappings":";;;;;;;;mbAgCoC,CAChCA,cAAe,iBACfC,aAAc,cACdC,iBAAkB,UAClBC,cAAe,oBAGNC,OAAS,CAClBC,cAAe,mBACfC,sBAAuB,yCACvBC,gBAAiB,qCACjBC,kBAAmB,uCACnBC,UAAW,+BACXC,UAAW,+BACXC,oBAAqB,wCACrBC,eAAgB,oCAChBC,YAAa,iCACbC,sBAAuB,0CACvBC,sBAAuB,0CACvBC,kCAAmC,qDACnCC,cAAe,mCACfC,eAAgB,oCAChBC,iBAAkB,iCAClBC,sBAAuB,sCACvBC,8BAA+B,4CAC/BC,eAAgB,+CAChBC,cAAe,+CACfC,mBAAoB,oDACpBC,iCAAkC,mCAClCC,YAAa,gCACbC,2GAGSC,UAAY,CACrBC,iCAAkC,cAClCC,+BAAgC,0CAG9BC,aACI,OADJA,aAEI,OAFJA,uBAGc,gBAHdA,kBAIS,YAJTA,eAKM,SALNA,yBAMgB,kBANhBA,yBAOgB,kBAPhBA,kBAQS,WARTA,mBASU,YATVA,qCAU4B,6BAV5BA,gBAWO,SAXPA,qBAYY,cAZZA,4BAamB,kCAMLC,qXACVC,eAAiB,IAAIC,iBAAQ,yBAE7BC,QAAS,0CAEfA,OAAOC,aAAaC,oBACfC,MAAK,KAEFC,gBAAgBC,sBAGhBL,OAAOC,aAAaK,OAAOC,iBAAiBC,gBAAOC,cAAeC,yDAElC,yBAAxBA,MAAMC,OAAOC,uCAEbC,SAASC,eAAe7C,OAAOC,uEAAgB6C,UAAUC,OAAO,yCAChEH,SAASC,eAAe7C,OAAOE,iFAAwB4C,UAAUC,OAAO,oBAK1EC,iBAAmBJ,SAASC,eAAe,+CAEjDG,iBAAiBC,UAAW,EAC5BD,MAAAA,kBAAAA,iBAAkBV,iBAAiB,SAAS,IAAMP,OAAOmB,SAAS,cAAc,MACzE,KAEVC,OAAMC,OAASC,aAAIC,MAAMF,wCAE9BR,SAASC,eAAe7C,OAAOG,2EAAkBmC,iBAAiB,SAC9D,IAAMH,gBAAgBoB,qBAAoB,EAAM/B,UAAUE,kCAAiC,kCAE/FkB,SAASC,eAAe7C,OAAOI,6EAAoBkC,iBAAiB,SAChE,IAAMH,gBAAgBoB,qBAAoB,EAAO/B,UAAUE,kCAAiC,kCAEhGkB,SAASC,eAAe7C,OAAOK,qEAAYiC,iBAAiB,SACxD,IAAMkB,aAAa7B,gBAAe,kCAEtCiB,SAASC,eAAe7C,OAAOM,qEAAYgC,iBAAiB,SACxD,IAAMkB,aAAa7B,gBAAe,kCAEtCiB,SAASC,eAAe7C,OAAOO,+EAAsB+B,iBAAiB,SAClE,IAAMkB,aAAa7B,0BAAyB,kCAEhDiB,SAASC,eAAe7C,OAAOQ,0EAAiB8B,iBAAiB,SAC7D,IAAMkB,aAAa7B,qBAAoB,kCAE3CiB,SAASC,eAAe7C,OAAOS,uEAAc6B,iBAAiB,SAC1D,IAAMkB,aAAa7B,kBAAiB,mCAExCiB,SAASC,eAAe7C,OAAOU,mFAAwB4B,iBAAiB,SACpE,IAAMkB,aAAa7B,4BAA2B,mCAElDiB,SAASC,eAAe7C,OAAOW,mFAAwB2B,iBAAiB,SACpE,IAAMkB,aAAa7B,4BAA2B,mCAElDiB,SAASC,eAAe7C,OAAOY,+FAAoC0B,iBAAiB,SAChF,IAAMkB,aAAa7B,wCAAuC,mCAE9DiB,SAASC,eAAe7C,OAAOa,2EAAgByB,iBAAiB,SAC5D,IAAMkB,aAAa7B,qBAAoB,mCAE3CiB,SAASC,eAAe7C,OAAOc,4EAAiBwB,iBAAiB,SAC7D,IAAMkB,aAAa7B,sBAAqB,mCAE5CiB,SAASC,eAAe7C,OAAOe,8EAAmBuB,iBAAiB,SAC/D,IAAMkB,aAAa7B,mBAAkB,mCAEzCiB,SAASC,eAAe7C,OAAOgB,mFAAwBsB,iBAAiB,SACpE,IAAMkB,aAAa7B,wBAAuB,mCAE9CiB,SAASC,eAAe7C,OAAOiB,2FAAgCqB,iBAAiB,SAC5E,IAAMkB,aAAa7B,+BAA8B,GAErDE,eAAe4B,iBASbD,aAAgBb,eACZe,WAAa,QACLf,iBACG,OAGjBe,WAAWC,UAAYxB,gBAAgByB,oBAGH,IAAhCF,WAAWC,UAAUE,cACrBC,aAAaC,IAAIC,WAAW,iBAAkB,sBACvC,SAIHrB,aACChB,kBACAA,kBACAA,4BACAA,uBACAA,iCACAA,0CACAA,uBACAA,wBACAA,oBACAA,8BACAA,oCAGAA,mBAED+B,WAAWO,aAAerB,SAASC,eAAe7C,OAAOmB,eAAe+C,MACjC,KAAnCR,WAAWO,aAAaE,cACxBL,aAAaC,IAAIC,WAAW,yBAA0B,sBAC/C,aAIVrC,wBAED+B,WAAWU,kBAAoBxB,SAASC,eAAe7C,OAAOoB,oBAAoB8C,MACtC,KAAxCR,WAAWU,kBAAkBD,cAC7BL,aAAaC,IAAIC,WAAW,yBAA0B,sBAC/C,uBAIXF,aAAa,mBAAqBnB,OAAS,oBACpC,SAGfC,SAASC,eAAe7C,OAAOqB,kCAAkC6C,MAAQG,KAAKC,UAAUZ,YACxFd,SAASC,eAAe7C,OAAOsB,aAAaiD,UACrC,GAGLT,aAAgBU,YAClBC,QAAQhB,QAAQ,CAACM,IAAIC,WAAW,QAAS,QAASQ,UAAWT,IAAIC,WAAW,OAAQ,UAC/E9B,MAAKwC,MAAQC,sBAAaC,MAAMF,KAAK,GAAIA,KAAK,GAAIA,KAAK,MACvDvB,OAAMC,OAASC,aAAIC,MAAMF"} \ No newline at end of file diff --git a/amd/src/checkboxmanager.js b/amd/src/checkboxmanager.js index 0aafff7..d5459f5 100644 --- a/amd/src/checkboxmanager.js +++ b/amd/src/checkboxmanager.js @@ -44,9 +44,8 @@ const sectionBoxes = {}; * The checkbox manager takes a given 'sections' data structure object and inserts a checkbox for each of the given * course modules in this data object into the DOM. * The checkbox manager returns another data object containing the ids of the added checkboxes. - * @param {[]} sectionsRestricted the sections which are restrected for the course format */ -export const initCheckboxManager = sectionsRestricted => { +export const initCheckboxManager = () => { const courseEditor = getCurrentCourseEditor(); const eventsToListen = { @@ -63,22 +62,21 @@ export const initCheckboxManager = sectionsRestricted => { if (event.detail.action === eventsToListen.CHANGE_FINISHED) { // Before every change to the state there is a transaction:start event. After the change is being commited, // we receive an transaction:end event. That is the point we want to react to changes of the state. - rebuildLocalState(sectionsRestricted); + rebuildLocalState(); } }); // Trigger rendering of sections dropdowns a first time. sectionsChanged = true; // Get initial state. - rebuildLocalState(sectionsRestricted); + rebuildLocalState(); }; /** * This method rebuilds the local state maintained in this module based on the course editor state. * * It will be called whenever a change to the courseeditor state is being detected. - * @param {[]} sectionsRestricted the sections which are restrected for the course format */ -const rebuildLocalState = sectionsRestricted => { +const rebuildLocalState = () => { if (localStateUpdating) { return; } @@ -97,7 +95,7 @@ const rebuildLocalState = sectionsRestricted => { // Now we use the new information to rebuild dropdowns and re-apply checkboxes. const sectionsUnfiltered = sections; sections = filterVisibleSections(sections); - updateSelectionAndMoveToDropdowns(sections, sectionsUnfiltered, sectionsRestricted); + updateSelectionAndMoveToDropdowns(sections, sectionsUnfiltered); addCheckboxesToDataStructure(); localStateUpdating = false; }; @@ -176,7 +174,7 @@ const addCheckboxesToDataStructure = () => { /** * Filter the sections data object depending on the visibility of the course modules contained in - * the data object. This is neccessary, because some course formats only show specific section(s) + * the data object. This is necessary, because some course formats only show specific section(s) * in editing mode. * * @param {[]} sections the sections data object @@ -198,10 +196,8 @@ const filterVisibleSections = (sections) => { * * @param {[]} sections the sections object filtered before by {@link filterVisibleSections} * @param {[]} sectionsUnfiltered the same data object as 'sections', but still containing all sections - * @param {[]} sectionsRestricted the sections which are restrected for the course format - * no matter if containing modules or are visible in the current course format or not */ -const updateSelectionAndMoveToDropdowns = (sections, sectionsUnfiltered, sectionsRestricted) => { +const updateSelectionAndMoveToDropdowns = (sections, sectionsUnfiltered) => { if (sectionsChanged) { Templates.renderForPromise('block_massaction/section_select', {'sections': sectionsUnfiltered}) .then(({html, js}) => { @@ -217,7 +213,7 @@ const updateSelectionAndMoveToDropdowns = (sections, sectionsUnfiltered, section Templates.renderForPromise('block_massaction/moveto_select', {'sections': sectionsUnfiltered}) .then(({html, js}) => { Templates.replaceNode('#' + cssIds.MOVETO_SELECT, html, js); - disableRestrictedSections(cssIds.MOVETO_SELECT, sectionsRestricted); + disableUnavailableSections(cssIds.MOVETO_SELECT); return true; }) .catch(ex => displayException(ex)); @@ -225,13 +221,13 @@ const updateSelectionAndMoveToDropdowns = (sections, sectionsUnfiltered, section Templates.renderForPromise('block_massaction/duplicateto_select', {'sections': sectionsUnfiltered}) .then(({html, js}) => { Templates.replaceNode('#' + cssIds.DUPLICATETO_SELECT, html, js); - disableRestrictedSections(cssIds.DUPLICATETO_SELECT, sectionsRestricted); + disableUnavailableSections(cssIds.DUPLICATETO_SELECT); return true; }) .catch(ex => displayException(ex)); } else { // If there has not been an event about a section change we do not have to rebuild the sections dropdowns. - // However there is a chance an section is being emptied or not empty anymore due to drag&dropping of modules. + // However, there is a chance a section is being emptied or not empty anymore due to drag&dropping of modules. // So we have to recalculate if we have to enable/disable the sections. disableInvisibleAndEmptySections(sections); } @@ -259,20 +255,21 @@ const disableInvisibleAndEmptySections = (sections) => { }; /** - * Sets the disabled/enabled status of sections in the section select dropdown - * by sectionsRestricted param + * Sets the disabled/enabled status of sections in the section select dropdown: + * Disabled if the section is not available due to some restrictions in block_massaction itself (provided by hooks). * * @param {string} elementId elementId to apply the restriction - * @param {[]} sectionsRestricted the sections which are restrected for the course format */ -const disableRestrictedSections = (elementId, sectionsRestricted) => { +const disableUnavailableSections = (elementId) => { if (document.getElementById(elementId) !== null) { + const sectionsAvailableInfo = document.querySelector(cssIds.SECTION_FILTER_DATA).dataset.availabletargetsections; + const sectionsAvailable = Array.prototype.map.call(sectionsAvailableInfo.split(','), (sectionnum) => parseInt(sectionnum)); Array.prototype.forEach.call(document.getElementById(elementId).options, option => { - // Disable every element which is in the sectionsRestricted list. - if (sectionsRestricted.includes(parseInt(option.value))) { - option.disabled = true; - } else { + // Disable every element which is not in the sectionsAvailable list. + if (sectionsAvailable.includes(parseInt(option.value))) { option.disabled = false; + } else { + option.disabled = true; } }); } diff --git a/amd/src/massactionblock.js b/amd/src/massactionblock.js index 48d159c..36c5294 100644 --- a/amd/src/massactionblock.js +++ b/amd/src/massactionblock.js @@ -60,6 +60,7 @@ export const cssIds = { DUPLICATETO_SELECT: 'block-massaction-control-section-list-duplicateto', HIDDEN_FIELD_REQUEST_INFORMATION: 'block-massaction-control-request', ACTION_FORM: 'block-massaction-control-form', + SECTION_FILTER_DATA: `[data-block-massaction-data="availabletargetsections"]` }; export const constants = { @@ -85,9 +86,8 @@ const actions = { /** * Initialize the mass-action block. - * @param {[]} sectionsRestricted the sections which are restrected for the course format */ -export const init = async(sectionsRestricted) => { +export const init = async() => { const pendingPromise = new Pending('block_massaction/init'); const editor = getCurrentCourseEditor(); @@ -95,7 +95,7 @@ export const init = async(sectionsRestricted) => { editor.stateManager.getInitialPromise() .then(() => { // Initialize the checkbox manager. - checkboxmanager.initCheckboxManager(sectionsRestricted); + checkboxmanager.initCheckboxManager(); // Show block depending on if the moodle bulk editing util has been activated. editor.stateManager.target.addEventListener(events.stateChanged, (event) => { diff --git a/block_massaction.php b/block_massaction.php index 1c478ba..405f7bf 100644 --- a/block_massaction.php +++ b/block_massaction.php @@ -22,6 +22,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use block_massaction\hook\filter_sections_different_course; +use block_massaction\hook\filter_sections_same_course; + /** * Configures and displays the block. * @@ -126,9 +129,13 @@ public function get_content(): stdClass { } } + $modinfo = get_fast_modinfo($COURSE->id); + $filtersectionshook = new filter_sections_same_course($COURSE->id, array_keys($modinfo->get_section_info_all())); + \core\di::get(\core\hook\manager::class)->dispatch($filtersectionshook); + $sectionsavailable = $filtersectionshook->get_sectionnums(); + // Initialize the JS module. - $sectionsrestricted = \block_massaction\massactionutils::get_restricted_sections($COURSE->id, $COURSE->format); - $this->page->requires->js_call_amd('block_massaction/massactionblock', 'init', [$sectionsrestricted]); + $this->page->requires->js_call_amd('block_massaction/massactionblock', 'init'); $context = context_course::instance($COURSE->id); // Actions to be rendered later on. @@ -187,6 +194,7 @@ public function get_content(): stdClass { has_capability('moodle/restore:restoretargetimport', $context) && has_capability('block/massaction:movetosection', $context)), 'sectionselecthelpicon' => $OUTPUT->help_icon('sectionselect', 'block_massaction'), + 'availabletargetsections' => implode(',', $sectionsavailable), ]); } return $this->content; diff --git a/classes/actions.php b/classes/actions.php index 821b71f..0d744ae 100644 --- a/classes/actions.php +++ b/classes/actions.php @@ -29,6 +29,8 @@ use base_setting_exception; use block_massaction\form\course_select_form; use block_massaction\form\section_select_form; +use block_massaction\hook\filter_sections_different_course; +use block_massaction\hook\filter_sections_same_course; use coding_exception; use context_course; use core\event\course_module_updated; @@ -151,13 +153,13 @@ public static function duplicate(array $modules, $sectionnumber = false): void { $cms = []; $errors = []; $duplicatedmods = []; - $targetformat = course_get_format($courseid); - $sectionsrestricted = massactionutils::get_restricted_sections($courseid, $targetformat->get_format()); + $filtersectionshook = new filter_sections_same_course($courseid, array_keys($modinfo->get_section_info_all())); + \core\di::get(\core\hook\manager::class)->dispatch($filtersectionshook); foreach ($idsincourseorder as $cmid) { $cm = $modinfo->get_cm($cmid); // Not duplicated if the section is restricted. - if (in_array($cm->sectionnum, $sectionsrestricted)) { - throw new moodle_exception('sectionrestricted', 'block_massaction'); + if (!in_array($cm->sectionnum, $filtersectionshook->get_sectionnums())) { + throw new moodle_exception('sectionrestricted', 'block_massaction', '', $cm->sectionnum); } try { @@ -244,9 +246,28 @@ public static function duplicate_to_course(array $modules, int $targetcourseid, $targetformat = course_get_format($targetmodinfo->get_course()); $targetsectionnum = $targetformat->get_last_section_number(); - $canaddsection = has_capability('moodle/course:update', context_course::instance($targetcourseid)); + $filtersectionshook = new filter_sections_different_course($targetcourseid, + array_keys($targetmodinfo->get_section_info_all())); + \core\di::get(\core\hook\manager::class)->dispatch($filtersectionshook); + $filteredsections = $filtersectionshook->get_sectionnums(); - // If a new section has been specified, we create one. + if ($targetsectionnum == -1 && !$filtersectionshook->is_keeporiginalsectionallowed()) { + // The course modules should be in the same section number as in the original course. However, the hook listener(s) + // disabled this option, so we cancel the operation. + // This is only a security measure and should not happen unless someone manipulates the UI. + return; + } + + if (!in_array($targetsectionnum, $filteredsections)) { + // The target section number has been filtered by a hook callback, thus must not be used. + // This is only a security measure and should not happen unless someone manipulates the UI. + return; + } + + $canaddsection = has_capability('moodle/course:update', context_course::instance($targetcourseid)) + && $filtersectionshook->is_createnewsectionallowed(); + + // If a new section (that means that $sectionnum of the user is higher than $targetsectionnum), we create one. if ($sectionnum > $targetsectionnum) { // No permissions to add section. if (!$canaddsection) { @@ -298,13 +319,15 @@ public static function duplicate_to_course(array $modules, int $targetcourseid, $duplicatedmods = []; $cms = []; $errors = []; - $sourceformat = course_get_format($sourcecourseid); - $sourcesectionsrestricted = massactionutils::get_restricted_sections($sourcecourseid, $sourceformat->get_format()); + $filtersectionshook = new filter_sections_same_course($sourcecourseid, array_keys($sourcemodinfo->get_section_info_all())); + \core\di::get(\core\hook\manager::class)->dispatch($filtersectionshook); + $sourcefilteredsections = $filtersectionshook->get_sectionnums(); + foreach ($idsincourseorder as $cmid) { $sourcecm = $sourcemodinfo->get_cm($cmid); // Not duplicated if the section is restricted. - if (in_array($sourcecm->sectionnum, $sourcesectionsrestricted)) { - throw new moodle_exception('sectionrestricted', 'block_massaction'); + if (!in_array($sourcecm->sectionnum, $sourcefilteredsections)) { + throw new moodle_exception('sectionrestricted', 'block_massaction', '', $sourcecm->sectionnum); } try { @@ -563,10 +586,14 @@ public static function perform_moveto(array $modules, int $target): void { require_once($CFG->dirroot . '/course/lib.php'); $idsincourseorder = self::sort_course_order($modules); - $sectionsrestricted = []; + if (!empty($modules)) { - $targetformat = course_get_format(reset($modules)->course); - $sectionsrestricted = massactionutils::get_restricted_sections(reset($modules)->course, $targetformat->get_format()); + $courseid = reset($modules)->course; + $filtersectionshook = new filter_sections_same_course( + $courseid, + array_keys(get_fast_modinfo($courseid)->get_section_info_all()) + ); + \core\di::get(\core\hook\manager::class)->dispatch($filtersectionshook); } foreach ($idsincourseorder as $cmid) { @@ -580,8 +607,8 @@ public static function perform_moveto(array $modules, int $target): void { } // Not moving if the section is restricted. - if (in_array($cm->sectionnum, $sectionsrestricted)) { - throw new moodle_exception('sectionrestricted', 'block_massaction'); + if (!in_array($cm->sectionnum, $filtersectionshook->get_sectionnums())) { + throw new moodle_exception('sectionrestricted', 'block_massaction', '', $cm->sectionnum); } // Move each module to the end of their section. diff --git a/classes/form/section_select_form.php b/classes/form/section_select_form.php index 0be261a..0e2dc09 100644 --- a/classes/form/section_select_form.php +++ b/classes/form/section_select_form.php @@ -25,6 +25,7 @@ namespace block_massaction\form; +use block_massaction\hook\filter_sections_different_course; use block_massaction\massactionutils; use core\output\notification; use moodleform; @@ -99,8 +100,13 @@ public function definition() { // Trims off any possible orphaned sections. $targetsections = array_slice($targetsections, 0, $targetsectionnum + 1); + $filtersectionshook = new filter_sections_different_course($targetcourseid, array_keys($targetsections)); + \core\di::get(\core\hook\manager::class)->dispatch($filtersectionshook); + $filteredsections = $filtersectionshook->get_sectionnums(); + // Check for permissions. - $canaddsection = has_capability('moodle/course:update', \context_course::instance($targetcourseid)); + $canaddsection = has_capability('moodle/course:update', \context_course::instance($targetcourseid)) + && $filtersectionshook->is_createnewsectionallowed(); // Find maximum section that may need to be created. $massactionrequest = $this->_customdata['request']; @@ -112,17 +118,16 @@ public function definition() { $radioarray = []; // If user can add sections in target course or don't need to be able to. - if ($canaddsection || $srcmaxsectionnum <= $targetsectionnum) { + if (($canaddsection || $srcmaxsectionnum <= $targetsectionnum) && $filtersectionshook->is_keeporiginalsectionallowed()) { // We add the default value: Restore each course module to the section number it has in the source course. $radioarray[] = $mform->createElement('radio', 'targetsectionnum', '', get_string('keepsectionnum', 'block_massaction'), -1, ['class' => 'mt-2']); } - $sectionsrestricted = massactionutils::get_restricted_sections($targetcourseid, $targetformat->get_format()); // Now add the sections of the target course. foreach ($targetsections as $sectionnum => $sectionname) { $attributes = ['class' => 'mt-2']; - if (in_array($sectionnum, $sectionsrestricted)) { + if (!in_array($sectionnum, $filteredsections)) { $attributes['disabled'] = 'disabled'; } $radioarray[] = $mform->createElement('radio', 'targetsectionnum', diff --git a/classes/hook/filter_sections_different_course.php b/classes/hook/filter_sections_different_course.php new file mode 100644 index 0000000..984e4e5 --- /dev/null +++ b/classes/hook/filter_sections_different_course.php @@ -0,0 +1,77 @@ +. + +namespace block_massaction\hook; + +defined('MOODLE_INTERNAL') || die(); + +#[\core\attribute\label('Hook dispatched when block_massaction is duplicating activities into another course. ' + . 'The hook provides ways to customize which sections the user can duplicate activities to.')] +#[\core\attribute\tags('block_massaction')] +/** + * Hook class for filtering a list of target sections when duplicating into another course. + * + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @package block_massaction + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class filter_sections_different_course { + + use filter_sections_handler; + + /** @var bool Determines if the user will be able to keep the original section of a course module when performing some operations. */ + private bool $keeporiginalsectionallowed = true; + + /** @var bool Determines if the user will be able to create a new section when performing some operations. */ + private bool $createnewsectionallowed = true; + + /** + * Disables the option to keep the original section of a course module. + */ + public function disable_keeporiginalsection(): void { + $this->keeporiginalsectionallowed = false; + } + + /** + * Returns if the option to keep the course modules in the original section when duplicating or not. + * + * This information is only used in the case that the target course is different from the one that contains the course modules. + * + * @return bool if the user will be allowed to keep the original section of the course modules + */ + public function is_keeporiginalsectionallowed(): bool { + return $this->keeporiginalsectionallowed; + } + + /** + * Disables the option to create a new section. + */ + public function disable_createnewsection(): void { + $this->createnewsectionallowed = false; + } + + /** + * Returns if the option to create a new section is allowed or not. + * + * This information is only used in the case that the target course is different from the one that contains the course modules. + * + * @return bool if the user will be allowed to create a new section + */ + public function is_createnewsectionallowed(): bool { + return $this->createnewsectionallowed; + } +} diff --git a/classes/hook/filter_sections_handler.php b/classes/hook/filter_sections_handler.php new file mode 100644 index 0000000..413a6bc --- /dev/null +++ b/classes/hook/filter_sections_handler.php @@ -0,0 +1,92 @@ +. + +namespace block_massaction\hook; + +use coding_exception; + +/** + * Trait for providing the common methods for the filter sections hooks. + * + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @package block_massaction + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +trait filter_sections_handler { + + /** @var array Array of section numbers which originally are available for block_massaction. */ + private readonly array $originalsectionnums; + + /** + * Creates the hook object. + * + * @param int $courseid the course id which is target for section select + * @param array $sectionnums the section numbers which are available (so the available sections the hook listeners may filter) + */ + public function __construct( + private readonly int $courseid, + private array $sectionnums + ) { + $this->originalsectionnums = $this->sectionnums; + } + + /** + * Getter for the available sections without any changes by any hook listener. + * + * @return array array of section numbers which are available by block_massaction + */ + public function get_original_sectionnums(): array { + return $this->originalsectionnums; + } + + /** + * Getter for the course id the section numbers are referring to. + * + * You can determine if this course id belongs to the same course + * + * @return int + */ + public function get_courseid(): int { + return $this->courseid; + } + + /** + * Getter for the currently available section numbers. + * + * This will be evaluated by block_massaction to determine the available sections. + * + * @return array array of available section numbers (integers) + */ + public function get_sectionnums(): array { + return array_values($this->sectionnums); + } + + /** + * Remove a section number from the list of available/allowed section numbers. + * + * Does nothing if a section number is passed which is not contained in the list of currently available sections + * + * @param int $sectionnum The section number to remove from the list + */ + public function remove_sectionnum(int $sectionnum): void { + $index = array_search($sectionnum, $this->sectionnums); + if ($index === false) { + return; + } + unset($this->sectionnums[$index]); + } +} diff --git a/classes/hook/filter_sections_same_course.php b/classes/hook/filter_sections_same_course.php new file mode 100644 index 0000000..aa50245 --- /dev/null +++ b/classes/hook/filter_sections_same_course.php @@ -0,0 +1,37 @@ +. + +namespace block_massaction\hook; + +defined('MOODLE_INTERNAL') || die(); + +#[\core\attribute\label('Hook dispatched when block_massaction is duplicating or moving activities inside a course. ' + . 'The hook provides ways to customize which sections the user can duplicate/move activities to.')] +#[\core\attribute\tags('block_massaction')] +/** + * Hook class for filtering a list of target sections when duplicating/moving inside a course. + * + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @package block_massaction + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class filter_sections_same_course { + + // We use the trait here, because inheritance is not recommended for hooks. + use filter_sections_handler; + +} diff --git a/classes/massactionutils.php b/classes/massactionutils.php index a5816fa..07ca39d 100644 --- a/classes/massactionutils.php +++ b/classes/massactionutils.php @@ -164,21 +164,4 @@ public static function duplicate_cm_to_course(object $course, object $cm): int { } return $newcmid; } - - /** - * Get array of restricted sections from course format callback. - * Example return values from pluginname_massaction_restricted_sections: [1, 3, 5] - * - * @param int $courseid - * @param string $format - * @return array - */ - public static function get_restricted_sections($courseid, $format): array { - $sectionsrestricted = []; - $callbacks = get_plugins_with_function('massaction_restricted_sections'); - if (!empty($callbacks['format'][$format])) { - $sectionsrestricted = $callbacks['format'][$format]($courseid); - } - return $sectionsrestricted; - } } diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index c00ddf0..f73acb1 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -34,16 +34,13 @@ */ class provider implements \core_privacy\local\metadata\null_provider { - // To provide php 5.6 (33_STABLE) and up support. - use \core_privacy\local\legacy_polyfill; - /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ - public static function get_reason() : string { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/lang/en/block_massaction.php b/lang/en/block_massaction.php index 018840e..8fefc91 100644 --- a/lang/en/block_massaction.php +++ b/lang/en/block_massaction.php @@ -22,48 +22,25 @@ * @author Philipp Memmel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -$string['pluginname'] = 'Mass Actions'; - -$string['massaction:activityshowhide'] = 'MassAction: Show/Hide modules'; -$string['massaction:addinstance'] = 'Add a new Mass Actions Block'; -$string['massaction:delete'] = 'MassAction: Delete modules'; -$string['massaction:descriptionshowhide'] = 'MassAction: Show/Hide Descriptions'; -$string['massaction:duplicate'] = 'MassAction: Duplicate modules'; -$string['massaction:duplicatetocourse'] = 'MassAction: Duplicate to Course'; -$string['massaction:indent'] = 'MassAction: Indent modules'; -$string['massaction:movetosection'] = 'MassAction: Move to Section'; -$string['massaction:use'] = 'Use the Mass Actions block'; -$string['massaction:sendcontentchangednotifications'] = 'Send content changed notifications'; - -$string['blockname'] = 'Mass Actions'; -$string['blocktitle'] = 'Mass Actions'; -$string['privacy:metadata'] = 'This block only offers the possibility to apply standard operations on multiple course modules at the same time. -Thus, no data is being stored by this block.'; - -$string['selectall'] = 'Select all'; -$string['selectallinsection'] = 'Select all in section'; -$string['deselectall'] = 'Deselect all'; -$string['withselected'] = 'With selected'; -$string['sectionselect'] = 'Section selection'; -$string['sectionselect_help'] = 'You can only select sections which include at least one course module. -Additionally, when using the Tiles or One Topic course format you can only select sections which are currently visible.'; - -$string['actionexecuted'] = 'The action you requested has been executed.'; $string['action_contentchangednotification'] = 'Send content changed notification'; $string['action_delete'] = 'Delete'; $string['action_duplicate'] = 'Duplicate'; $string['action_duplicatetocourse'] = 'Duplicate to another course'; $string['action_duplicatetosection'] = 'Duplicate to section'; $string['action_hide'] = 'Hide'; +$string['action_hidedescription'] = 'Hide description'; $string['action_makeavailable'] = 'Make available'; $string['action_moveleft'] = 'Outdent (move left)'; $string['action_moveright'] = 'Indent (move right)'; $string['action_movetosection'] = 'Move to section'; $string['action_show'] = 'Show'; $string['action_showdescription'] = 'Show description'; -$string['action_hidedescription'] = 'Hide description'; +$string['actionexecuted'] = 'The action you requested has been executed.'; +$string['applicablecourseformats'] = 'Applicable course formats'; +$string['applicablecourseformats_description'] = 'Mass Actions block will only be available for the selected course formats.
Preselected defaults are the ones tested and supported by the plugin maintainer. Add other formats at your own risk.'; $string['backgroundtaskinformation'] = 'The action you demanded is being executed in the background. You can continue your work while waiting for it to finish.'; +$string['blockname'] = 'Mass Actions'; +$string['blocktitle'] = 'Mass Actions'; $string['bulkeditingdisabled'] = 'To use this block, you need to enable bulk editing mode.'; $string['choosecoursetoduplicateto'] = 'Choose the course you want to duplicate the selected course modules to'; $string['choosesectiontoduplicateto'] = 'Choose the section you want the selected course modules to be duplicated to.'; @@ -73,41 +50,58 @@ $string['confirmsectionselect'] = 'Choose section'; $string['deletecheck'] = 'Confirm mass deletion'; $string['deletecheckconfirm'] = 'Are you sure you want to delete the following module(s)?'; +$string['deselectall'] = 'Deselect all'; $string['duplicatefailed'] = 'Could not duplicate course module from id {$a}'; $string['duplicatemaxactivities'] = 'Maximum amount of course modules to duplicate'; $string['duplicatemaxactivities_description'] = 'Maximum amount of course modules which can be duplicated at the same time without running the process as background task. If set to "0" all duplication operations will be run as background task.'; $string['enablebulkediting'] = 'Enable bulk editing'; $string['event:course_modules_duplicated'] = 'Course modules duplicated'; $string['event:course_modules_duplicated_failed'] = 'Course modules failed to duplicate'; -$string['event:duplicated_description'] = 'cmid from \'{$a->src}\' to \'{$a->dst}\''; -$string['event:duplicated_summary'] = 'Course module duplication has been completed. Summary: {$a->countcomplete} Completed, {$a->countfailed} Failed.'; $string['event:duplicated_completed_list'] = 'Completed {$a->list}.'; -$string['event:duplicated_failed_list'] = 'Failed {$a->list}.'; +$string['event:duplicated_description'] = 'cmid from \'{$a->src}\' to \'{$a->dst}\''; $string['event:duplicated_failed_description'] = 'Course module duplication failed. cmid: {$a->cmid} error: {$a->error}'; +$string['event:duplicated_failed_list'] = 'Failed {$a->list}.'; +$string['event:duplicated_summary'] = 'Course module duplication has been completed. Summary: {$a->countcomplete} Completed, {$a->countfailed} Failed.'; $string['invalidaction'] = 'Unknown action: {$a}'; -$string['invalidmoduleid'] = 'Invalid module ID: {$a}'; -$string['invalidcoursemodule'] = 'Invalid course module'; $string['invalidcourseid'] = 'Invalid course ID'; +$string['invalidcoursemodule'] = 'Invalid course module'; +$string['invalidmoduleid'] = 'Invalid module ID: {$a}'; $string['jsonerror'] = 'Error coding: Invalid JSON format'; +$string['keepsectionnum'] = 'Keep original section number'; $string['limittoenrolled'] = 'Limit target course list to courses in which the user is enrolled'; $string['limittoenrolled_description'] = 'If enabled the course selection of the feature "Duplicate to another course" will be limited to courses in which the user is enrolled. Enabling this is recommended for instances with many courses, because not limiting the courses is likely to result in performance issues and timeouts. Disabling this option is at one own\'s risk.'; -$string['keepsectionnum'] = 'Keep original section number'; +$string['massaction:activityshowhide'] = 'MassAction: Show/Hide modules'; +$string['massaction:addinstance'] = 'Add a new Mass Actions Block'; +$string['massaction:delete'] = 'MassAction: Delete modules'; +$string['massaction:descriptionshowhide'] = 'MassAction: Show/Hide Descriptions'; +$string['massaction:duplicate'] = 'MassAction: Duplicate modules'; +$string['massaction:duplicatetocourse'] = 'MassAction: Duplicate to Course'; +$string['massaction:indent'] = 'MassAction: Indent modules'; +$string['massaction:movetosection'] = 'MassAction: Move to Section'; +$string['massaction:sendcontentchangednotifications'] = 'Send content changed notifications'; +$string['massaction:use'] = 'Use the Mass Actions block'; $string['modulename'] = 'Activity name'; $string['moduletype'] = 'Activity type'; $string['multipleinstances'] = 'There must not be multiple instances of this block on the same page.
Please remove additional instances.'; $string['newsection'] = 'New Section'; -$string['noitemselected'] = 'Please select at least one item to apply the mass-action'; $string['noaction'] = 'No action specified'; $string['noactionsavailable'] = 'You do not have the permissions to execute any of the possible operations this block is providing'; $string['nocaptobackup'] = 'You do not have sufficient permissions to perform a backup in the course'; $string['nocaptorestore'] = 'You do not have sufficient permissions to perform a restore in the course'; +$string['noitemselected'] = 'Please select at least one item to apply the mass-action'; $string['nomovingtargetselected'] = 'Please select a target section'; $string['notargetcourseidspecified'] = 'No target course id has been specified'; +$string['pluginname'] = 'Mass Actions'; +$string['privacy:metadata'] = 'This block only offers the possibility to apply standard operations on multiple course modules at the same time. +Thus, no data is being stored by this block.'; $string['sectionnotexist'] = 'Target section does not exist'; -$string['sectionrestricted'] = 'Source section is restricted'; +$string['sectionrestricted'] = 'Source section is restricted: You are not allowed to apply an action to activities in section {$a}.'; +$string['sectionselect'] = 'Section selection'; +$string['sectionselect_help'] = 'You can only select sections which include at least one course module. +Additionally, when using the Tiles or One Topic course format you can only select sections which are currently visible.'; +$string['selectall'] = 'Select all'; +$string['selectallinsection'] = 'Select all in section'; $string['sourcecourseidlost'] = 'Source course id could not be found'; -$string['applicablecourseformats'] = 'Applicable course formats'; -$string['applicablecourseformats_description'] = 'Mass Actions block will only be available for the selected course formats.
Preselected defaults are the ones tested and supported by the plugin maintainer. Add other formats at your own risk.'; $string['unusable'] = 'The mass action functionality cannot be used in this course format or the current theme'; $string['usage'] = 'Usage of the Mass Actions Block'; $string['usage_help'] = '

This block allows instructors to perform actions upon multiple resources or activities in the class view, rather than having to perform repeated actions on individual items.

@@ -115,3 +109,4 @@

Supported actions include deletion, in-/outdentation, hiding/revealing and moving. To select items to perform actions on, simply click the checkbox to the left of it in the course home page, or you may select all items, or select all items in a section using the block. To perform actions, click the action you would like to perform inside the block.

'; +$string['withselected'] = 'With selected'; diff --git a/templates/block_massaction.mustache b/templates/block_massaction.mustache index 1ee0d35..1ee6190 100644 --- a/templates/block_massaction.mustache +++ b/templates/block_massaction.mustache @@ -32,7 +32,8 @@ "formaction": "doSomething.php", "instanceid": "1", "requesturl": "doSomething.php", - "helpicon": "help.gif" + "helpicon": "help.gif", + "availabletargetsections": "1,3,5" } }}
@@ -119,4 +120,5 @@
{{{helpicon}}}
+
diff --git a/tests/massaction_test.php b/tests/massaction_test.php index f03b9d3..04cbec0 100644 --- a/tests/massaction_test.php +++ b/tests/massaction_test.php @@ -37,7 +37,7 @@ * @author Philipp Memmel * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class massaction_test extends advanced_testcase { +final class massaction_test extends advanced_testcase { /** * @var stdClass Course record. */ diff --git a/version.php b/version.php index 202c8d3..82e787c 100644 --- a/version.php +++ b/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2024011601; +$plugin->version = 2024011602; $plugin->requires = 2023100900; $plugin->component = 'block_massaction'; $plugin->maturity = MATURITY_ALPHA;