diff --git a/.upgradenotes/MDL-52046-2024061805141748.yml b/.upgradenotes/MDL-52046-2024061805141748.yml new file mode 100644 index 00000000000..4e0600dacce --- /dev/null +++ b/.upgradenotes/MDL-52046-2024061805141748.yml @@ -0,0 +1,8 @@ +issueNumber: MDL-52046 +notes: + core_reportbuilder: + - message: >- + The return type of the `set_checkbox_toggleall` callback, defined by + system reports, can now be null. Use if the checkbox should not be shown + for the row. + type: improved diff --git a/cohort/amd/build/actions.min.js b/cohort/amd/build/actions.min.js new file mode 100644 index 00000000000..c5d49ea3974 --- /dev/null +++ b/cohort/amd/build/actions.min.js @@ -0,0 +1,10 @@ +define("core_cohort/actions",["exports","core/event_dispatcher","core/notification","core/pending","core/prefetch","core/str","core_cohort/repository","core_reportbuilder/local/events","core_reportbuilder/local/selectors","core/local/inplace_editable/events"],(function(_exports,_event_dispatcher,_notification,_pending,_prefetch,_str,_repository,reportEvents,reportSelectors,_events2){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Cohorts actions. + * + * @module core_cohort/actions + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.registerEventListeners=_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),reportEvents=_interopRequireWildcard(reportEvents),reportSelectors=_interopRequireWildcard(reportSelectors);const SELECTORS_CHECKBOXES='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',SELECTORS_DELETEBUTTON='[data-action="cohort-delete-selected"]',SELECTORS_EDITNAME='[data-itemtype="cohortname"]';_exports.init=()=>{(0,_prefetch.prefetchStrings)("core_cohort",["delcohortsconfirm"]),(0,_prefetch.prefetchStrings)("core",["delete","deleteselected","selectitem"]),registerEventListeners()};const registerEventListeners=()=>{document.addEventListener(_events2.eventTypes.elementUpdated,(async event=>{if(event.target.closest(SELECTORS_EDITNAME)){const newName=await(0,_str.getString)("selectitem","core",event.target.dataset.value),cohortId=event.target.dataset.itemid,checkbox=document.querySelector('input[value="'.concat(cohortId,'"]')),label=document.querySelector('label[for="'.concat(checkbox.id,'"]'));newName&&label&&(label.innerHTML=newName)}})),document.addEventListener("click",(event=>{const cohortDeleteSelected=event.target.closest(SELECTORS_DELETEBUTTON);if(cohortDeleteSelected){event.preventDefault();const reportElement=document.querySelector(reportSelectors.regions.report),cohortDeleteChecked=reportElement.querySelectorAll(SELECTORS_CHECKBOXES);if(0===cohortDeleteChecked.length)return;_notification.default.saveCancelPromise((0,_str.getString)("deleteselected","core"),(0,_str.getString)("delcohortsconfirm","core_cohort"),(0,_str.getString)("delete","core"),{triggerElement:cohortDeleteSelected}).then((()=>{const pendingPromise=new _pending.default("core_cohort/cohorts:delete"),deleteCohortIds=[...cohortDeleteChecked].map((check=>check.value));return(0,_repository.deleteCohorts)(deleteCohortIds).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}}))};_exports.registerEventListeners=registerEventListeners})); + +//# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/cohort/amd/build/actions.min.js.map b/cohort/amd/build/actions.min.js.map new file mode 100644 index 00000000000..7f777fd08ae --- /dev/null +++ b/cohort/amd/build/actions.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"actions.min.js","sources":["../src/actions.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 * Cohorts actions.\n *\n * @module core_cohort/actions\n * @copyright 2024 David Woloszyn \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport {deleteCohorts} from 'core_cohort/repository';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\nimport {eventTypes} from 'core/local/inplace_editable/events';\n\nconst SELECTORS = {\n CHECKBOXES: '[data-togglegroup=\"report-select-all\"][data-toggle=\"slave\"]:checked',\n DELETEBUTTON: '[data-action=\"cohort-delete-selected\"]',\n EDITNAME: '[data-itemtype=\"cohortname\"]',\n};\n\n/**\n * Initialise module.\n */\nexport const init = () => {\n\n prefetchStrings('core_cohort', [\n 'delcohortsconfirm',\n ]);\n\n prefetchStrings('core', [\n 'delete',\n 'deleteselected',\n 'selectitem',\n ]);\n\n registerEventListeners();\n};\n\n/**\n * Register event listeners.\n */\nexport const registerEventListeners = () => {\n\n // Edit cohort name inplace.\n document.addEventListener(eventTypes.elementUpdated, async(event) => {\n\n const editCohortName = event.target.closest(SELECTORS.EDITNAME);\n\n if (editCohortName) {\n const newName = await getString('selectitem', 'core', event.target.dataset.value);\n const cohortId = event.target.dataset.itemid;\n const checkbox = document.querySelector(`input[value=\"${cohortId}\"]`);\n const label = document.querySelector(`label[for=\"${checkbox.id}\"]`);\n\n if (newName && label) {\n label.innerHTML = newName;\n }\n }\n });\n\n // Delete multiple cohorts.\n document.addEventListener('click', event => {\n\n const cohortDeleteSelected = event.target.closest(SELECTORS.DELETEBUTTON);\n\n if (cohortDeleteSelected) {\n event.preventDefault();\n\n const reportElement = document.querySelector(reportSelectors.regions.report);\n const cohortDeleteChecked = reportElement.querySelectorAll(SELECTORS.CHECKBOXES);\n if (cohortDeleteChecked.length === 0) {\n return;\n }\n\n Notification.saveCancelPromise(\n getString('deleteselected', 'core'),\n getString('delcohortsconfirm', 'core_cohort'),\n getString('delete', 'core'),\n {triggerElement: cohortDeleteSelected}\n ).then(() => {\n const pendingPromise = new Pending('core_cohort/cohorts:delete');\n const deleteCohortIds = [...cohortDeleteChecked].map(check => check.value);\n\n // eslint-disable-next-line promise/no-nesting\n return deleteCohorts(deleteCohortIds)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n });\n};\n"],"names":["SELECTORS","registerEventListeners","document","addEventListener","eventTypes","elementUpdated","async","event","target","closest","newName","dataset","value","cohortId","itemid","checkbox","querySelector","label","id","innerHTML","cohortDeleteSelected","preventDefault","reportElement","reportSelectors","regions","report","cohortDeleteChecked","querySelectorAll","length","saveCancelPromise","triggerElement","then","pendingPromise","Pending","deleteCohortIds","map","check","reportEvents","tableReload","preservePagination","resolve","catch","Notification","exception"],"mappings":";;;;;;;kUAiCMA,qBACU,sEADVA,uBAEY,yCAFZA,mBAGQ,6CAMM,mCAEA,cAAe,CAC3B,oDAGY,OAAQ,CACpB,SACA,iBACA,eAGJC,gCAMSA,uBAAyB,KAGlCC,SAASC,iBAAiBC,oBAAWC,gBAAgBC,MAAAA,WAE1BC,MAAMC,OAAOC,QAAQT,oBAExB,OACVU,cAAgB,kBAAU,aAAc,OAAQH,MAAMC,OAAOG,QAAQC,OACrEC,SAAWN,MAAMC,OAAOG,QAAQG,OAChCC,SAAWb,SAASc,qCAA8BH,gBAClDI,MAAQf,SAASc,mCAA4BD,SAASG,UAExDR,SAAWO,QACXA,MAAME,UAAYT,aAM9BR,SAASC,iBAAiB,SAASI,cAEzBa,qBAAuBb,MAAMC,OAAOC,QAAQT,2BAE9CoB,qBAAsB,CACtBb,MAAMc,uBAEAC,cAAgBpB,SAASc,cAAcO,gBAAgBC,QAAQC,QAC/DC,oBAAsBJ,cAAcK,iBAAiB3B,yBACxB,IAA/B0B,oBAAoBE,oCAIXC,mBACT,kBAAU,iBAAkB,SAC5B,kBAAU,oBAAqB,gBAC/B,kBAAU,SAAU,QACpB,CAACC,eAAgBV,uBACnBW,MAAK,WACGC,eAAiB,IAAIC,iBAAQ,8BAC7BC,gBAAkB,IAAIR,qBAAqBS,KAAIC,OAASA,MAAMxB,eAG7D,6BAAcsB,iBAChBH,MAAK,yCACYM,aAAaC,YAAa,CAACC,oBAAoB,GAAOjB,eAC7DU,eAAeQ,aAEzBC,MAAMC,sBAAaC,cACzBF,OAAM"} \ No newline at end of file diff --git a/cohort/amd/build/repository.min.js b/cohort/amd/build/repository.min.js new file mode 100644 index 00000000000..76f8244785f --- /dev/null +++ b/cohort/amd/build/repository.min.js @@ -0,0 +1,10 @@ +define("core_cohort/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj; +/** + * Module to handle cohort AJAX requests. + * + * @module core_cohort/repository + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.deleteCohorts=_exports.deleteCohort=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.deleteCohort=cohort=>deleteCohorts([cohort]);const deleteCohorts=cohorts=>{const request={methodname:"core_cohort_delete_cohorts",args:{cohortids:cohorts}};return _ajax.default.call([request])[0]};_exports.deleteCohorts=deleteCohorts})); + +//# sourceMappingURL=repository.min.js.map \ No newline at end of file diff --git a/cohort/amd/build/repository.min.js.map b/cohort/amd/build/repository.min.js.map new file mode 100644 index 00000000000..893bb2c2bdc --- /dev/null +++ b/cohort/amd/build/repository.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"repository.min.js","sources":["../src/repository.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 * Module to handle cohort AJAX requests.\n *\n * @module core_cohort/repository\n * @copyright 2024 David Woloszyn \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Delete single cohort.\n *\n * @param {Number} cohort Cohort ID\n * @return {Promise}\n */\nexport const deleteCohort = cohort => deleteCohorts([cohort]);\n\n/**\n * Delete multiple cohorts.\n *\n * @param {Number[]} cohorts Cohort IDs\n * @return {Promise}\n */\nexport const deleteCohorts = cohorts => {\n const request = {\n methodname: 'core_cohort_delete_cohorts',\n args: {cohortids: cohorts}\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["cohort","deleteCohorts","cohorts","request","methodname","args","cohortids","Ajax","call"],"mappings":";;;;;;;2LA+B4BA,QAAUC,cAAc,CAACD,eAQxCC,cAAgBC,gBACnBC,QAAU,CACZC,WAAY,6BACZC,KAAM,CAACC,UAAWJ,iBAGfK,cAAKC,KAAK,CAACL,UAAU"} \ No newline at end of file diff --git a/cohort/amd/src/actions.js b/cohort/amd/src/actions.js new file mode 100644 index 00000000000..e529714f798 --- /dev/null +++ b/cohort/amd/src/actions.js @@ -0,0 +1,115 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Cohorts actions. + * + * @module core_cohort/actions + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {dispatchEvent} from 'core/event_dispatcher'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import {prefetchStrings} from 'core/prefetch'; +import {getString} from 'core/str'; +import {deleteCohorts} from 'core_cohort/repository'; +import * as reportEvents from 'core_reportbuilder/local/events'; +import * as reportSelectors from 'core_reportbuilder/local/selectors'; +import {eventTypes} from 'core/local/inplace_editable/events'; + +const SELECTORS = { + CHECKBOXES: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked', + DELETEBUTTON: '[data-action="cohort-delete-selected"]', + EDITNAME: '[data-itemtype="cohortname"]', +}; + +/** + * Initialise module. + */ +export const init = () => { + + prefetchStrings('core_cohort', [ + 'delcohortsconfirm', + ]); + + prefetchStrings('core', [ + 'delete', + 'deleteselected', + 'selectitem', + ]); + + registerEventListeners(); +}; + +/** + * Register event listeners. + */ +export const registerEventListeners = () => { + + // Edit cohort name inplace. + document.addEventListener(eventTypes.elementUpdated, async(event) => { + + const editCohortName = event.target.closest(SELECTORS.EDITNAME); + + if (editCohortName) { + const newName = await getString('selectitem', 'core', event.target.dataset.value); + const cohortId = event.target.dataset.itemid; + const checkbox = document.querySelector(`input[value="${cohortId}"]`); + const label = document.querySelector(`label[for="${checkbox.id}"]`); + + if (newName && label) { + label.innerHTML = newName; + } + } + }); + + // Delete multiple cohorts. + document.addEventListener('click', event => { + + const cohortDeleteSelected = event.target.closest(SELECTORS.DELETEBUTTON); + + if (cohortDeleteSelected) { + event.preventDefault(); + + const reportElement = document.querySelector(reportSelectors.regions.report); + const cohortDeleteChecked = reportElement.querySelectorAll(SELECTORS.CHECKBOXES); + if (cohortDeleteChecked.length === 0) { + return; + } + + Notification.saveCancelPromise( + getString('deleteselected', 'core'), + getString('delcohortsconfirm', 'core_cohort'), + getString('delete', 'core'), + {triggerElement: cohortDeleteSelected} + ).then(() => { + const pendingPromise = new Pending('core_cohort/cohorts:delete'); + const deleteCohortIds = [...cohortDeleteChecked].map(check => check.value); + + // eslint-disable-next-line promise/no-nesting + return deleteCohorts(deleteCohortIds) + .then(() => { + dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); + return pendingPromise.resolve(); + }) + .catch(Notification.exception); + }).catch(() => { + return; + }); + } + }); +}; diff --git a/cohort/amd/src/repository.js b/cohort/amd/src/repository.js new file mode 100644 index 00000000000..8bd52578e63 --- /dev/null +++ b/cohort/amd/src/repository.js @@ -0,0 +1,47 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Module to handle cohort AJAX requests. + * + * @module core_cohort/repository + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; + +/** + * Delete single cohort. + * + * @param {Number} cohort Cohort ID + * @return {Promise} + */ +export const deleteCohort = cohort => deleteCohorts([cohort]); + +/** + * Delete multiple cohorts. + * + * @param {Number[]} cohorts Cohort IDs + * @return {Promise} + */ +export const deleteCohorts = cohorts => { + const request = { + methodname: 'core_cohort_delete_cohorts', + args: {cohortids: cohorts} + }; + + return Ajax.call([request])[0]; +}; diff --git a/cohort/classes/reportbuilder/local/systemreports/cohorts.php b/cohort/classes/reportbuilder/local/systemreports/cohorts.php index 91825c73e22..84e2d151b08 100644 --- a/cohort/classes/reportbuilder/local/systemreports/cohorts.php +++ b/cohort/classes/reportbuilder/local/systemreports/cohorts.php @@ -59,7 +59,18 @@ class cohorts extends system_report { // Any columns required by actions should be defined here to ensure they're always available. $this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.contextid, {$entitymainalias}.visible, " . - "{$entitymainalias}.component"); + "{$entitymainalias}.component, {$entitymainalias}.name"); + + $this->set_checkbox_toggleall(static function(stdClass $cohort): ?array { + if (!has_capability('moodle/cohort:manage', context::instance_by_id($cohort->contextid))) { + return null; + } + + return [ + $cohort->id, + get_string('selectitem', 'moodle', $cohort->name), + ]; + }); // Check if report needs to show a specific category. $contextid = $this->get_parameter('contextid', 0, PARAM_INT); diff --git a/cohort/index.php b/cohort/index.php index 9b40c5db09c..47dd29d11a8 100644 --- a/cohort/index.php +++ b/cohort/index.php @@ -115,5 +115,21 @@ if (!empty($searchquery)) { ]); } +// Show the report. echo $report->output(); + +// Show the delete selected button if there are records. +if ($DB->record_exists('cohort', [])) { + + echo $OUTPUT->render(new single_button( + new moodle_url('#'), + get_string('deleteselected'), + 'post', + single_button::BUTTON_PRIMARY, + ['data-action' => 'cohort-delete-selected'] + )); + + $PAGE->requires->js_call_amd('core_cohort/actions', 'init'); +} + echo $OUTPUT->footer(); diff --git a/cohort/tests/behat/delete_cohorts.feature b/cohort/tests/behat/delete_cohorts.feature new file mode 100644 index 00000000000..8ad66c67fe9 --- /dev/null +++ b/cohort/tests/behat/delete_cohorts.feature @@ -0,0 +1,28 @@ +@core @core_cohort +Feature: Delete cohorts + In order to delete multiple cohorts + As an admin + I need to select specific cohorts and perform that action + + Background: + Given the following "cohorts" exist: + | name | idnumber | contextlevel | reference | description | + | Cohort 1 | cohort1 | System | | About cohort 1 | + | Cohort 2 | cohort2 | System | | About cohort 2 | + | Cohort 3 | cohort3 | System | | About cohort 3 | + + @javascript + Scenario: I can delete multiple cohorts using the checkboxes + Given I log in as "admin" + And I navigate to "Users > Accounts > Cohorts" in site administration + And I should see "Cohort 1" + And I should see "Cohort 2" + And I should see "Cohort 3" + And I click on "Select 'Cohort 1'" "checkbox" + And I click on "Select 'Cohort 2'" "checkbox" + When I click on "Delete selected" "button" + Then I should see "Do you really want to delete the selected cohorts?" + And I click on "Delete" "button" in the "Delete selected" "dialogue" + And I should not see "Cohort 1" + And I should not see "Cohort 2" + And I should see "Cohort 3" diff --git a/lang/en/cohort.php b/lang/en/cohort.php index b913521a1c0..93c94e0f205 100644 --- a/lang/en/cohort.php +++ b/lang/en/cohort.php @@ -47,6 +47,7 @@ $string['currentusers'] = 'Current users'; $string['currentusersmatching'] = 'Current users matching'; $string['defaultcontext'] = 'Default context'; $string['delcohort'] = 'Delete cohort'; +$string['delcohortsconfirm'] = 'Do you really want to delete the selected cohorts?'; $string['delconfirm'] = 'Do you really want to delete cohort \'{$a}\'?'; $string['description'] = 'Description'; $string['displayedrows'] = '{$a->displayed} rows displayed out of {$a->total}.'; diff --git a/lib/db/services.php b/lib/db/services.php index 0fffbe9295f..feb0ce346c4 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -373,7 +373,8 @@ $functions = array( 'classpath' => 'cohort/externallib.php', 'description' => 'Deletes all specified cohorts.', 'type' => 'write', - 'capabilities' => 'moodle/cohort:manage' + 'capabilities' => 'moodle/cohort:manage', + 'ajax' => true, ), 'core_cohort_get_cohort_members' => array( 'classname' => 'core_cohort_external', diff --git a/reportbuilder/classes/system_report.php b/reportbuilder/classes/system_report.php index ad3f66adc11..c7fb8ae2dac 100644 --- a/reportbuilder/classes/system_report.php +++ b/reportbuilder/classes/system_report.php @@ -144,7 +144,7 @@ abstract class system_report extends base { * Define toggle all checkbox for the report, required row data should be defined by calling {@see add_base_fields} * * @param callable $callback Callback to return value/label for each checkbox, implementing the following signature: - * function(stdClass $row): array containing value/label pair + * function(stdClass $row): ?array containing value/label pair, or null if the checkbox should not be shown for the row */ final protected function set_checkbox_toggleall(callable $callback): void { $this->checkboxcallback = $callback; @@ -167,7 +167,11 @@ abstract class system_report extends base { $value = ''; $label = get_string('selectall'); } else { - [$value, $label] = ($this->checkboxcallback)($row); + $checkboxdata = ($this->checkboxcallback)($row); + if ($checkboxdata === null) { + return null; + } + [$value, $label] = $checkboxdata; } return new checkbox_toggleall('report-select-all', $ismaster, [ diff --git a/version.php b/version.php index b9719c53492..7b147de2472 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024062700.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024070300.00; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.5dev (Build: 20240627)'; // Human-friendly version name