MDL-71378 qbank_bulkmove: refactor for moving to shared question banks

This commit is contained in:
Simon Adams 2024-08-12 15:25:41 +01:00
parent 746efe94eb
commit a6c4ddada5
32 changed files with 1067 additions and 175 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -490,6 +490,8 @@ define([
/**
* Rebuild the list of suggestions based on the current values in the select list, and the query.
* Any options in the original select with [data-enabled=disabled] will not be included
* as a suggestion option in the enhanced field.
*
* @method updateSuggestions
* @private
@ -510,7 +512,7 @@ define([
// Used to track if we found any visible suggestions.
var matchingElements = false;
// Options is used by the context when rendering the suggestions from a template.
var suggestions = rebuildOptions(originalSelect.children('option:not(:selected)'), true);
var suggestions = rebuildOptions(originalSelect.children('option:not(:selected, [data-enabled="disabled"])'), true);
// Re-render the list of suggestions.
var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();

View File

@ -3270,6 +3270,12 @@ $functions = array(
'type' => 'write',
'ajax' => true,
],
'core_question_move_questions' => [
'classname' => '\core_question\external\move_questions',
'description' => 'Bulk move questions to a new category.',
'type' => 'write',
'ajax' => true,
],
);
$services = array(

11
question/amd/build/repository.min.js vendored Normal file
View File

@ -0,0 +1,11 @@
define("core_question/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* A javascript module to handle core_question ajax actions.
*
* @module core_question/repository
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Simon Adams <simon.adams@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.moveQuestions=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.moveQuestions=function(newContextId,newCategoryId,questionIds){let returnUrl=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"";return _ajax.default.call([{methodname:"core_question_move_questions",args:{newcontextid:newContextId,newcategoryid:newCategoryId,questionids:questionIds,returnurl:returnUrl}}])[0]}}));
//# sourceMappingURL=repository.min.js.map

View File

@ -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 <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle core_question ajax actions.\n *\n * @module core_question/repository\n * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}\n * @author Simon Adams <simon.adams@catalyst-eu.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * @param {integer} newContextId target bank context id\n * @param {integer} newCategoryId target question category id\n * @param {string} questionIds questionIds comma separated list of question ids to move.\n * @param {string} returnUrl optional url to add/update the filter param with the new category id\n * @return {*}\n */\nexport const moveQuestions = (\n newContextId,\n newCategoryId,\n questionIds,\n returnUrl = '',\n) => Ajax.call([{\n methodname: 'core_question_move_questions',\n args: {\n newcontextid: newContextId,\n newcategoryid: newCategoryId,\n questionids: questionIds,\n returnurl: returnUrl,\n },\n}])[0];\n"],"names":["newContextId","newCategoryId","questionIds","returnUrl","Ajax","call","methodname","args","newcontextid","newcategoryid","questionids","returnurl"],"mappings":";;;;;;;;sKAiC6B,SACzBA,aACAC,cACAC,iBACAC,iEAAY,UACXC,cAAKC,KAAK,CAAC,CACZC,WAAY,+BACZC,KAAM,CACFC,aAAcR,aACdS,cAAeR,cACfS,YAAaR,YACbS,UAAWR,cAEf"}

View File

@ -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 <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle core_question ajax actions.
*
* @module core_question/repository
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Simon Adams <simon.adams@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* @param {integer} newContextId target bank context id
* @param {integer} newCategoryId target question category id
* @param {string} questionIds questionIds comma separated list of question ids to move.
* @param {string} returnUrl optional url to add/update the filter param with the new category id
* @return {*}
*/
export const moveQuestions = (
newContextId,
newCategoryId,
questionIds,
returnUrl = '',
) => Ajax.call([{
methodname: 'core_question_move_questions',
args: {
newcontextid: newContextId,
newcategoryid: newCategoryId,
questionids: questionIds,
returnurl: returnUrl,
},
}])[0];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,333 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Contain the logic for the bulkmove questions modal.
*
* @module qbank_bulkmove/modal_question_bank_bulkmove
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Simon Adams <simon.adams@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Modal from 'core/modal';
import * as Fragment from 'core/fragment';
import {getString} from 'core/str';
import AutoComplete from 'core/form-autocomplete';
import {moveQuestions} from 'core_question/repository';
import Templates from 'core/templates';
import Notification from 'core/notification';
export default class ModalQuestionBankBulkmove extends Modal {
static TYPE = 'qbank_bulkmove/bulkmove';
static SELECTORS = {
SAVE_BUTTON: '[data-action="bulkmovesave"]',
SELECTED_QUESTIONS: 'table#categoryquestions input[id^="checkq"]',
SEARCH_BANK: '#searchbanks',
SEARCH_CATEGORY: '#searchcategories',
CATEGORY_OPTIONS: '#searchcategories option',
BANK_OPTIONS: '#searchbanks option',
CATEGORY_ENHANCED_INPUT: '.search-categories input',
ORIGINAL_SELECTS: 'select.bulk-move',
CATEGORY_WARNING: '#searchcatwarning',
CATEGORY_SUGGESTION: '.search-categories span.form-autocomplete-downarrow',
CONFIRM_BUTTON: '.bulk-move-footer button[data-action="save"]',
CANCEL_BUTTON: '.bulk-move-footer button[data-action="cancel"]'
};
/**
* @param {integer} contextId The current bank context id.
* @param {integer} categoryId The current question category id.
*/
static init(contextId, categoryId) {
document.addEventListener('click', (e) => {
const trigger = e.target;
if (trigger.className === 'dropdown-item' && trigger.getAttribute('name') === 'move') {
e.preventDefault();
ModalQuestionBankBulkmove.create({
contextId,
title: getString('bulkmoveheader', 'qbank_bulkmove'),
show: true,
categoryId: categoryId,
});
}
});
}
/**
* Set the initialised config on the class.
*
* @param {Object} modalConfig
*/
configure(modalConfig) {
this.contextId = modalConfig.contextId;
this.targetBankContextId = modalConfig.contextId;
this.initSelectedCategoryId(modalConfig.categoryId);
modalConfig.removeOnClose = true;
super.configure(modalConfig);
}
/**
* Initialise the category select based on the data passed to the JS or if a filter is applied in the url.
* @param {integer} categoryId
*/
initSelectedCategoryId(categoryId) {
const filter = new URLSearchParams(window.location.href).get('filter');
if (filter) {
const filteredCategoryId = JSON.parse(filter)?.category.values[0];
this.currentCategoryId = filteredCategoryId > 0 ? filteredCategoryId : null;
this.targetCategoryId = filteredCategoryId;
return;
}
this.currentCategoryId = categoryId;
this.targetCategoryId = categoryId;
}
/**
* Render the modal contents.
* @return {Promise}
*/
show() {
void this.display(this.contextId, this.currentCategoryId);
return super.show();
}
/**
* Get the content to display and enhance the selects into auto complete fields.
* @param {integer} currentBankContextId
* @param {integer} currentCategoryId
*/
async display(currentBankContextId, currentCategoryId) {
this.bodyPromise = await Fragment.loadFragment(
'qbank_bulkmove',
'bulk_move',
currentBankContextId,
{
'categoryid': currentCategoryId,
}
);
await this.setBody(this.bodyPromise);
await this.enhanceSelects(document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.ORIGINAL_SELECTS));
this.registerEnhancedEventListeners();
this.mapCategoryContextIds();
this.updateSaveButtonState();
}
/**
* Register event listeners on the enhanced selects. Must be done after they have been enhanced.
*/
registerEnhancedEventListeners() {
document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_CATEGORY).addEventListener("change", (e) => {
const targetCategoryId = e.currentTarget.value;
this.targetCategoryId = targetCategoryId;
this.rebuildOptions(this.targetBankContextId, targetCategoryId);
this.updateSaveButtonState();
});
document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SEARCH_BANK).addEventListener("change", (e) => {
const selectedBankContextId = e.currentTarget.value;
this.targetBankContextId = selectedBankContextId;
this.rebuildOptions(selectedBankContextId, this.targetCategoryId);
});
this.getModal().on("click", ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON, (e) => {
e.preventDefault();
void this.displayConfirmMove();
});
}
/**
* Set a map, so we can determine which bank belongs to which category.
*/
mapCategoryContextIds() {
const customSelectCategoryOptions = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS);
if (customSelectCategoryOptions.length === 0) {
return;
}
const categoryContextIds = [];
customSelectCategoryOptions.forEach((option) => {
categoryContextIds[option.value] = option.dataset.bankContextid;
});
this.categoryContextIds = categoryContextIds;
}
/**
* Update the body with a confirmation prompt and set confirm cancel buttons in the footer.
* @return {Promise<void>}
*/
async displayConfirmMove() {
this.setTitle(getString('confirm', 'core'));
this.setBody(getString('confirmmove', 'qbank_bulkmove'));
if (!this.hasFooterContent()) {
// We don't have the footer yet so go grab it and register event listeners on the buttons.
this.setFooter(Templates.render('qbank_bulkmove/bulk_move_footer', {}));
await this.getFooterPromise();
document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CONFIRM_BUTTON).addEventListener("click", (e) => {
e.preventDefault();
this.moveQuestionsAfterConfirm(this.targetBankContextId, this.targetCategoryId);
});
document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CANCEL_BUTTON).addEventListener("click", (e) => {
e.preventDefault();
this.setTitle(getString('bulkmoveheader', 'qbank_bulkmove'));
this.setBodyContent(Templates.renderForPromise('core/loading', {}));
this.hideFooter();
this.display(this.targetBankContextId, this.targetCategoryId);
});
} else {
// We already have a footer so just show it.
this.showFooter();
}
}
/**
* Dynamically update all enhanced selects options based on what is selected.
*
* @param {integer} selectedBankContextId
* @param {integer} selectedCategoryId
*/
rebuildOptions(selectedBankContextId, selectedCategoryId) {
const categoryContextIds = this.categoryContextIds;
const customSelectCategoryOptions = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_OPTIONS);
// Disable the category selector if no bank selected.
if (!selectedBankContextId) {
this.updateCategorySelectorState(false);
} else {
// Mark to be disabled all the categories not belonging to the selected bank.
// This will then be handled by the enhanced selects event handlers.
customSelectCategoryOptions.forEach((option) => {
if (option.dataset.bankContextid != selectedBankContextId) {
option.dataset.enabled = 'disabled';
} else {
option.dataset.enabled = 'enabled';
}
});
this.updateCategorySelectorState(true);
}
// De-select the selected category if it does not belong to the selected bank.
if (selectedCategoryId && selectedBankContextId && categoryContextIds[selectedCategoryId] != selectedBankContextId) {
const selectedCategoryElement = document.querySelector(
'.search-categories span[role="option"][data-value="' + selectedCategoryId + '"]'
);
selectedCategoryElement.click();
}
}
/**
* Disable/enable the enhanced category selector field.
* @param {boolean} toEnable True to enable, false to disable the field.
*/
updateCategorySelectorState(toEnable) {
const warning = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_WARNING);
const enhancedInput = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_ENHANCED_INPUT);
const suggestionButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.CATEGORY_SUGGESTION);
if (toEnable) {
warning.classList.add('d-none');
enhancedInput.removeAttribute('disabled');
suggestionButton.classList.remove('d-none');
} else {
warning.classList.remove('d-none');
enhancedInput.setAttribute('disabled', 'disabled');
suggestionButton.classList.add('d-none');
}
}
/**
* Disable the button if the selected category is the same as the one the questions already belong to. Enable it otherwise.
*/
updateSaveButtonState() {
const saveButton = document.querySelector(ModalQuestionBankBulkmove.SELECTORS.SAVE_BUTTON);
const targetCategoryId = this.targetCategoryId;
if (targetCategoryId && targetCategoryId != this.currentCategoryId) {
saveButton.removeAttribute('disabled');
} else {
saveButton.setAttribute('disabled', 'disabled');
}
}
/**
* Move the selected questions to their new target category.
* @param {integer} targetContextId the target bank context id.
* @param {integer} targetCategoryId the target question category id.
* @return {Promise<void>}
*/
async moveQuestionsAfterConfirm(targetContextId, targetCategoryId) {
await this.setBody(Templates.render('core/loading', {}));
const qelements = document.querySelectorAll(ModalQuestionBankBulkmove.SELECTORS.SELECTED_QUESTIONS);
const questionids = [];
qelements.forEach((element) => {
if (element.checked) {
const name = element.getAttribute('name');
questionids.push(name.substr(1, name.length));
}
});
if (questionids.length === 0) {
await Notification.exception('No questions selected');
}
try {
window.location.href = await moveQuestions(
targetContextId,
targetCategoryId,
questionids.join(),
window.location.href
);
} catch (error) {
await Notification.exception(error);
}
}
/**
* Take the provided select options and enhance them into auto-complete fields.
* @param {NodeList} selects Custom select elements to enhance.
* @return {Promise<Promise[]>}
*/
async enhanceSelects(selects) {
const placeholder = await getString('searchbyname', 'mod_quiz');
const enhanced = [];
if (selects.length > 0) {
for (let i = 0; i < selects.length; i++) {
enhanced.push(AutoComplete.enhance(
selects.item(i),
false,
'',
placeholder,
false,
true,
'',
true
)
);
}
return Promise.all(enhanced);
}
return Promise.reject('No selects to enhance');
}
}

View File

@ -16,6 +16,8 @@
namespace qbank_bulkmove;
use moodle_exception;
/**
* Class bulk_move_action is the base class for moving questions.
*
@ -44,4 +46,31 @@ class bulk_move_action extends \core_question\local\bank\bulk_action_base {
'moodle/question:add',
];
}
/**
* Initialise the modal js with the current bank context id and question category id.
* @return void
*/
public function initialise_javascript(): void {
global $PAGE;
$category = $this->qbank->get_pagevars('cat');
if (!empty($category)) {
[$categoryid, $contextid] = explode(',', $category);
} else {
$defaultcategory = question_get_default_category($this->qbank->cm->context->id, true);
$categoryid = $defaultcategory->id;
$contextid = $defaultcategory->contextid;
}
$PAGE->requires->js_call_amd(
'qbank_bulkmove/modal_question_bank_bulkmove',
'init',
[
'contextid' => $contextid,
'categoryid' => $categoryid,
]
);
}
}

View File

@ -33,9 +33,10 @@ class helper {
* @param \stdClass $tocategory the category where the questions will be moved to.
*/
public static function bulk_move_questions(string $movequestionselected, \stdClass $tocategory): void {
global $DB;
global $DB, $CFG;
require_once($CFG->libdir .'/questionlib.php');
if ($questionids = explode(',', $movequestionselected)) {
list($usql, $params) = $DB->get_in_or_equal($questionids);
[$usql, $params] = $DB->get_in_or_equal($questionids);
$sql = "SELECT q.*, c.contextid
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
@ -58,8 +59,17 @@ class helper {
* @param \moodle_url $moveurl the url where the move script will point to.
* @param \moodle_url $returnurl return url in case the form is cancelled.
* @return array the data to be rendered in the mustache where it contains the dropdown, move url and return url.
* @deprecated since Moodle 5.0.
* @todo MDL-82413 Final deprecation in Moodle 6.0.
*/
#[\core\attribute\deprecated(
replacement: 'replaced by a modal and webservice.
See qbank_bulkmove/modal_question_bank_bulkmove and core_question_external\move_questions',
since: '5.0',
mdl: 'MDL-71378'
)]
public static function get_displaydata(array $addcontexts, \moodle_url $moveurl, \moodle_url $returnurl): array {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
$displaydata = [];
$displaydata ['categorydropdown'] = \qbank_managecategories\helper::question_category_select_menu($addcontexts,
false, 0, '', -1, true);

View File

@ -0,0 +1,141 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Output class file.
*
* @package qbank_bulkmove
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Simon Adams <simon.adams@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qbank_bulkmove\output;
use cm_info;
use core_question\local\bank\question_bank_helper;
use moodle_url;
use renderer_base;
use single_button;
/**
* Output class to create a modal template with selects for question banks, question categories, and a move button.
*/
class bulk_move implements \renderable, \templatable {
/** @var int The question bank id you are currently moving the question(s) from */
protected int $currentbankid;
/** @var int The question category id you are moving the question(s) from */
protected int $currentcategoryid;
/**
* Instantiate the output class.
*
* @param int $currentbankid
* @param int $currentcategoryid
*/
public function __construct(int $currentbankid, int $currentcategoryid) {
$this->currentbankid = $currentbankid;
$this->currentcategoryid = $currentcategoryid;
}
/**
* Export data for use by the template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output) {
[, $cmrec] = get_module_from_cmid($this->currentbankid);
$currentbank = cm_info::create($cmrec);
// Get all shared banks and categories and make the current bank/category pre-selected, i.e. ordered first in the list.
$bankstorender = question_bank_helper::get_activity_instances_with_shareable_questions(
[],
[],
['moodle/question:add'],
true,
$this->currentbankid
);
$allcategories = array_map(function($bank) {
if ($bank->modid == $this->currentbankid) {
// If this is the current bank then sort the categories so that our current categoryid is first in the list.
$this->sort_categories($bank->questioncategories, $this->currentcategoryid);
}
return $bank->questioncategories;
}, $bankstorender);
// The current bank is not a shared bank, but grab the category records anyway so that we can at least allow them
// to be moved to another local category in the bank.
if (!plugin_supports('mod', $currentbank->modname, FEATURE_PUBLISHES_QUESTIONS, false)) {
$currentbank = question_bank_helper::get_activity_instances_with_private_questions(
incourseids: [$currentbank->course],
getcategories: true,
currentbankid: $this->currentbankid,
)[0];
$currentbankcats = $currentbank->questioncategories;
// Move the current category to the top of the list.
$this->sort_categories($currentbankcats, $this->currentcategoryid);
// Add the current bank categories to the front of the categories list.
array_unshift($allcategories, $currentbankcats);
// Add the current bank to the front of the banks list.
array_unshift($bankstorender, $currentbank);
}
// Flatten all the categories into a 2D array.
$allcategories = array_merge(...array_values($allcategories));
$savebutton = new single_button(
new moodle_url('#'),
get_string('movequestions', 'qbank_bulkmove'),
'post',
single_button::BUTTON_PRIMARY,
[
'data-action' => 'bulkmovesave',
'disabled' => 'disabled',
]
);
return [
'allsharedbanks' => $bankstorender,
'allcategories' => $allcategories,
'save' => $savebutton->export_for_template($output),
];
}
/**
* Wrapped usort to move the currentcategoryid to the top of the list of question categories.
*
* @param array $categories categories to sort
* @param int $currentcategoryid the category to be sorted to the top of the list
* @return void
*/
protected function sort_categories(array &$categories, int $currentcategoryid): void {
usort($categories, static function($categorya, $categoryb) use ($currentcategoryid) {
if ($categorya->id != $currentcategoryid && $categoryb->id == $currentcategoryid) {
return 1;
}
if ($categorya->id == $currentcategoryid && $categoryb->id != $currentcategoryid) {
return -1;
}
return $categoryb->id <=> $categorya->id;
});
}
}

View File

@ -31,8 +31,12 @@ class renderer extends \plugin_renderer_base {
*
* @param array $displaydata
* @return string
* @deprecated since Moodle 5.0.
* @todo MDL-82413 Final deprecation in Moodle 6.0.
*/
#[\core\attribute\deprecated('qbank_bulkmove\output\bulk_move', since: '5.0', mdl: 'MDL-71378')]
public function render_bulk_move_form($displaydata) {
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
return $this->render_from_template('qbank_bulkmove/bulk_move', $displaydata);
}

View File

@ -16,8 +16,8 @@
namespace qbank_bulkmove;
use core_question\local\bank\bulk_action_base;
use core_question\local\bank\plugin_features_base;
use core_question\local\bank\view;
/**
* Class plugin_feature is the entrypoint for the features.
@ -28,9 +28,15 @@ use core_question\local\bank\plugin_features_base;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugin_feature extends plugin_features_base {
public function get_bulk_actions(): array {
/**
* Initialise the bulk action.
* @param view $qbank
* @return bulk_move_action[]
*/
public function get_bulk_actions(view $qbank): array {
return [
new bulk_move_action(),
new bulk_move_action($qbank),
];
}
}

View File

@ -23,9 +23,12 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['bulkmoveheader'] = 'Move the selected questions';
$string['bulkmoveheader'] = 'Move the selected questions to...';
$string['close'] = 'Close';
$string['confirmmove'] = 'Are you sure you want to move these questions?';
$string['movequestions'] = 'Move questions';
$string['movetobulkaction'] = 'Move to...';
$string['pluginname'] = 'Bulk move questions';
$string['privacy:metadata'] = 'The Bulk move questions question bank plugin does not store any personal data.';
$string['questionsmoved'] = 'Questions successfully moved';
$string['warning'] = 'You must select a question bank before you can select a category.';

View File

@ -0,0 +1,40 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* qbank_bulkmove lib functions.
*
* @package qbank_bulkmove
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Simon Adams <simon.adams@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Generates the bulkmove bank and category chooser for display in the modal.
*
* @param array $args
* @return bool|string
*/
function qbank_bulkmove_output_fragment_bulk_move(array $args) {
global $OUTPUT;
$currentbankid = clean_param($args['context']->instanceid, PARAM_INT);
$currentcategoryid = clean_param($args['categoryid'], PARAM_INT);
$qbankcatchooser = new \qbank_bulkmove\output\bulk_move($currentbankid, $currentcategoryid);
return $OUTPUT->render($qbankcatchooser);
}

View File

@ -1,110 +0,0 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Move questions page.
*
* @package qbank_bulkmove
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_once(__DIR__ . '/../../editlib.php');
global $DB, $OUTPUT, $PAGE, $COURSE;
$moveselected = optional_param('move', false, PARAM_BOOL);
$returnurl = optional_param('returnurl', 0, PARAM_LOCALURL);
$cmid = optional_param('cmid', 0, PARAM_INT);
$courseid = optional_param('courseid', 0, PARAM_INT);
$category = optional_param('category', null, PARAM_SEQUENCE);
$confirm = optional_param('confirm', '', PARAM_ALPHANUM);
$movequestionselected = optional_param('movequestionsselected', null, PARAM_RAW);
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
}
\core_question\local\bank\helper::require_plugin_enabled('qbank_bulkmove');
if ($cmid) {
list($module, $cm) = get_module_from_cmid($cmid);
require_login($cm->course, false, $cm);
$thiscontext = context_module::instance($cmid);
} else if ($courseid) {
require_login($courseid, false);
$thiscontext = context_course::instance($courseid);
} else {
throw new moodle_exception('missingcourseorcmid', 'question');
}
$contexts = new core_question\local\bank\question_edit_contexts($thiscontext);
$url = new moodle_url('/question/bank/bulkmove/move.php');
$PAGE->set_url($url);
$streditingquestions = get_string('movequestions', 'qbank_bulkmove');
$PAGE->set_title($streditingquestions);
$PAGE->set_heading($COURSE->fullname);
$PAGE->activityheader->disable();
$PAGE->set_secondary_active_tab("questionbank");
if ($category) {
list($tocategoryid, $contextid) = explode(',', $category);
if (! $tocategory = $DB->get_record('question_categories',
['id' => $tocategoryid, 'contextid' => $contextid])) {
throw new \moodle_exception('cannotfindcate', 'question');
}
}
if ($movequestionselected && $confirm && confirm_sesskey()) {
if ($confirm == md5($movequestionselected)) {
\qbank_bulkmove\helper::bulk_move_questions($movequestionselected, $tocategory);
}
$returnfilters = \core_question\local\bank\filter_condition_manager::update_filter_param_to_category(
$returnurl->param('filter'),
$tocategoryid,
);
redirect(new moodle_url($returnurl, ['filter' => $returnfilters]));
}
echo $OUTPUT->header();
if ($moveselected) {
$rawquestions = $_REQUEST;
list($questionids, $questionlist) = \qbank_bulkmove\helper::process_question_ids($rawquestions);
// No questions were selected.
if (!$questionids) {
redirect($returnurl);
}
// Create the urls.
$moveparam = [
'movequestionsselected' => $questionlist,
'confirm' => md5($questionlist),
'sesskey' => sesskey(),
'returnurl' => $returnurl,
'cmid' => $cmid,
'courseid' => $courseid,
];
$moveurl = new \moodle_url($url, $moveparam);
$addcontexts = $contexts->having_cap('moodle/question:add');
$displaydata = \qbank_bulkmove\helper::get_displaydata($addcontexts, $moveurl, $returnurl);
echo $PAGE->get_renderer('qbank_bulkmove')->render_bulk_move_form($displaydata);
}
echo $OUTPUT->footer();

View File

@ -17,27 +17,124 @@
{{!
@template qbank_bulkmove/bulk_move
The move form to move selested questions.
Context variables required for this template:
* categorydropdown - dropdown html from the managecategories plugin for the list of categories
* moveurl - the url to post the selected category
* returnurl - the base page to return to
Template for qbank_bulkmove/modal_question_bank_bulkmove
Example context (json):
{
"categorydropdown": "<select class='select custom-select custom-select'><optgroup label='Course: tes'><option value='2,13'>Default for test (5)</option></optgroup></select>",
"moveurl": "/question/bank/bulkmove/move.php?courseid=2",
"returnurl": "/question/edit.php?courseid=2"
{
"allsharedbanks": [
{
"name": "Quiz 1",
"contextid": 1,
"coursenamebankname": "c1 - Quiz 1"
},
{
"name": "Question bank 1",
"modid": "2",
"contextid": 2,
"coursenamebankname": "c1 - Question bank 1",
"cminfo": {},
"questioncategories": [
{
"id": "22",
"name": "Default for Question bank 1",
"contextid": "22",
"enabled": "disabled"
}
]
},
{
"name": "Question bank 2",
"modid": "3",
"contextid": 33,
"coursenamebankname": "c2 - Question bank 2",
"cminfo": {},
"questioncategories": [
{
"id": "23",
"name": "Default for Question bank 2",
"contextid": "23",
"enabled": "disabled"
}
]
}
],
"allcategories": [
{
"id": "22",
"name": "Default for Question bank 1",
"contextid": "22"
},
{
"id": "23",
"name": "Default for Question bank 2",
"contextid": "23",
"enabled": "disabled"
},
{
"id": "24",
"name": "Default for Question bank 3",
"contextid": "24",
"enabled": "disabled"
},
{
"id": "25",
"name": "Default for Question bank 4",
"contextid": "25",
"enabled": "disabled"
}
],
"save": {
"id": "single_button669e368d496707",
"formid": null,
"method": "post",
"url": "#",
"label": "Move questions",
"classes": "singlebutton",
"disabled": false,
"tooltip": null,
"type": "primary",
"attributes": [
{
"name": "data-action",
"value": "bulkmovesave"
},
{
"name": "disabled",
"value": "disabled"
}
],
"params": [
{
"name": "sesskey",
"value": "abcde12345"
}
],
"actions": [],
"hasactions": false
}
}
}}
<div class="bulkmovequestion-header">
<h3>{{#str}} bulkmoveheader, qbank_bulkmove {{/str}}</h3>
<div class="search-banks">
<h5>{{#str}}questionbank, question{{/str}}</h5>
<select class="custom-select bulk-move d-none" id="searchbanks">
{{#allsharedbanks}}
<option value="{{contextid}}">{{{coursenamebankname}}}</option>
{{/allsharedbanks}}
</select>
</div>
<form action="{{{moveurl}}}" method="post" id="bulkmovequestion">
{{{categorydropdown}}}
<input type="submit" value="{{#str}} moveto, question {{/str}}" class="btn btn-primary" name="move" data-action="toggle" data-togglegroup="qbank"
data-toggle="action" form="bulkmovequestion">
<a href="{{{returnurl}}}" class="btn btn-secondary">{{#str}} close, qbank_bulkmove {{/str}}</a>
</form>
<div class="search-categories mt-3">
<h5>{{#str}}questioncategories, question{{/str}}</h5>
<select class="custom-select bulk-move d-none" id="searchcategories">
{{#allcategories}}
<option value="{{id}}" data-bank-contextid="{{contextid}}" data-enabled="{{enabled}}">{{{name}}}</option>
{{/allcategories}}
</select>
<div id="searchcatwarning" class="d-none">{{#str}}warning, qbank_bulkmove{{/str}}</div>
</div>
{{#save}}
<div class="move-questions mt-3">
{{>core/single_button}}
</div>
{{/save}}

View File

@ -0,0 +1,31 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template qbank_bulkmove/bulk_move_footer
Template for qbank_bulkmove/modal_question_bank_bulkmove
Example context (json):
{
}
}}
<div class="bulk-move-footer">
<button class="btn btn-primary" data-action="save">{{#str}}confirm{{/str}}</button>
<button class="btn btn-primary" data-action="cancel">{{#str}}cancel{{/str}}</button>
</div>

View File

@ -6,15 +6,37 @@ Feature: Use the qbank plugin manager page for bulkmove
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
| Course 3 | C3 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
And the following "activities" exist:
| activity | name | course | idnumber |
| quiz | Test quiz | C1 | quiz1 |
| activity | name | course | idnumber |
| quiz | Test quiz | C1 | quiz1 |
| qbank | Question bank 1 | C1 | qbank1 |
| qbank | Question bank 2 | C2 | qbank2 |
| qbank | Question bank 3 | C3 | qbank3 |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
| contextlevel | reference | name |
| Activity module | quiz1 | Test questions 1 |
| Activity module | qbank1 | Test questions 2 |
| Activity module | qbank2 | Test questions 3 |
| Activity module | qbank3 | Test questions 4 |
| Activity module | qbank1 | Test questions 5 |
| Activity module | quiz1 | Test questions 6 |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
| questioncategory | qtype | name | questiontext |
| Test questions 1 | truefalse | First question | Answer the first question |
| Test questions 2 | truefalse | Second question | Answer the second question |
| Test questions 3 | truefalse | Third question | Answer the third question |
| Test questions 4 | truefalse | Fourth question | Answer the fourth question |
| Test questions 5 | truefalse | Fifth question | Answer the fifth question |
| Test questions 6 | truefalse | Sixth question | Answer the sixth question |
@javascript
Scenario: Enable/disable bulk move questions bulk action from the base view
@ -32,3 +54,42 @@ Feature: Use the qbank plugin manager page for bulkmove
And I click on "First question" "checkbox"
And I click on "With selected" "button"
And I should see question bulk action "move"
@javascript
Scenario: Selecting a shared question bank limits the available categories to those belonging to the selected bank.
Given I log in as "teacher1"
And I am on the "Test quiz" "mod_quiz > question bank" page
And I click on "First question" "checkbox"
And I click on "With selected" "button"
And I click on "move" "button"
And I open the autocomplete suggestions list in the ".search-categories" "css_element"
And "Test questions 1" "autocomplete_suggestions" should exist
And "Test questions 2" "autocomplete_suggestions" should not exist
And "Test questions 3" "autocomplete_suggestions" should not exist
And "Test questions 4" "autocomplete_suggestions" should not exist
And "Test questions 5" "autocomplete_suggestions" should not exist
And "Test questions 6" "autocomplete_suggestions" should exist
When I open the autocomplete suggestions list in the ".search-banks" "css_element"
Then I should not see "C3 - Question bank 3" in the ".search-banks" "css_element"
And I click on "C1 - Question bank 1" item in the autocomplete list
Then I should not see "Test questions 1" in the ".search-categories" "css_element"
And I open the autocomplete suggestions list in the ".search-categories" "css_element"
And "Test questions 2" "autocomplete_suggestions" should exist
And "Test questions 3" "autocomplete_suggestions" should not exist
And "Test questions 4" "autocomplete_suggestions" should not exist
And "Test questions 5" "autocomplete_suggestions" should exist
@javascript
Scenario: Move a question from one bank category to another.
Given I log in as "teacher1"
And I am on the "Test quiz" "mod_quiz > question bank" page
And I click on "First question" "checkbox"
And I click on "With selected" "button"
And I click on "move" "button"
And I open the autocomplete suggestions list in the ".search-categories" "css_element"
And I click on "Test questions 6" item in the autocomplete list
And I click on "Move questions" "button"
Then I should see "Are you sure you want to move these questions?"
And I click on "Confirm" "button"
And I wait until the page is ready
Then I should see "Questions successfully moved"

View File

@ -216,6 +216,7 @@ class helper_test extends \advanced_testcase {
$addcontexts = $contexts->having_cap('moodle/question:add');
$url = new \moodle_url('/question/bank/bulkmove/move.php');
$displaydata = \qbank_bulkmove\helper::get_displaydata($addcontexts, $url, $url);
$this->assertDebuggingCalled();
$this->assertStringContainsString('Test question category 1', $displaydata['categorydropdown']);
$this->assertStringContainsString('Default for QBANK 1', $displaydata['categorydropdown']);
$this->assertEquals($url, $displaydata ['moveurl']);

View File

@ -26,6 +26,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qbank_bulkmove';
$plugin->version = 2024100700;
$plugin->version = 2024100800;
$plugin->requires = 2024100100;
$plugin->maturity = MATURITY_STABLE;

View File

@ -44,9 +44,14 @@ class plugin_feature extends plugin_features_base {
];
}
public function get_bulk_actions(): array {
/**
* Initialise the bulk action.
* @param view $qbank
* @return bulk_delete_action[]
*/
public function get_bulk_actions(view $qbank): array {
return [
new bulk_delete_action(),
new bulk_delete_action($qbank),
];
}

View File

@ -45,11 +45,12 @@ class question_history_view_test extends \advanced_testcase {
// Create a course.
$course = $generator->create_course();
$context = \context_course::instance($course->id);
$qbank = $generator->create_module('qbank', ['course' => $course->id]);
$context = \context_module::instance($qbank->cmid);
// Create a question in the default category.
$contexts = new \core_question\local\bank\question_edit_contexts($context);
$cat = $questiongenerator->create_question_category();
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
$questiondata1 = $questiongenerator->create_question('numerical', null,
['name' => 'Example question', 'category' => $cat->id]);

View File

@ -24,6 +24,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\context;
use core\notification;
use core_external\external_api;
use core_external\external_description;
use core_external\external_value;
@ -31,6 +33,7 @@ use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_function_parameters;
use core_external\external_warnings;
use core_question\local\bank\filter_condition_manager;
defined('MOODLE_INTERNAL') || die();

View File

@ -0,0 +1,132 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace core_question\external;
use core\context;
use core\exception\moodle_exception;
use core\notification;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_value;
use core_external\restricted_context_exception;
use core_question\local\bank\filter_condition_manager;
use moodle_url;
/**
* API for moving questions from one question bank category to another.
*
* @package core_question
* @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Simon Adams <simon.adams@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class move_questions extends external_api {
/**
* Declare the method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'newcontextid' => new external_value(PARAM_INT, 'Contextid of the target question bank'),
'newcategoryid' => new external_value(PARAM_INT, 'ID of the target question category'),
'questionids' => new external_value(PARAM_SEQUENCE, 'Comma separated list of question ids to move'),
'returnurl' => new external_value(PARAM_URL,
desc: 'A URL to add/update the filter param with the new category',
default: ''
),
]
);
}
/**
* Define the webservice response.
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_URL, 'Modified URL with filter param containing the new question category', VALUE_OPTIONAL);
}
/**
* Move questions to a new question category.
* Optionally provide a url to add/update it with the filter param containing the new category.
*
* @param int $newcontextid of the target question bank
* @param int $newcategoryid of the target category
* @param string $questionids comma separated list of question ids to move
* @param string $returnurlstring optional, provide this to have the filter url param added/updated to reflect the new category
* @return null|string if $returnurlstring was provided then an updated url which filters to the new category
*/
public static function execute(
int $newcontextid,
int $newcategoryid,
string $questionids,
string $returnurlstring = ''
): ?string {
global $DB;
[
'newcontextid' => $newcontextid,
'newcategoryid' => $newcategoryid,
'questionids' => $questionids,
'returnurl' => $returnurlstring,
] = self::validate_parameters(self::execute_parameters(), [
'newcontextid' => $newcontextid,
'newcategoryid' => $newcategoryid,
'questionids' => $questionids,
'returnurl' => $returnurlstring,
]);
$newcontext = context::instance_by_id($newcontextid);
self::validate_context($newcontext);
\core_question\local\bank\helper::require_plugin_enabled('qbank_bulkmove');
$contexts = new \core_question\local\bank\question_edit_contexts($newcontext);
$contexts->require_cap('moodle/question:add');
if (!$targetcategory = $DB->get_record('question_categories', ['id' => $newcategoryid, 'contextid' => $newcontextid])) {
throw new \moodle_exception('cannotfindcate', 'question');
}
\qbank_bulkmove\helper::bulk_move_questions($questionids, $targetcategory);
notification::success(get_string('questionsmoved', 'qbank_bulkmove'));
if ($returnurlstring) {
$returnurl = new moodle_url($returnurlstring);
$returnurl->param('cmid', $newcontext->instanceid);
$filter = $returnurl->param('filter');
if ($filter) {
$returnfilters = filter_condition_manager::update_filter_param_to_category(
$filter,
$newcategoryid,
);
} else {
$returnfilters = json_encode(
filter_condition_manager::get_default_filter("{$newcategoryid},{$newcontextid}"),
JSON_THROW_ON_ERROR
);
}
$returnurl->param('filter', $returnfilters);
return $returnurl->out(false);
}
}
}

View File

@ -27,7 +27,7 @@ namespace core_question\local\bank;
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class bulk_action_base {
abstract class bulk_action_base extends view_component {
/**
* Title of the bulk action.
@ -69,6 +69,15 @@ abstract class bulk_action_base {
return null;
}
/**
* Override if you want to load your own javascript.
*
* @return void
*/
public function initialise_javascript(): void {
}
/**
* @deprecated since Moodle 4.0
*/

View File

@ -25,6 +25,10 @@
namespace core_question\local\bank;
use core\output\datafilter;
use qbank_deletequestion\hidden_condition;
use qbank_managecategories\category_condition;
/**
* Static methods for parsing and formatting data related to filter conditions.
*/
@ -117,4 +121,29 @@ class filter_condition_manager {
}
return $filters;
}
/**
* Provide a category-context string to get a default filter array for the category.
*
* @param string $catstring in format '1,2' or 'categoryid,contextid'
* @return array
*/
public static function get_default_filter(string $catstring): array {
$filter = [];
[$validcatid, $contextid] = category_condition::validate_category_param($catstring);
if (!is_null($validcatid)) {
$category = category_condition::get_category_record($validcatid, $contextid);
$filter['category'] = [
'jointype' => condition::JOINTYPE_DEFAULT,
'values' => [$category->id],
'filteroptions' => ['includesubcategories' => false],
];
}
$filter['hidden'] = [
'jointype' => condition::JOINTYPE_DEFAULT,
'values' => [0],
];
return $filter;
}
}

View File

@ -74,9 +74,10 @@ class plugin_features_base {
/**
* This method will return the array objects for the bulk actions ui.
*
* @param view $qbank
* @return bulk_action_base[]
*/
public function get_bulk_actions() {
public function get_bulk_actions(view $qbank): array {
return [];
}

View File

@ -179,7 +179,7 @@ class view {
public $returnurl;
/**
* @var array $bulkactions to identify the bulk actions for the api.
* @var bulk_action_base[] $bulkactions bulk actions for the api.
*/
public $bulkactions = [];
@ -237,20 +237,7 @@ class view {
// Default filter condition.
if (!isset($params['filter']) && isset($params['cat'])) {
$params['filter'] = [];
[$categoryid, $contextid] = category_condition::validate_category_param($params['cat']);
if (!is_null($categoryid)) {
$category = category_condition::get_category_record($categoryid, $contextid);
$params['filter']['category'] = [
'jointype' => category_condition::JOINTYPE_DEFAULT,
'values' => [$category->id],
'filteroptions' => ['includesubcategories' => false],
];
}
$params['filter']['hidden'] = [
'jointype' => hidden_condition::JOINTYPE_DEFAULT,
'values' => [0],
];
$params['filter'] = filter_condition_manager::get_default_filter($params['cat']);
$params['jointype'] = datafilter::JOINTYPE_ALL;
}
if (!empty($params['filter'])) {
@ -321,7 +308,7 @@ class view {
*/
protected function init_bulk_actions(): void {
foreach ($this->plugins as $componentname => $plugin) {
$bulkactions = $plugin->get_bulk_actions();
$bulkactions = $plugin->get_bulk_actions($this);
if (!is_array($bulkactions)) {
debugging("The method {$componentname}::get_bulk_actions() must return an " .
"array of bulk actions instead of a single bulk action. " .
@ -331,11 +318,7 @@ class view {
}
foreach ($bulkactions as $bulkactionobject) {
$this->bulkactions[$bulkactionobject->get_key()] = [
'title' => $bulkactionobject->get_bulk_action_title(),
'url' => $bulkactionobject->get_bulk_action_url(),
'capabilities' => $bulkactionobject->get_bulk_action_capabilities()
];
$this->bulkactions[$bulkactionobject->get_key()] = $bulkactionobject;
}
}
}
@ -1328,7 +1311,7 @@ class view {
foreach ($this->bulkactions as $key => $action) {
// Check capabilities.
$capcount = 0;
foreach ($action['capabilities'] as $capability) {
foreach ($action->get_bulk_action_capabilities() as $capability) {
if (has_capability($capability, $catcontext)) {
$capcount ++;
}
@ -1339,9 +1322,9 @@ class view {
continue;
}
$actiondata = new \stdClass();
$actiondata->actionname = $action['title'];
$actiondata->actionname = $action->get_bulk_action_title();
$actiondata->actionkey = $key;
$actiondata->actionurl = new \moodle_url($action['url'], $params);
$actiondata->actionurl = new \moodle_url($action->get_bulk_action_url(), $params);
$bulkactiondata[] = $actiondata;
$bulkactiondatas ['bulkactionitems'] = $bulkactiondata;
@ -1353,6 +1336,16 @@ class view {
}
}
/**
* Give each bulk action a chance to load its own javascript module.
* @return void
*/
public function init_bulk_actions_js(): void {
foreach ($this->bulkactions as $action) {
$action->initialise_javascript();
}
}
/**
* Display the questions.
*

View File

@ -82,8 +82,10 @@ echo $renderer->render($qbankaction);
// Print the question area.
$questionbank->display();
[$categoryid, $contextid] = explode(',', $pagevars['cat']);
$questionbank->init_bulk_actions_js();
// Log the view of this category.
list($categoryid, $contextid) = explode(',', $pagevars['cat']);
$category = new stdClass();
$category->id = $categoryid;
$catcontext = context::instance_by_id($contextid);