diff --git a/mod/quiz/addrandom.php b/mod/quiz/addrandom.php deleted file mode 100644 index 09862164abf..00000000000 --- a/mod/quiz/addrandom.php +++ /dev/null @@ -1,141 +0,0 @@ -. - -/** - * Fallback page of /mod/quiz/edit.php add random question dialog, - * for users who do not use javascript. - * - * @package mod_quiz - * @copyright 2008 Olli Savolainen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(__DIR__ . '/../../config.php'); -require_once($CFG->dirroot . '/mod/quiz/locallib.php'); -require_once($CFG->dirroot . '/question/editlib.php'); - -use mod_quiz\form\add_random_form; -use mod_quiz\quiz_settings; -use qbank_managecategories\question_category_object; - -list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = - question_edit_setup('editq', '/mod/quiz/addrandom.php', true); - -// These params are only passed from page request to request while we stay on -// this page otherwise they would go in question_edit_setup. -$returnurl = optional_param('returnurl', '', PARAM_LOCALURL); -$addonpage = optional_param('addonpage', 0, PARAM_INT); -$category = optional_param('category', 0, PARAM_INT); -$mdlscrollto = optional_param('mdlscrollto', 0, PARAM_INT); - -$quizobj = quiz_settings::create($quiz->id); -$course = $quizobj->get_course(); - -// You need mod/quiz:manage in addition to question capabilities to access this page. -// You also need the moodle/question:useall capability somewhere. -require_capability('mod/quiz:manage', $contexts->lowest()); -if (!$contexts->having_cap('moodle/question:useall')) { - throw new \moodle_exception('nopermissions', '', '', 'use'); -} - -$PAGE->set_url($thispageurl); - -if ($returnurl) { - $returnurl = new moodle_url($returnurl); -} else { - $returnurl = new moodle_url('/mod/quiz/edit.php', ['cmid' => $cmid]); -} -if ($mdlscrollto) { - $returnurl->param('mdlscrollto', $mdlscrollto); -} - -$defaultcategoryobj = question_make_default_categories($contexts->all()); -$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid; - -$qcobject = new question_category_object( - $pagevars['cpage'], - $thispageurl, - $contexts->having_one_edit_tab_cap('categories'), - $defaultcategoryobj->id, - $defaultcategory, - null, - $contexts->having_cap('moodle/question:add')); - -$mform = new add_random_form(new moodle_url('/mod/quiz/addrandom.php'), - ['contexts' => $contexts, 'cat' => $pagevars['cat']]); - -if ($mform->is_cancelled()) { - redirect($returnurl); -} - -if ($data = $mform->get_data()) { - if (!empty($data->existingcategory)) { - list($categoryid) = explode(',', $data->category); - $includesubcategories = !empty($data->includesubcategories); - if (!$includesubcategories) { - // If the chosen category is a top category. - $includesubcategories = $DB->record_exists('question_categories', ['id' => $categoryid, 'parent' => 0]); - } - $returnurl->param('cat', $data->category); - - } else if (!empty($data->newcategory)) { - list($parentid, $contextid) = explode(',', $data->parent); - $categoryid = $qcobject->add_category($data->parent, $data->name, '', true); - $includesubcategories = 0; - - $returnurl->param('cat', $categoryid . ',' . $contextid); - } else { - throw new coding_exception( - 'It seems a form was submitted without any button being pressed???'); - } - - if (empty($data->fromtags)) { - $data->fromtags = []; - } - - $tagids = array_map(function($tagstrings) { - return (int)explode(',', $tagstrings)[0]; - }, $data->fromtags); - - quiz_add_random_questions($quiz, $addonpage, $categoryid, $data->numbertoadd, $includesubcategories, $tagids); - quiz_delete_previews($quiz); - $quizobj->get_grade_calculator()->recompute_quiz_sumgrades(); - redirect($returnurl); -} - -$mform->set_data([ - 'addonpage' => $addonpage, - 'returnurl' => $returnurl, - 'cmid' => $cm->id, - 'category' => $category, -]); - -// Setup $PAGE. -$streditingquiz = get_string('editinga', 'moodle', get_string('modulename', 'quiz')); -$PAGE->navbar->add($streditingquiz); -$PAGE->set_title($streditingquiz); -$PAGE->set_heading($course->fullname); -echo $OUTPUT->header(); - -if (!$quizname = $DB->get_field($cm->modname, 'name', ['id' => $cm->instance])) { - throw new \moodle_exception('invalidcoursemodule'); -} - -echo $OUTPUT->heading(get_string('addrandomquestiontoquiz', 'quiz', $quizname), 2); -$mform->display(); -echo $OUTPUT->footer(); - diff --git a/mod/quiz/amd/build/modal_add_random_question.min.js b/mod/quiz/amd/build/modal_add_random_question.min.js index a33960fe2e9..1c0f5a3d5a0 100644 --- a/mod/quiz/amd/build/modal_add_random_question.min.js +++ b/mod/quiz/amd/build/modal_add_random_question.min.js @@ -1,3 +1,3 @@ -define("mod_quiz/modal_add_random_question",["exports","jquery","core/modal","core/notification","core/fragment","core/templates","core_form/changechecker"],(function(_exports,_jquery,_modal,Notification,Fragment,Templates,FormChangeChecker){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}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_modal=_interopRequireDefault(_modal),Notification=_interopRequireWildcard(Notification),Fragment=_interopRequireWildcard(Fragment),Templates=_interopRequireWildcard(Templates),FormChangeChecker=_interopRequireWildcard(FormChangeChecker);const SELECTORS_EXISTING_CATEGORY_CONTAINER='[data-region="existing-category-container"]',SELECTORS_EXISTING_CATEGORY_FORM_ELEMENT="#id_existingcategoryheader",SELECTORS_NEW_CATEGORY_CONTAINER='[data-region="new-category-container"]',SELECTORS_NEW_CATEGORY_FORM_ELEMENT="#id_newcategoryheader",SELECTORS_TAB_CONTENT='[data-region="tab-content"]',SELECTORS_ADD_ON_PAGE_FORM_ELEMENT='[name="addonpage"]',SELECTORS_SUBMIT_BUTTON_ELEMENT='input[type="submit"]',SELECTORS_CANCEL_BUTTON_ELEMENT='input[type="submit"][name="cancel"]',SELECTORS_FORM_HEADER="legend",SELECTORS_BUTTON_CONTAINER=".fitem";class ModalAddRandomQuestion extends _modal.default{constructor(root){super(root),this.contextId=null,this.addOnPageId=null,this.category=null,this.returnUrl=null,this.cmid=null,this.loadedForm=!1}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id,this.getBody().find(SELECTORS_ADD_ON_PAGE_FORM_ELEMENT).val(id)}getAddOnPageId(){return this.addOnPageId}setCategory(category){this.category=category}getCategory(){return this.category}setReturnUrl(url){this.returnUrl=url}getReturnUrl(){return this.returnUrl}setCMID(id){this.cmid=id}getCMID(){return this.cmid}moveFormElementIntoTab(formElement,tabElement){const submitButtons=formElement.find(SELECTORS_SUBMIT_BUTTON_ELEMENT),footer=(0,_jquery.default)('');formElement.find(SELECTORS_FORM_HEADER).addClass("hidden"),formElement.wrap(tabElement),submitButtons.closest(SELECTORS_BUTTON_CONTAINER).remove(),submitButtons.appendTo(footer),footer.appendTo(formElement)}moveTabsIntoTabContent(form){const tabContent=this.getBody().find(SELECTORS_TAB_CONTENT).empty();form.find('[role="tabpanel"]').wrapAll(tabContent)}moveCancelButtonToTabs(form){const cancelButton=form.find(SELECTORS_CANCEL_BUTTON_ELEMENT).addClass("ml-1"),tabFooters=form.find('[data-region="footer"]');cancelButton.closest(SELECTORS_BUTTON_CONTAINER).remove(),cancelButton.clone().appendTo(tabFooters)}loadForm(){return Fragment.loadFragment("mod_quiz","add_random_question_form",this.getContextId(),{addonpage:this.getAddOnPageId(),cat:this.getCategory(),returnurl:this.getReturnUrl(),cmid:this.getCMID()}).then(((html,js)=>{const form=(0,_jquery.default)(html),existingCategoryFormElement=form.find(SELECTORS_EXISTING_CATEGORY_FORM_ELEMENT),existingCategoryTab=this.getBody().find(SELECTORS_EXISTING_CATEGORY_CONTAINER),newCategoryFormElement=form.find(SELECTORS_NEW_CATEGORY_FORM_ELEMENT),newCategoryTab=this.getBody().find(SELECTORS_NEW_CATEGORY_CONTAINER);this.moveFormElementIntoTab(existingCategoryFormElement,existingCategoryTab),this.moveFormElementIntoTab(newCategoryFormElement,newCategoryTab),this.moveTabsIntoTabContent(form),this.moveCancelButtonToTabs(form),Templates.replaceNode(this.getBody().find(SELECTORS_TAB_CONTENT),form,js)})).then((()=>{FormChangeChecker.disableAllChecks()})).catch(Notification.exception)}show(){super.show(this),this.loadedForm||(this.loadForm(),this.loadedForm=!0)}}return _exports.default=ModalAddRandomQuestion,_defineProperty(ModalAddRandomQuestion,"TYPE","mod_quiz-quiz-add-random-question"),_defineProperty(ModalAddRandomQuestion,"TEMPLATE","mod_quiz/modal_add_random_question"),ModalAddRandomQuestion.registerModalType(),_exports.default})); +define("mod_quiz/modal_add_random_question",["exports","jquery","core/modal","core/notification","core/fragment","core/templates","core_form/changechecker","core/ajax"],(function(_exports,_jquery,_modal,Notification,Fragment,Templates,FormChangeChecker,_ajax){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}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_modal=_interopRequireDefault(_modal),Notification=_interopRequireWildcard(Notification),Fragment=_interopRequireWildcard(Fragment),Templates=_interopRequireWildcard(Templates),FormChangeChecker=_interopRequireWildcard(FormChangeChecker);const SELECTORS={EXISTING_CATEGORY_CONTAINER:'[data-region="existing-category-container"]',EXISTING_CATEGORY_TAB:"#id_existingcategoryheader",NEW_CATEGORY_CONTAINER:'[data-region="new-category-container"]',NEW_CATEGORY_TAB:"#id_newcategoryheader",TAB_CONTENT:'[data-region="tab-content"]',ADD_ON_PAGE_FORM_ELEMENT:'[name="addonpage"]',ADD_RANDOM_BUTTON:'input[type="submit"][name="addrandom"]',ADD_NEW_CATEGORY_BUTTON:'input[type="submit"][name="newcategory"]',SUBMIT_BUTTON_ELEMENT:'input[type="submit"][name="addrandom"], input[type="submit"][name="newcategory"]',FORM_HEADER:"legend",SELECT_NUMBER_TO_ADD:"#menurandomcount",NEW_CATEGORY_ELEMENT:"#categoryname",PARENT_CATEGORY_ELEMENT:"#parentcategory",FILTER_CONDITION_ELEMENT:"[data-filtercondition]",FORM_ELEMENT:"#add_random_question_form",MESSAGE_INPUT:'[name="message"]'};class ModalAddRandomQuestion extends _modal.default{constructor(root){super(root),this.contextId=null,this.addOnPageId=null,this.category=null,this.returnUrl=null,this.cmid=null,this.loadedForm=!1}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id,this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id)}getAddOnPageId(){return this.addOnPageId}setCategory(category){this.category=category}getCategory(){return this.category}setReturnUrl(url){this.returnUrl=url}getReturnUrl(){return this.returnUrl}setCMID(id){this.cmid=id}getCMID(){return this.cmid}moveContentIntoTab(tabContent,tabElement){tabContent.find(SELECTORS.FORM_HEADER).addClass("hidden"),tabContent.wrap(tabElement)}moveTabsIntoTabContent(form){const tabContent=this.getBody().find(SELECTORS.TAB_CONTENT).empty();form.find('[role="tabpanel"]').wrapAll(tabContent)}moveCancelButtonToTabs(form){const cancelButton=form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass("ml-1"),tabFooters=form.find('[data-region="footer"]');cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove(),cancelButton.clone().appendTo(tabFooters)}loadForm(){const cmid=this.getCMID(),cat=this.getCategory(),addonpage=this.getAddOnPageId(),returnurl=this.getReturnUrl();return Fragment.loadFragment("mod_quiz","add_random_question_form",this.getContextId(),{addonpage:addonpage,cat:cat,returnurl:returnurl,cmid:cmid}).then(((html,js)=>{const form=(0,_jquery.default)(html),existingCategoryTabContent=form.find(SELECTORS.EXISTING_CATEGORY_TAB),existingCategoryTab=this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER),newCategoryTabContent=form.find(SELECTORS.NEW_CATEGORY_TAB),newCategoryTab=this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);this.moveContentIntoTab(existingCategoryTabContent,existingCategoryTab),this.moveContentIntoTab(newCategoryTabContent,newCategoryTab),this.moveTabsIntoTabContent(form),Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT),form,js)})).then((()=>{FormChangeChecker.disableAllChecks(),this.getBody()[0].addEventListener("click",(e=>{var _document$querySelect;if(!e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT))return;e.preventDefault();const randomcount=document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value,filtercondition=null===(_document$querySelect=document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset)||void 0===_document$querySelect?void 0:_document$querySelect.filtercondition;if(e.target.closest(SELECTORS.ADD_RANDOM_BUTTON))return void this.addQuestions(cmid,addonpage,randomcount,filtercondition,"","");e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON)&&this.addQuestions(cmid,addonpage,randomcount,filtercondition,document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value)}))})).catch(Notification.exception)}async addQuestions(cmid,addonpage,randomcount,filtercondition,newcategory,parentcategory){const call={methodname:"mod_quiz_add_random_questions",args:{cmid:cmid,addonpage:addonpage,randomcount:randomcount,filtercondition:filtercondition,newcategory:newcategory,parentcategory:parentcategory}};try{const response=await(0,_ajax.call)([call])[0],form=document.querySelector(SELECTORS.FORM_ELEMENT);form.querySelector(SELECTORS.MESSAGE_INPUT).value=response.message,form.submit()}catch(e){Notification.exception(e)}}show(){super.show(this),this.loadedForm||(this.loadForm(window.location.search),this.loadedForm=!0)}}return _exports.default=ModalAddRandomQuestion,_defineProperty(ModalAddRandomQuestion,"TYPE","mod_quiz-quiz-add-random-question"),_defineProperty(ModalAddRandomQuestion,"TEMPLATE","mod_quiz/modal_add_random_question"),ModalAddRandomQuestion.registerModalType(),_exports.default})); //# sourceMappingURL=modal_add_random_question.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/modal_add_random_question.min.js.map b/mod/quiz/amd/build/modal_add_random_question.min.js.map index 728f80bcca9..9ec71431ca5 100644 --- a/mod/quiz/amd/build/modal_add_random_question.min.js.map +++ b/mod/quiz/amd/build/modal_add_random_question.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal_add_random_question.min.js","sources":["../src/modal_add_random_question.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 * Contain the logic for the add random question modal.\n *\n * @module mod_quiz/modal_add_random_question\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Modal from 'core/modal';\nimport * as Notification from 'core/notification';\nimport * as Fragment from 'core/fragment';\nimport * as Templates from 'core/templates';\nimport * as FormChangeChecker from 'core_form/changechecker';\n\nconst SELECTORS = {\n EXISTING_CATEGORY_CONTAINER: '[data-region=\"existing-category-container\"]',\n EXISTING_CATEGORY_FORM_ELEMENT: '#id_existingcategoryheader',\n NEW_CATEGORY_CONTAINER: '[data-region=\"new-category-container\"]',\n NEW_CATEGORY_FORM_ELEMENT: '#id_newcategoryheader',\n TAB_CONTENT: '[data-region=\"tab-content\"]',\n ADD_ON_PAGE_FORM_ELEMENT: '[name=\"addonpage\"]',\n SUBMIT_BUTTON_ELEMENT: 'input[type=\"submit\"]',\n CANCEL_BUTTON_ELEMENT: 'input[type=\"submit\"][name=\"cancel\"]',\n FORM_HEADER: 'legend',\n BUTTON_CONTAINER: '.fitem',\n};\n\nexport default class ModalAddRandomQuestion extends Modal {\n static TYPE = 'mod_quiz-quiz-add-random-question';\n static TEMPLATE = 'mod_quiz/modal_add_random_question';\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n this.contextId = null;\n this.addOnPageId = null;\n this.category = null;\n this.returnUrl = null;\n this.cmid = null;\n this.loadedForm = false;\n }\n\n /**\n * Save the Moodle context id that the question bank is being\n * rendered in.\n *\n * @method setContextId\n * @param {int} id\n */\n setContextId(id) {\n this.contextId = id;\n }\n\n /**\n * Retrieve the saved Moodle context id.\n *\n * @method getContextId\n * @return {int}\n */\n getContextId() {\n return this.contextId;\n }\n\n /**\n * Set the id of the page that the question should be added to\n * when the user clicks the add to quiz link.\n *\n * @method setAddOnPageId\n * @param {int} id\n */\n setAddOnPageId(id) {\n this.addOnPageId = id;\n this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);\n }\n\n /**\n * Returns the saved page id for the question to be added to.\n *\n * @method getAddOnPageId\n * @return {int}\n */\n getAddOnPageId() {\n return this.addOnPageId;\n }\n\n /**\n * Set the category for this form. The category is a comma separated\n * category id and category context id.\n *\n * @method setCategory\n * @param {string} category\n */\n setCategory(category) {\n this.category = category;\n }\n\n /**\n * Returns the saved category.\n *\n * @method getCategory\n * @return {string}\n */\n getCategory() {\n return this.category;\n }\n\n /**\n * Set the return URL for the form.\n *\n * @method setReturnUrl\n * @param {string} url\n */\n setReturnUrl(url) {\n this.returnUrl = url;\n }\n\n /**\n * Returns the return URL for the form.\n *\n * @method getReturnUrl\n * @return {string}\n */\n getReturnUrl() {\n return this.returnUrl;\n }\n\n /**\n * Set the course module id for the form.\n *\n * @method setCMID\n * @param {int} id\n */\n setCMID(id) {\n this.cmid = id;\n }\n\n /**\n * Returns the course module id for the form.\n *\n * @method getCMID\n * @return {int}\n */\n getCMID() {\n return this.cmid;\n }\n\n /**\n * Moves a given form element inside (a child of) a given tab element.\n *\n * Hides the 'legend' (e.g. header) element of the form element because the\n * tab has the name.\n *\n * Moves the submit button into a footer element at the bottom of the form\n * element for styling purposes.\n *\n * @method moveFormElementIntoTab\n * @param {jquery} formElement The form element to move into the tab.\n * @param {jquey} tabElement The tab element for the form element to move into.\n */\n moveFormElementIntoTab(formElement, tabElement) {\n const submitButtons = formElement.find(SELECTORS.SUBMIT_BUTTON_ELEMENT);\n const footer = $('');\n // Hide the header because the tabs show us which part of the form we're\n // looking at.\n formElement.find(SELECTORS.FORM_HEADER).addClass('hidden');\n // Move the element inside a tab.\n formElement.wrap(tabElement);\n // Remove the buttons container element.\n submitButtons.closest(SELECTORS.BUTTON_CONTAINER).remove();\n // Put the button inside a footer.\n submitButtons.appendTo(footer);\n // Add the footer to the end of the category form element.\n footer.appendTo(formElement);\n }\n\n /**\n * Empty the tab content container and move all tabs from the form into the\n * tab container element.\n *\n * @method moveTabsIntoTabContent\n * @param {jquery} form The form element.\n */\n moveTabsIntoTabContent(form) {\n // Empty it to remove the loading icon.\n const tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();\n // Make sure all tabs are inside the tab content element.\n form.find('[role=\"tabpanel\"]').wrapAll(tabContent);\n }\n\n /**\n * Make sure all of the tabs have a cancel button in their fotter to sit along\n * side the submit button.\n *\n * @method moveCancelButtonToTabs\n * @param {jquey} form The form element.\n */\n moveCancelButtonToTabs(form) {\n const cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('ml-1');\n const tabFooters = form.find('[data-region=\"footer\"]');\n // Remove the buttons container element.\n cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();\n cancelButton.clone().appendTo(tabFooters);\n }\n\n /**\n * Load the add random question form in a fragement and perform some transformation\n * on the HTML to convert it into tabs for rendering in the modal.\n *\n * @method loadForm\n * @return {promise} Resolved with form HTML and JS.\n */\n loadForm() {\n return Fragment.loadFragment(\n 'mod_quiz',\n 'add_random_question_form',\n this.getContextId(),\n {\n addonpage: this.getAddOnPageId(),\n cat: this.getCategory(),\n returnurl: this.getReturnUrl(),\n cmid: this.getCMID()\n }\n )\n .then((html, js) =>{\n const form = $(html);\n const existingCategoryFormElement = form.find(SELECTORS.EXISTING_CATEGORY_FORM_ELEMENT);\n const existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);\n const newCategoryFormElement = form.find(SELECTORS.NEW_CATEGORY_FORM_ELEMENT);\n const newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);\n\n // Transform the form into tabs for better rendering in the modal.\n this.moveFormElementIntoTab(existingCategoryFormElement, existingCategoryTab);\n this.moveFormElementIntoTab(newCategoryFormElement, newCategoryTab);\n this.moveTabsIntoTabContent(form);\n this.moveCancelButtonToTabs(form);\n\n Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);\n return;\n })\n .then(() => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n return;\n })\n .catch(Notification.exception);\n }\n\n /**\n * Override the modal show function to load the form when this modal is first\n * shown.\n *\n * @method show\n */\n show() {\n super.show(this);\n\n if (!this.loadedForm) {\n this.loadForm();\n this.loadedForm = true;\n }\n }\n}\n\nModalAddRandomQuestion.registerModalType();\n"],"names":["SELECTORS","ModalAddRandomQuestion","Modal","constructor","root","contextId","addOnPageId","category","returnUrl","cmid","loadedForm","setContextId","id","getContextId","this","setAddOnPageId","getBody","find","val","getAddOnPageId","setCategory","getCategory","setReturnUrl","url","getReturnUrl","setCMID","getCMID","moveFormElementIntoTab","formElement","tabElement","submitButtons","footer","addClass","wrap","closest","remove","appendTo","moveTabsIntoTabContent","form","tabContent","empty","wrapAll","moveCancelButtonToTabs","cancelButton","tabFooters","clone","loadForm","Fragment","loadFragment","addonpage","cat","returnurl","then","html","js","existingCategoryFormElement","existingCategoryTab","newCategoryFormElement","newCategoryTab","Templates","replaceNode","FormChangeChecker","disableAllChecks","catch","Notification","exception","show","registerModalType"],"mappings":"2wDA8BMA,sCAC2B,8CAD3BA,yCAE8B,6BAF9BA,iCAGsB,yCAHtBA,oCAIyB,wBAJzBA,sBAKW,8BALXA,mCAMwB,qBANxBA,gCAOqB,uBAPrBA,gCAQqB,sCARrBA,sBASW,SATXA,2BAUgB,eAGDC,+BAA+BC,eAShDC,YAAYC,YACFA,WACDC,UAAY,UACZC,YAAc,UACdC,SAAW,UACXC,UAAY,UACZC,KAAO,UACPC,YAAa,EAUtBC,aAAaC,SACJP,UAAYO,GASrBC,sBACWC,KAAKT,UAUhBU,eAAeH,SACNN,YAAcM,QACdI,UAAUC,KAAKjB,oCAAoCkB,IAAIN,IAShEO,wBACWL,KAAKR,YAUhBc,YAAYb,eACHA,SAAWA,SASpBc,qBACWP,KAAKP,SAShBe,aAAaC,UACJf,UAAYe,IASrBC,sBACWV,KAAKN,UAShBiB,QAAQb,SACCH,KAAOG,GAShBc,iBACWZ,KAAKL,KAgBhBkB,uBAAuBC,YAAaC,kBAC1BC,cAAgBF,YAAYX,KAAKjB,iCACjC+B,QAAS,mBAAE,8DAGjBH,YAAYX,KAAKjB,uBAAuBgC,SAAS,UAEjDJ,YAAYK,KAAKJ,YAEjBC,cAAcI,QAAQlC,4BAA4BmC,SAElDL,cAAcM,SAASL,QAEvBA,OAAOK,SAASR,aAUpBS,uBAAuBC,YAEbC,WAAazB,KAAKE,UAAUC,KAAKjB,uBAAuBwC,QAE9DF,KAAKrB,KAAK,qBAAqBwB,QAAQF,YAU3CG,uBAAuBJ,YACbK,aAAeL,KAAKrB,KAAKjB,iCAAiCgC,SAAS,QACnEY,WAAaN,KAAKrB,KAAK,0BAE7B0B,aAAaT,QAAQlC,4BAA4BmC,SACjDQ,aAAaE,QAAQT,SAASQ,YAUlCE,kBACWC,SAASC,aACZ,WACA,2BACAlC,KAAKD,eACL,CACIoC,UAAWnC,KAAKK,iBAChB+B,IAAKpC,KAAKO,cACV8B,UAAWrC,KAAKU,eAChBf,KAAMK,KAAKY,YAGlB0B,MAAK,CAACC,KAAMC,YACHhB,MAAO,mBAAEe,MACTE,4BAA8BjB,KAAKrB,KAAKjB,0CACxCwD,oBAAsB1C,KAAKE,UAAUC,KAAKjB,uCAC1CyD,uBAAyBnB,KAAKrB,KAAKjB,qCACnC0D,eAAiB5C,KAAKE,UAAUC,KAAKjB,uCAGtC2B,uBAAuB4B,4BAA6BC,0BACpD7B,uBAAuB8B,uBAAwBC,qBAC/CrB,uBAAuBC,WACvBI,uBAAuBJ,MAE5BqB,UAAUC,YAAY9C,KAAKE,UAAUC,KAAKjB,uBAAwBsC,KAAMgB,OAG3EF,MAAK,KAGFS,kBAAkBC,sBAGrBC,MAAMC,aAAaC,WASxBC,aACUA,KAAKpD,MAENA,KAAKJ,kBACDoC,gBACApC,YAAa,mEA5OTT,8BACH,qDADGA,kCAEC,sCA+OtBA,uBAAuBkE"} \ No newline at end of file +{"version":3,"file":"modal_add_random_question.min.js","sources":["../src/modal_add_random_question.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 * Contain the logic for the add random question modal.\n *\n * @module mod_quiz/modal_add_random_question\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Modal from 'core/modal';\nimport * as Notification from 'core/notification';\nimport * as Fragment from 'core/fragment';\nimport * as Templates from 'core/templates';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport {call as fetchMany} from 'core/ajax';\n\nconst SELECTORS = {\n EXISTING_CATEGORY_CONTAINER: '[data-region=\"existing-category-container\"]',\n EXISTING_CATEGORY_TAB: '#id_existingcategoryheader',\n NEW_CATEGORY_CONTAINER: '[data-region=\"new-category-container\"]',\n NEW_CATEGORY_TAB: '#id_newcategoryheader',\n TAB_CONTENT: '[data-region=\"tab-content\"]',\n ADD_ON_PAGE_FORM_ELEMENT: '[name=\"addonpage\"]',\n ADD_RANDOM_BUTTON: 'input[type=\"submit\"][name=\"addrandom\"]',\n ADD_NEW_CATEGORY_BUTTON: 'input[type=\"submit\"][name=\"newcategory\"]',\n SUBMIT_BUTTON_ELEMENT: 'input[type=\"submit\"][name=\"addrandom\"], input[type=\"submit\"][name=\"newcategory\"]',\n FORM_HEADER: 'legend',\n SELECT_NUMBER_TO_ADD: '#menurandomcount',\n NEW_CATEGORY_ELEMENT: '#categoryname',\n PARENT_CATEGORY_ELEMENT: '#parentcategory',\n FILTER_CONDITION_ELEMENT: '[data-filtercondition]',\n FORM_ELEMENT: '#add_random_question_form',\n MESSAGE_INPUT: '[name=\"message\"]',\n};\n\nexport default class ModalAddRandomQuestion extends Modal {\n static TYPE = 'mod_quiz-quiz-add-random-question';\n static TEMPLATE = 'mod_quiz/modal_add_random_question';\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n this.contextId = null;\n this.addOnPageId = null;\n this.category = null;\n this.returnUrl = null;\n this.cmid = null;\n this.loadedForm = false;\n }\n\n /**\n * Save the Moodle context id that the question bank is being\n * rendered in.\n *\n * @method setContextId\n * @param {int} id\n */\n setContextId(id) {\n this.contextId = id;\n }\n\n /**\n * Retrieve the saved Moodle context id.\n *\n * @method getContextId\n * @return {int}\n */\n getContextId() {\n return this.contextId;\n }\n\n /**\n * Set the id of the page that the question should be added to\n * when the user clicks the add to quiz link.\n *\n * @method setAddOnPageId\n * @param {int} id\n */\n setAddOnPageId(id) {\n this.addOnPageId = id;\n this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);\n }\n\n /**\n * Returns the saved page id for the question to be added to.\n *\n * @method getAddOnPageId\n * @return {int}\n */\n getAddOnPageId() {\n return this.addOnPageId;\n }\n\n /**\n * Set the category for this form. The category is a comma separated\n * category id and category context id.\n *\n * @method setCategory\n * @param {string} category\n */\n setCategory(category) {\n this.category = category;\n }\n\n /**\n * Returns the saved category.\n *\n * @method getCategory\n * @return {string}\n */\n getCategory() {\n return this.category;\n }\n\n /**\n * Set the return URL for the form.\n *\n * @method setReturnUrl\n * @param {string} url\n */\n setReturnUrl(url) {\n this.returnUrl = url;\n }\n\n /**\n * Returns the return URL for the form.\n *\n * @method getReturnUrl\n * @return {string}\n */\n getReturnUrl() {\n return this.returnUrl;\n }\n\n /**\n * Set the course module id for the form.\n *\n * @method setCMID\n * @param {int} id\n */\n setCMID(id) {\n this.cmid = id;\n }\n\n /**\n * Returns the course module id for the form.\n *\n * @method getCMID\n * @return {int}\n */\n getCMID() {\n return this.cmid;\n }\n\n /**\n * Moves a given form element inside (a child of) a given tab element.\n *\n * Hides the 'legend' (e.g. header) element of the form element because the\n * tab has the name.\n *\n * Moves the submit button into a footer element at the bottom of the form\n * element for styling purposes.\n *\n * @method moveContentIntoTab\n * @param {jquery} tabContent The form element to move into the tab.\n * @param {jquey} tabElement The tab element for the form element to move into.\n */\n moveContentIntoTab(tabContent, tabElement) {\n // Hide the header because the tabs show us which part of the form we're\n // looking at.\n tabContent.find(SELECTORS.FORM_HEADER).addClass('hidden');\n // Move the element inside a tab.\n tabContent.wrap(tabElement);\n }\n\n /**\n * Empty the tab content container and move all tabs from the form into the\n * tab container element.\n *\n * @method moveTabsIntoTabContent\n * @param {jquery} form The form element.\n */\n moveTabsIntoTabContent(form) {\n // Empty it to remove the loading icon.\n const tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();\n // Make sure all tabs are inside the tab content element.\n form.find('[role=\"tabpanel\"]').wrapAll(tabContent);\n }\n\n /**\n * Make sure all of the tabs have a cancel button in their fotter to sit along\n * side the submit button.\n *\n * @method moveCancelButtonToTabs\n * @param {jquey} form The form element.\n */\n moveCancelButtonToTabs(form) {\n const cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('ml-1');\n const tabFooters = form.find('[data-region=\"footer\"]');\n // Remove the buttons container element.\n cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();\n cancelButton.clone().appendTo(tabFooters);\n }\n\n /**\n * Load the add random question form in a fragement and perform some transformation\n * on the HTML to convert it into tabs for rendering in the modal.\n *\n * @method loadForm\n * @return {promise} Resolved with form HTML and JS.\n */\n loadForm() {\n const cmid = this.getCMID();\n const cat = this.getCategory();\n const addonpage = this.getAddOnPageId();\n const returnurl = this.getReturnUrl();\n\n return Fragment.loadFragment(\n 'mod_quiz',\n 'add_random_question_form',\n this.getContextId(),\n {\n addonpage,\n cat,\n returnurl,\n cmid,\n }\n )\n .then((html, js) =>{\n const form = $(html);\n const existingCategoryTabContent = form.find(SELECTORS.EXISTING_CATEGORY_TAB);\n const existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);\n const newCategoryTabContent = form.find(SELECTORS.NEW_CATEGORY_TAB);\n const newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);\n\n // Transform the form into tabs for better rendering in the modal.\n this.moveContentIntoTab(existingCategoryTabContent, existingCategoryTab);\n this.moveContentIntoTab(newCategoryTabContent, newCategoryTab);\n this.moveTabsIntoTabContent(form);\n\n Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);\n return;\n })\n .then(() => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n\n // Add question to quiz.\n this.getBody()[0].addEventListener('click', (e) => {\n const button = e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT);\n if (!button) {\n return;\n }\n e.preventDefault();\n\n const randomcount = document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value;\n const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition;\n\n // Add Random questions if the add random button was clicked.\n const addRandomButton = e.target.closest(SELECTORS.ADD_RANDOM_BUTTON);\n if (addRandomButton) {\n this.addQuestions(cmid, addonpage, randomcount, filtercondition, '', '');\n return;\n }\n // Add new category if the add category button was clicked.\n const addCategoryButton = e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON);\n if (addCategoryButton) {\n this.addQuestions(\n cmid,\n addonpage,\n randomcount,\n filtercondition,\n document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value,\n document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value\n );\n return;\n }\n });\n })\n .catch(Notification.exception);\n }\n\n /**\n * Call web service function to add random questions\n *\n * @param {number} cmid course module id\n * @param {number} addonpage the page where random questions will be added to\n * @param {number} randomcount Number of random questions\n * @param {string} filtercondition Filter condition\n * @param {string} newcategory add new category\n * @param {string} parentcategory parent category of new category\n */\n async addQuestions(\n cmid,\n addonpage,\n randomcount,\n filtercondition,\n newcategory,\n parentcategory\n ) {\n const call = {\n methodname: 'mod_quiz_add_random_questions',\n args: {\n cmid,\n addonpage,\n randomcount,\n filtercondition,\n newcategory,\n parentcategory,\n }\n };\n try {\n const response = await fetchMany([call])[0];\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\n const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT);\n messageInput.value = response.message;\n form.submit();\n } catch (e) {\n Notification.exception(e);\n }\n }\n\n /**\n * Override the modal show function to load the form when this modal is first\n * shown.\n *\n * @method show\n */\n show() {\n super.show(this);\n\n if (!this.loadedForm) {\n this.loadForm(window.location.search);\n this.loadedForm = true;\n }\n }\n}\n\nModalAddRandomQuestion.registerModalType();\n"],"names":["SELECTORS","EXISTING_CATEGORY_CONTAINER","EXISTING_CATEGORY_TAB","NEW_CATEGORY_CONTAINER","NEW_CATEGORY_TAB","TAB_CONTENT","ADD_ON_PAGE_FORM_ELEMENT","ADD_RANDOM_BUTTON","ADD_NEW_CATEGORY_BUTTON","SUBMIT_BUTTON_ELEMENT","FORM_HEADER","SELECT_NUMBER_TO_ADD","NEW_CATEGORY_ELEMENT","PARENT_CATEGORY_ELEMENT","FILTER_CONDITION_ELEMENT","FORM_ELEMENT","MESSAGE_INPUT","ModalAddRandomQuestion","Modal","constructor","root","contextId","addOnPageId","category","returnUrl","cmid","loadedForm","setContextId","id","getContextId","this","setAddOnPageId","getBody","find","val","getAddOnPageId","setCategory","getCategory","setReturnUrl","url","getReturnUrl","setCMID","getCMID","moveContentIntoTab","tabContent","tabElement","addClass","wrap","moveTabsIntoTabContent","form","empty","wrapAll","moveCancelButtonToTabs","cancelButton","CANCEL_BUTTON_ELEMENT","tabFooters","closest","BUTTON_CONTAINER","remove","clone","appendTo","loadForm","cat","addonpage","returnurl","Fragment","loadFragment","then","html","js","existingCategoryTabContent","existingCategoryTab","newCategoryTabContent","newCategoryTab","Templates","replaceNode","FormChangeChecker","disableAllChecks","addEventListener","e","target","preventDefault","randomcount","document","querySelector","value","filtercondition","dataset","_document$querySelect","addQuestions","catch","Notification","exception","newcategory","parentcategory","call","methodname","args","response","message","submit","show","window","location","search","registerModalType"],"mappings":"6xDA+BMA,UAAY,CACdC,4BAA6B,8CAC7BC,sBAAuB,6BACvBC,uBAAwB,yCACxBC,iBAAkB,wBAClBC,YAAa,8BACbC,yBAA0B,qBAC1BC,kBAAmB,yCACnBC,wBAAyB,2CACzBC,sBAAuB,mFACvBC,YAAa,SACbC,qBAAsB,mBACtBC,qBAAsB,gBACtBC,wBAAyB,kBACzBC,yBAA0B,yBAC1BC,aAAc,4BACdC,cAAe,0BAGEC,+BAA+BC,eAShDC,YAAYC,YACFA,WACDC,UAAY,UACZC,YAAc,UACdC,SAAW,UACXC,UAAY,UACZC,KAAO,UACPC,YAAa,EAUtBC,aAAaC,SACJP,UAAYO,GASrBC,sBACWC,KAAKT,UAUhBU,eAAeH,SACNN,YAAcM,QACdI,UAAUC,KAAKjC,UAAUM,0BAA0B4B,IAAIN,IAShEO,wBACWL,KAAKR,YAUhBc,YAAYb,eACHA,SAAWA,SASpBc,qBACWP,KAAKP,SAShBe,aAAaC,UACJf,UAAYe,IASrBC,sBACWV,KAAKN,UAShBiB,QAAQb,SACCH,KAAOG,GAShBc,iBACWZ,KAAKL,KAgBhBkB,mBAAmBC,WAAYC,YAG3BD,WAAWX,KAAKjC,UAAUU,aAAaoC,SAAS,UAEhDF,WAAWG,KAAKF,YAUpBG,uBAAuBC,YAEbL,WAAad,KAAKE,UAAUC,KAAKjC,UAAUK,aAAa6C,QAE9DD,KAAKhB,KAAK,qBAAqBkB,QAAQP,YAU3CQ,uBAAuBH,YACbI,aAAeJ,KAAKhB,KAAKjC,UAAUsD,uBAAuBR,SAAS,QACnES,WAAaN,KAAKhB,KAAK,0BAE7BoB,aAAaG,QAAQxD,UAAUyD,kBAAkBC,SACjDL,aAAaM,QAAQC,SAASL,YAUlCM,iBACUpC,KAAOK,KAAKY,UACZoB,IAAMhC,KAAKO,cACX0B,UAAYjC,KAAKK,iBACjB6B,UAAYlC,KAAKU,sBAEhByB,SAASC,aACZ,WACA,2BACApC,KAAKD,eACL,CACIkC,UAAAA,UACAD,IAAAA,IACAE,UAAAA,UACAvC,KAAAA,OAGP0C,MAAK,CAACC,KAAMC,YACHpB,MAAO,mBAAEmB,MACTE,2BAA6BrB,KAAKhB,KAAKjC,UAAUE,uBACjDqE,oBAAsBzC,KAAKE,UAAUC,KAAKjC,UAAUC,6BACpDuE,sBAAwBvB,KAAKhB,KAAKjC,UAAUI,kBAC5CqE,eAAiB3C,KAAKE,UAAUC,KAAKjC,UAAUG,6BAGhDwC,mBAAmB2B,2BAA4BC,0BAC/C5B,mBAAmB6B,sBAAuBC,qBAC1CzB,uBAAuBC,MAE5ByB,UAAUC,YAAY7C,KAAKE,UAAUC,KAAKjC,UAAUK,aAAc4C,KAAMoB,OAG3EF,MAAK,KAGFS,kBAAkBC,wBAGb7C,UAAU,GAAG8C,iBAAiB,SAAUC,kCAC1BA,EAAEC,OAAOxB,QAAQxD,UAAUS,8BAI1CsE,EAAEE,uBAEIC,YAAcC,SAASC,cAAcpF,UAAUW,sBAAsB0E,MACrEC,8CAAkBH,SAASC,cAAcpF,UAAUc,0BAA0ByE,gDAA3DC,sBAAoEF,mBAGpEP,EAAEC,OAAOxB,QAAQxD,UAAUO,oCAE1CkF,aAAahE,KAAMsC,UAAWmB,YAAaI,gBAAiB,GAAI,IAI/CP,EAAEC,OAAOxB,QAAQxD,UAAUQ,+BAE5CiF,aACDhE,KACAsC,UACAmB,YACAI,gBACAH,SAASC,cAAcpF,UAAUY,sBAAsByE,MACvDF,SAASC,cAAcpF,UAAUa,yBAAyBwE,aAMzEK,MAAMC,aAAaC,8BAcpBnE,KACAsC,UACAmB,YACAI,gBACAO,YACAC,sBAEMC,KAAO,CACTC,WAAY,gCACZC,KAAM,CACFxE,KAAAA,KACAsC,UAAAA,UACAmB,YAAAA,YACAI,gBAAAA,gBACAO,YAAAA,YACAC,eAAAA,2BAIEI,eAAiB,cAAU,CAACH,OAAO,GACnC9C,KAAOkC,SAASC,cAAcpF,UAAUe,cACzBkC,KAAKmC,cAAcpF,UAAUgB,eACrCqE,MAAQa,SAASC,QAC9BlD,KAAKmD,SACP,MAAOrB,GACLY,aAAaC,UAAUb,IAU/BsB,aACUA,KAAKvE,MAENA,KAAKJ,kBACDmC,SAASyC,OAAOC,SAASC,aACzB9E,YAAa,mEA/STT,8BACH,qDADGA,kCAEC,sCAkTtBA,uBAAuBwF"} \ No newline at end of file diff --git a/mod/quiz/amd/build/modal_quiz_question_bank.min.js b/mod/quiz/amd/build/modal_quiz_question_bank.min.js index 9b8b9a7c194..d9285224524 100644 --- a/mod/quiz/amd/build/modal_quiz_question_bank.min.js +++ b/mod/quiz/amd/build/modal_quiz_question_bank.min.js @@ -1,3 +1,3 @@ -define("mod_quiz/modal_quiz_question_bank",["exports","jquery","core/modal","core/fragment","core_form/changechecker","core/modal_events"],(function(_exports,_jquery,_modal,Fragment,FormChangeChecker,ModalEvents){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}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_modal=_interopRequireDefault(_modal),Fragment=_interopRequireWildcard(Fragment),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTORS_ADD_TO_QUIZ_CONTAINER="td.addtoquizaction",SELECTORS_ANCHOR="a[href]",SELECTORS_PREVIEW_CONTAINER="td.previewaction",SELECTORS_SEARCH_OPTIONS="#advancedsearch",SELECTORS_DISPLAY_OPTIONS="#displayoptions",SELECTORS_ADD_QUESTIONS_FORM="form#questionsubmit";class ModalQuizQuestionBank extends _modal.default{constructor(root){super(root),this.contextId=null,this.addOnPageId=null}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id}getAddOnPageId(){return this.addOnPageId}show(){return this.reloadBodyContent(window.location.search),super.show(this)}reloadBodyContent(querystring){this.setBody(Fragment.loadFragment("mod_quiz","quiz_question_bank",this.getContextId(),{querystring:querystring}))}handleAddToQuizEvent(e,anchorElement){const href=new URL(anchorElement.attr("href"));href.searchParams.set("addonpage",this.getAddOnPageId()),anchorElement.attr("href",href)}handlePreviewContainerEvent(e,anchorElement){window.openpopup(e,{url:anchorElement.attr("href"),name:"questionpreview",options:["height=600","width=800","top=0","left=0","menubar=0","location=0","scrollbars","resizable","toolbar","status","directories=0","fullscreen=0","dependent"].join(",")})}handleDisplayOptionFormEvent(e){e.stopPropagation(),e.preventDefault();const queryString="?"+(0,_jquery.default)(e.target).closest(SELECTORS_DISPLAY_OPTIONS).serialize();this.reloadBodyContent(queryString)}registerDisplayOptionListeners(){this.getModal().on("change",SELECTORS_DISPLAY_OPTIONS,(e=>{(0,_jquery.default)(e.target).attr("aria-autocomplete")||this.handleDisplayOptionFormEvent(e)})),this.getModal().on("submit",SELECTORS_DISPLAY_OPTIONS,(e=>{this.handleDisplayOptionFormEvent(e)}))}registerEventListeners(){super.registerEventListeners(this),this.registerDisplayOptionListeners(),this.getModal().on("submit",SELECTORS_ADD_QUESTIONS_FORM,(e=>{const formElement=(0,_jquery.default)(e.currentTarget);(0,_jquery.default)("").attr("type","hidden").attr("name","addonpage").attr("value",this.getAddOnPageId()).appendTo(formElement)})),this.getModal().on("click",SELECTORS_ANCHOR,(e=>{const anchorElement=(0,_jquery.default)(e.currentTarget);anchorElement.closest(SELECTORS_ADD_TO_QUIZ_CONTAINER).length?this.handleAddToQuizEvent(e,anchorElement):anchorElement.closest(SELECTORS_PREVIEW_CONTAINER).length?this.handlePreviewContainerEvent(e,anchorElement):anchorElement.closest(SELECTORS_SEARCH_OPTIONS).length||(e.preventDefault(),this.reloadBodyContent(anchorElement.prop("search")))})),this.getRoot().on(ModalEvents.bodyRendered,(()=>{FormChangeChecker.disableAllChecks()}))}}var obj,key,value;return _exports.default=ModalQuizQuestionBank,value="mod_quiz-quiz-question-bank",(key="TYPE")in(obj=ModalQuizQuestionBank)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,ModalQuizQuestionBank.registerModalType(),_exports.default})); +define("mod_quiz/modal_quiz_question_bank",["exports","jquery","core/modal","core/fragment","core_form/changechecker","core/modal_events"],(function(_exports,_jquery,_modal,Fragment,FormChangeChecker,ModalEvents){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}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_modal=_interopRequireDefault(_modal),Fragment=_interopRequireWildcard(Fragment),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTORS_ADD_TO_QUIZ_CONTAINER="td.addtoquizaction",SELECTORS_ANCHOR="a[href]",SELECTORS_PREVIEW_CONTAINER="td.previewaction",SELECTORS_ADD_QUESTIONS_FORM="form#questionsubmit",SELECTORS_SORTERS=".sorters";class ModalQuizQuestionBank extends _modal.default{constructor(root){super(root),this.contextId=null,this.addOnPageId=null}setContextId(id){this.contextId=id}getContextId(){return this.contextId}setAddOnPageId(id){this.addOnPageId=id}getAddOnPageId(){return this.addOnPageId}show(){return this.reloadBodyContent(window.location.search),super.show(this)}reloadBodyContent(querystring){this.setBody(Fragment.loadFragment("mod_quiz","quiz_question_bank",this.getContextId(),{querystring:querystring}))}handleAddToQuizEvent(e,anchorElement){const href=new URL(anchorElement.attr("href"));href.searchParams.set("addonpage",this.getAddOnPageId()),anchorElement.attr("href",href)}handlePreviewContainerEvent(e,anchorElement){window.openpopup(e,{url:anchorElement.attr("href"),name:"questionpreview",options:["height=600","width=800","top=0","left=0","menubar=0","location=0","scrollbars","resizable","toolbar","status","directories=0","fullscreen=0","dependent"].join(",")})}registerEventListeners(){super.registerEventListeners(this),this.getModal().on("submit",SELECTORS_ADD_QUESTIONS_FORM,(e=>{const formElement=(0,_jquery.default)(e.currentTarget);(0,_jquery.default)("").attr("type","hidden").attr("name","addonpage").attr("value",this.getAddOnPageId()).appendTo(formElement)})),this.getModal().on("click",SELECTORS_ANCHOR,(e=>{const anchorElement=(0,_jquery.default)(e.currentTarget);anchorElement.closest(SELECTORS_ADD_TO_QUIZ_CONTAINER).length?this.handleAddToQuizEvent(e,anchorElement):anchorElement.closest(SELECTORS_PREVIEW_CONTAINER).length?this.handlePreviewContainerEvent(e,anchorElement):anchorElement.closest(SELECTORS_SORTERS).length||(e.preventDefault(),this.reloadBodyContent(anchorElement.prop("search")))})),this.getRoot().on(ModalEvents.bodyRendered,(()=>{FormChangeChecker.disableAllChecks()}))}}var obj,key,value;return _exports.default=ModalQuizQuestionBank,value="mod_quiz-quiz-question-bank",(key="TYPE")in(obj=ModalQuizQuestionBank)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,ModalQuizQuestionBank.registerModalType(),_exports.default})); //# sourceMappingURL=modal_quiz_question_bank.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map b/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map index 46f0ef7bff8..9eb5605e017 100644 --- a/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map +++ b/mod/quiz/amd/build/modal_quiz_question_bank.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal_quiz_question_bank.min.js","sources":["../src/modal_quiz_question_bank.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 * Contain the logic for the question bank modal.\n *\n * @module mod_quiz/modal_quiz_question_bank\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Modal from 'core/modal';\nimport * as Fragment from 'core/fragment';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTORS = {\n ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction',\n ANCHOR: 'a[href]',\n PREVIEW_CONTAINER: 'td.previewaction',\n SEARCH_OPTIONS: '#advancedsearch',\n DISPLAY_OPTIONS: '#displayoptions',\n ADD_QUESTIONS_FORM: 'form#questionsubmit',\n};\n\nexport default class ModalQuizQuestionBank extends Modal {\n static TYPE = 'mod_quiz-quiz-question-bank';\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n\n this.contextId = null;\n this.addOnPageId = null;\n }\n\n /**\n * Save the Moodle context id that the question bank is being\n * rendered in.\n *\n * @method setContextId\n * @param {int} id\n */\n setContextId(id) {\n this.contextId = id;\n }\n\n /**\n * Retrieve the saved Moodle context id.\n *\n * @method getContextId\n * @return {int}\n */\n getContextId() {\n return this.contextId;\n }\n\n /**\n * Set the id of the page that the question should be added to\n * when the user clicks the add to quiz link.\n *\n * @method setAddOnPageId\n * @param {int} id\n */\n setAddOnPageId(id) {\n this.addOnPageId = id;\n }\n\n /**\n * Returns the saved page id for the question to be added it.\n *\n * @method getAddOnPageId\n * @return {int}\n */\n getAddOnPageId() {\n return this.addOnPageId;\n }\n\n /**\n * Override the parent show function.\n *\n * Reload the body contents when the modal is shown. The current\n * window URL is used to inform the new content that should be\n * displayed.\n *\n * @method show\n * @return {void}\n */\n show() {\n this.reloadBodyContent(window.location.search);\n return super.show(this);\n }\n\n /**\n * Replaces the current body contents with a new version of the question\n * bank.\n *\n * The contents of the question bank are generated using the provided\n * query string.\n *\n * @method reloadBodyContent\n * @param {string} querystring URL encoded string.\n */\n reloadBodyContent(querystring) {\n // Load the question bank fragment to be displayed in the modal.\n this.setBody(Fragment.loadFragment(\n 'mod_quiz',\n 'quiz_question_bank',\n this.getContextId(),\n {\n querystring,\n }\n ));\n }\n\n /**\n * Update the URL of the anchor element that the user clicked on to make\n * sure that the question is added to the correct page.\n *\n * @method handleAddToQuizEvent\n * @param {event} e A JavaScript event\n * @param {object} anchorElement The anchor element that was triggered\n */\n handleAddToQuizEvent(e, anchorElement) {\n // If the user clicks the plus icon to add the question to the page\n // directly then we need to intercept the click in order to adjust the\n // href and include the correct add on page id before the page is\n // redirected.\n const href = new URL(anchorElement.attr('href'));\n href.searchParams.set('addonpage', this.getAddOnPageId());\n anchorElement.attr('href', href);\n }\n\n /**\n * Open a popup window to show the preview of the question.\n *\n * @method handlePreviewContainerEvent\n * @param {event} e A JavaScript event\n * @param {object} anchorElement The anchor element that was triggered\n */\n handlePreviewContainerEvent(e, anchorElement) {\n const popupOptions = [\n 'height=600',\n 'width=800',\n 'top=0',\n 'left=0',\n 'menubar=0',\n 'location=0',\n 'scrollbars',\n 'resizable',\n 'toolbar',\n 'status',\n 'directories=0',\n 'fullscreen=0',\n 'dependent',\n ];\n window.openpopup(e, {\n url: anchorElement.attr('href'),\n name: 'questionpreview',\n options: popupOptions.join(',')\n });\n }\n\n /**\n * Reload the modal body with the new display options the user has selected.\n *\n * A query string is built using the form elements to be used to generate the\n * new body content.\n *\n * @method handleDisplayOptionFormEvent\n * @param {event} e A JavaScript event\n */\n handleDisplayOptionFormEvent(e) {\n // Stop propagation to prevent other wild event handlers\n // from submitting the form on change.\n e.stopPropagation();\n e.preventDefault();\n\n const form = $(e.target).closest(SELECTORS.DISPLAY_OPTIONS);\n const queryString = '?' + form.serialize();\n this.reloadBodyContent(queryString);\n }\n\n /**\n * Listen for changes to the display options form.\n *\n * This handles the user changing:\n * - The quiz category select box\n * - The tags to filter by\n * - Show/hide questions from sub categories\n * - Show/hide old questions\n *\n * @method registerDisplayOptionListeners\n */\n registerDisplayOptionListeners() {\n // Listen for changes to the display options form.\n this.getModal().on('change', SELECTORS.DISPLAY_OPTIONS, (e) => {\n // Get the element that was changed.\n const modifiedElement = $(e.target);\n if (modifiedElement.attr('aria-autocomplete')) {\n // If the element that was change is the autocomplete\n // input then we should ignore it because that is for\n // display purposes only.\n return;\n }\n\n this.handleDisplayOptionFormEvent(e);\n });\n\n // Listen for the display options form submission because the tags\n // filter will submit the form when it is changed.\n this.getModal().on('submit', SELECTORS.DISPLAY_OPTIONS, (e) => {\n this.handleDisplayOptionFormEvent(e);\n });\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n // Set up the event handlers for all of the display options.\n this.registerDisplayOptionListeners();\n\n this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => {\n // If the user clicks on the \"Add selected questions to the quiz\" button to add some questions to the page\n // then we need to intercept the submit in order to include the correct \"add on page id\" before the form is\n // submitted.\n const formElement = $(e.currentTarget);\n\n $('').attr('type', 'hidden')\n .attr('name', \"addonpage\")\n .attr('value', this.getAddOnPageId())\n .appendTo(formElement);\n });\n\n this.getModal().on('click', SELECTORS.ANCHOR, (e) => {\n const anchorElement = $(e.currentTarget);\n\n // If the anchor element was the add to quiz link.\n if (anchorElement.closest(SELECTORS.ADD_TO_QUIZ_CONTAINER).length) {\n this.handleAddToQuizEvent(e, anchorElement);\n return;\n }\n\n // If the anchor element was a preview question link.\n if (anchorElement.closest(SELECTORS.PREVIEW_CONTAINER).length) {\n this.handlePreviewContainerEvent(e, anchorElement);\n return;\n }\n\n // Click on expand/collaspse search-options. Has its own handler.\n // We should not interfere.\n if (anchorElement.closest(SELECTORS.SEARCH_OPTIONS).length) {\n return;\n }\n\n // Anything else means reload the pop-up contents.\n e.preventDefault();\n this.reloadBodyContent(anchorElement.prop('search'));\n });\n\n // Disable the form change checker when the body is rendered.\n this.getRoot().on(ModalEvents.bodyRendered, () => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n });\n }\n}\n\nModalQuizQuestionBank.registerModalType();\n"],"names":["SELECTORS","ModalQuizQuestionBank","Modal","constructor","root","contextId","addOnPageId","setContextId","id","getContextId","this","setAddOnPageId","getAddOnPageId","show","reloadBodyContent","window","location","search","super","querystring","setBody","Fragment","loadFragment","handleAddToQuizEvent","e","anchorElement","href","URL","attr","searchParams","set","handlePreviewContainerEvent","openpopup","url","name","options","join","handleDisplayOptionFormEvent","stopPropagation","preventDefault","queryString","target","closest","serialize","registerDisplayOptionListeners","getModal","on","registerEventListeners","formElement","currentTarget","appendTo","length","prop","getRoot","ModalEvents","bodyRendered","FormChangeChecker","disableAllChecks","registerModalType"],"mappings":"2hDA6BMA,gCACqB,qBADrBA,iBAEM,UAFNA,4BAGiB,mBAHjBA,yBAIc,kBAJdA,0BAKe,kBALfA,6BAMkB,4BAGHC,8BAA8BC,eAQ/CC,YAAYC,YACFA,WAEDC,UAAY,UACZC,YAAc,KAUvBC,aAAaC,SACJH,UAAYG,GASrBC,sBACWC,KAAKL,UAUhBM,eAAeH,SACNF,YAAcE,GASvBI,wBACWF,KAAKJ,YAahBO,mBACSC,kBAAkBC,OAAOC,SAASC,QAChCC,MAAML,KAAKH,MAatBI,kBAAkBK,kBAETC,QAAQC,SAASC,aAClB,WACA,qBACAZ,KAAKD,eACL,CACIU,YAAAA,eAaZI,qBAAqBC,EAAGC,qBAKdC,KAAO,IAAIC,IAAIF,cAAcG,KAAK,SACxCF,KAAKG,aAAaC,IAAI,YAAapB,KAAKE,kBACxCa,cAAcG,KAAK,OAAQF,MAU/BK,4BAA4BP,EAAGC,eAgB3BV,OAAOiB,UAAUR,EAAG,CAChBS,IAAKR,cAAcG,KAAK,QACxBM,KAAM,kBACNC,QAlBiB,CACjB,aACA,YACA,QACA,SACA,YACA,aACA,aACA,YACA,UACA,SACA,gBACA,eACA,aAKsBC,KAAK,OAanCC,6BAA6Bb,GAGzBA,EAAEc,kBACFd,EAAEe,uBAGIC,YAAc,KADP,mBAAEhB,EAAEiB,QAAQC,QAAQ1C,2BACF2C,iBAC1B7B,kBAAkB0B,aAc3BI,sCAESC,WAAWC,GAAG,SAAU9C,2BAA4BwB,KAE7B,mBAAEA,EAAEiB,QACRb,KAAK,2BAOpBS,6BAA6Bb,WAKjCqB,WAAWC,GAAG,SAAU9C,2BAA4BwB,SAChDa,6BAA6Bb,MAS1CuB,+BAEUA,uBAAuBrC,WAGxBkC,sCAEAC,WAAWC,GAAG,SAAU9C,8BAA+BwB,UAIlDwB,aAAc,mBAAExB,EAAEyB,mCAEtB,aAAarB,KAAK,OAAQ,UACvBA,KAAK,OAAQ,aACbA,KAAK,QAASlB,KAAKE,kBACnBsC,SAASF,qBAGbH,WAAWC,GAAG,QAAS9C,kBAAmBwB,UACrCC,eAAgB,mBAAED,EAAEyB,eAGtBxB,cAAciB,QAAQ1C,iCAAiCmD,YAClD5B,qBAAqBC,EAAGC,eAK7BA,cAAciB,QAAQ1C,6BAA6BmD,YAC9CpB,4BAA4BP,EAAGC,eAMpCA,cAAciB,QAAQ1C,0BAA0BmD,SAKpD3B,EAAEe,sBACGzB,kBAAkBW,cAAc2B,KAAK,oBAIzCC,UAAUP,GAAGQ,YAAYC,cAAc,KAGxCC,kBAAkBC,6FAxPZ,iDADGxD,6HA8PrBA,sBAAsByD"} \ No newline at end of file +{"version":3,"file":"modal_quiz_question_bank.min.js","sources":["../src/modal_quiz_question_bank.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 * Contain the logic for the question bank modal.\n *\n * @module mod_quiz/modal_quiz_question_bank\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Modal from 'core/modal';\nimport * as Fragment from 'core/fragment';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as ModalEvents from 'core/modal_events';\n\nconst SELECTORS = {\n ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction',\n ANCHOR: 'a[href]',\n PREVIEW_CONTAINER: 'td.previewaction',\n ADD_QUESTIONS_FORM: 'form#questionsubmit',\n SORTERS: '.sorters',\n};\n\nexport default class ModalQuizQuestionBank extends Modal {\n static TYPE = 'mod_quiz-quiz-question-bank';\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n\n this.contextId = null;\n this.addOnPageId = null;\n }\n\n /**\n * Save the Moodle context id that the question bank is being\n * rendered in.\n *\n * @method setContextId\n * @param {int} id\n */\n setContextId(id) {\n this.contextId = id;\n }\n\n /**\n * Retrieve the saved Moodle context id.\n *\n * @method getContextId\n * @return {int}\n */\n getContextId() {\n return this.contextId;\n }\n\n /**\n * Set the id of the page that the question should be added to\n * when the user clicks the add to quiz link.\n *\n * @method setAddOnPageId\n * @param {int} id\n */\n setAddOnPageId(id) {\n this.addOnPageId = id;\n }\n\n /**\n * Returns the saved page id for the question to be added it.\n *\n * @method getAddOnPageId\n * @return {int}\n */\n getAddOnPageId() {\n return this.addOnPageId;\n }\n\n /**\n * Override the parent show function.\n *\n * Reload the body contents when the modal is shown. The current\n * window URL is used to inform the new content that should be\n * displayed.\n *\n * @method show\n * @return {void}\n */\n show() {\n this.reloadBodyContent(window.location.search);\n return super.show(this);\n }\n\n /**\n * Replaces the current body contents with a new version of the question\n * bank.\n *\n * The contents of the question bank are generated using the provided\n * query string.\n *\n * @method reloadBodyContent\n * @param {string} querystring URL encoded string.\n */\n reloadBodyContent(querystring) {\n // Load the question bank fragment to be displayed in the modal.\n this.setBody(Fragment.loadFragment(\n 'mod_quiz',\n 'quiz_question_bank',\n this.getContextId(),\n {\n querystring,\n }\n ));\n }\n\n /**\n * Update the URL of the anchor element that the user clicked on to make\n * sure that the question is added to the correct page.\n *\n * @method handleAddToQuizEvent\n * @param {event} e A JavaScript event\n * @param {object} anchorElement The anchor element that was triggered\n */\n handleAddToQuizEvent(e, anchorElement) {\n // If the user clicks the plus icon to add the question to the page\n // directly then we need to intercept the click in order to adjust the\n // href and include the correct add on page id before the page is\n // redirected.\n const href = new URL(anchorElement.attr('href'));\n href.searchParams.set('addonpage', this.getAddOnPageId());\n anchorElement.attr('href', href);\n }\n\n /**\n * Open a popup window to show the preview of the question.\n *\n * @method handlePreviewContainerEvent\n * @param {event} e A JavaScript event\n * @param {object} anchorElement The anchor element that was triggered\n */\n handlePreviewContainerEvent(e, anchorElement) {\n const popupOptions = [\n 'height=600',\n 'width=800',\n 'top=0',\n 'left=0',\n 'menubar=0',\n 'location=0',\n 'scrollbars',\n 'resizable',\n 'toolbar',\n 'status',\n 'directories=0',\n 'fullscreen=0',\n 'dependent',\n ];\n window.openpopup(e, {\n url: anchorElement.attr('href'),\n name: 'questionpreview',\n options: popupOptions.join(',')\n });\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => {\n // If the user clicks on the \"Add selected questions to the quiz\" button to add some questions to the page\n // then we need to intercept the submit in order to include the correct \"add on page id\" before the form is\n // submitted.\n const formElement = $(e.currentTarget);\n\n $('').attr('type', 'hidden')\n .attr('name', \"addonpage\")\n .attr('value', this.getAddOnPageId())\n .appendTo(formElement);\n });\n\n this.getModal().on('click', SELECTORS.ANCHOR, (e) => {\n const anchorElement = $(e.currentTarget);\n\n // If the anchor element was the add to quiz link.\n if (anchorElement.closest(SELECTORS.ADD_TO_QUIZ_CONTAINER).length) {\n this.handleAddToQuizEvent(e, anchorElement);\n return;\n }\n\n // If the anchor element was a preview question link.\n if (anchorElement.closest(SELECTORS.PREVIEW_CONTAINER).length) {\n this.handlePreviewContainerEvent(e, anchorElement);\n return;\n }\n\n // Sorting links have their own handler.\n if (anchorElement.closest(SELECTORS.SORTERS).length) {\n return;\n }\n\n // Anything else means reload the pop-up contents.\n e.preventDefault();\n this.reloadBodyContent(anchorElement.prop('search'));\n });\n\n // Disable the form change checker when the body is rendered.\n this.getRoot().on(ModalEvents.bodyRendered, () => {\n // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the\n // page once the modal is hidden.\n FormChangeChecker.disableAllChecks();\n });\n }\n}\n\nModalQuizQuestionBank.registerModalType();\n"],"names":["SELECTORS","ModalQuizQuestionBank","Modal","constructor","root","contextId","addOnPageId","setContextId","id","getContextId","this","setAddOnPageId","getAddOnPageId","show","reloadBodyContent","window","location","search","super","querystring","setBody","Fragment","loadFragment","handleAddToQuizEvent","e","anchorElement","href","URL","attr","searchParams","set","handlePreviewContainerEvent","openpopup","url","name","options","join","registerEventListeners","getModal","on","formElement","currentTarget","appendTo","closest","length","preventDefault","prop","getRoot","ModalEvents","bodyRendered","FormChangeChecker","disableAllChecks","registerModalType"],"mappings":"2hDA6BMA,gCACqB,qBADrBA,iBAEM,UAFNA,4BAGiB,mBAHjBA,6BAIkB,sBAJlBA,kBAKO,iBAGQC,8BAA8BC,eAQ/CC,YAAYC,YACFA,WAEDC,UAAY,UACZC,YAAc,KAUvBC,aAAaC,SACJH,UAAYG,GASrBC,sBACWC,KAAKL,UAUhBM,eAAeH,SACNF,YAAcE,GASvBI,wBACWF,KAAKJ,YAahBO,mBACSC,kBAAkBC,OAAOC,SAASC,QAChCC,MAAML,KAAKH,MAatBI,kBAAkBK,kBAETC,QAAQC,SAASC,aAClB,WACA,qBACAZ,KAAKD,eACL,CACIU,YAAAA,eAaZI,qBAAqBC,EAAGC,qBAKdC,KAAO,IAAIC,IAAIF,cAAcG,KAAK,SACxCF,KAAKG,aAAaC,IAAI,YAAapB,KAAKE,kBACxCa,cAAcG,KAAK,OAAQF,MAU/BK,4BAA4BP,EAAGC,eAgB3BV,OAAOiB,UAAUR,EAAG,CAChBS,IAAKR,cAAcG,KAAK,QACxBM,KAAM,kBACNC,QAlBiB,CACjB,aACA,YACA,QACA,SACA,YACA,aACA,aACA,YACA,UACA,SACA,gBACA,eACA,aAKsBC,KAAK,OASnCC,+BAEUA,uBAAuB3B,WAExB4B,WAAWC,GAAG,SAAUvC,8BAA+BwB,UAIlDgB,aAAc,mBAAEhB,EAAEiB,mCAEtB,aAAab,KAAK,OAAQ,UACvBA,KAAK,OAAQ,aACbA,KAAK,QAASlB,KAAKE,kBACnB8B,SAASF,qBAGbF,WAAWC,GAAG,QAASvC,kBAAmBwB,UACrCC,eAAgB,mBAAED,EAAEiB,eAGtBhB,cAAckB,QAAQ3C,iCAAiC4C,YAClDrB,qBAAqBC,EAAGC,eAK7BA,cAAckB,QAAQ3C,6BAA6B4C,YAC9Cb,4BAA4BP,EAAGC,eAKpCA,cAAckB,QAAQ3C,mBAAmB4C,SAK7CpB,EAAEqB,sBACG/B,kBAAkBW,cAAcqB,KAAK,oBAIzCC,UAAUR,GAAGS,YAAYC,cAAc,KAGxCC,kBAAkBC,6FA/LZ,iDADGlD,6HAqMrBA,sBAAsBmD"} \ No newline at end of file diff --git a/mod/quiz/amd/build/update_random_question_filter_condition.min.js b/mod/quiz/amd/build/update_random_question_filter_condition.min.js new file mode 100644 index 00000000000..42e55788bd2 --- /dev/null +++ b/mod/quiz/amd/build/update_random_question_filter_condition.min.js @@ -0,0 +1,12 @@ +define("mod_quiz/update_random_question_filter_condition",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Event handling for the edit random question form. + * + * Dynamically saves the new filter condition before navigating back to the quiz question list. + * + * @module mod_quiz/update_random_question_filter_condition + * @author 2022 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.init=()=>{const SELECTORS_QUESTION_BANK_CONTAINER="#questionbank_container",SELECTORS_FORM_ELEMENT="#update_filter_condition_form",SELECTORS_UPDATE_BUTTON='[name="update"]',SELECTORS_MESSAGE_INPUT='[name="message"]',SELECTORS_FILTER_CONDITION_ELEMENT="[data-filtercondition]",questionBank=document.querySelector(SELECTORS_QUESTION_BANK_CONTAINER),form=document.querySelector(SELECTORS_FORM_ELEMENT);form.querySelector(SELECTORS_UPDATE_BUTTON).addEventListener("click",(async e=>{var _form$dataset,_form$dataset2,_questionBank$querySe;e.preventDefault();const request={methodname:"mod_quiz_update_filter_condition",args:{cmid:null===(_form$dataset=form.dataset)||void 0===_form$dataset?void 0:_form$dataset.cmid,slotid:null===(_form$dataset2=form.dataset)||void 0===_form$dataset2?void 0:_form$dataset2.slotid,filtercondition:null===(_questionBank$querySe=questionBank.querySelector(SELECTORS_FILTER_CONDITION_ELEMENT).dataset)||void 0===_questionBank$querySe?void 0:_questionBank$querySe.filtercondition}};try{const response=await _ajax.default.call([request])[0];form.querySelector(SELECTORS_MESSAGE_INPUT).value=response.message,form.submit()}catch(e){_notification.default.exception(e)}}))}})); + +//# sourceMappingURL=update_random_question_filter_condition.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/update_random_question_filter_condition.min.js.map b/mod/quiz/amd/build/update_random_question_filter_condition.min.js.map new file mode 100644 index 00000000000..822c02476fd --- /dev/null +++ b/mod/quiz/amd/build/update_random_question_filter_condition.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"update_random_question_filter_condition.min.js","sources":["../src/update_random_question_filter_condition.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 * Event handling for the edit random question form.\n *\n * Dynamically saves the new filter condition before navigating back to the quiz question list.\n *\n * @module mod_quiz/update_random_question_filter_condition\n * @author 2022 \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport const init = () => {\n const SELECTORS = {\n QUESTION_BANK_CONTAINER: '#questionbank_container',\n FORM_ELEMENT: '#update_filter_condition_form',\n UPDATE_BUTTON: '[name=\"update\"]',\n CANCEL_BUTTON: '[name=\"cancel\"]',\n MESSAGE_INPUT: '[name=\"message\"]',\n FILTER_CONDITION_ELEMENT: '[data-filtercondition]',\n };\n\n const questionBank = document.querySelector(SELECTORS.QUESTION_BANK_CONTAINER);\n const form = document.querySelector(SELECTORS.FORM_ELEMENT);\n const updateButton = form.querySelector(SELECTORS.UPDATE_BUTTON);\n\n updateButton.addEventListener(\"click\", async(e) => {\n e.preventDefault();\n const request = {\n methodname: 'mod_quiz_update_filter_condition',\n args: {\n cmid: form.dataset?.cmid,\n slotid: form.dataset?.slotid,\n filtercondition: questionBank.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition,\n }\n };\n try {\n const response = await ajax.call([request])[0];\n const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT);\n messageInput.value = response.message;\n form.submit();\n } catch (e) {\n Notification.exception(e);\n }\n });\n\n};\n"],"names":["SELECTORS","questionBank","document","querySelector","form","addEventListener","async","e","preventDefault","request","methodname","args","cmid","dataset","_form$dataset","slotid","_form$dataset2","filtercondition","_questionBank$querySe","response","ajax","call","value","message","submit","exception"],"mappings":";;;;;;;;;wLA4BoB,WACVA,kCACuB,0BADvBA,uBAEY,gCAFZA,wBAGa,kBAHbA,wBAKa,mBALbA,mCAMwB,yBAGxBC,aAAeC,SAASC,cAAcH,mCACtCI,KAAOF,SAASC,cAAcH,wBACfI,KAAKD,cAAcH,yBAE3BK,iBAAiB,SAASC,MAAAA,2DACnCC,EAAEC,uBACIC,QAAU,CACZC,WAAY,mCACZC,KAAM,CACFC,2BAAMR,KAAKS,wCAALC,cAAcF,KACpBG,8BAAQX,KAAKS,yCAALG,eAAcD,OACtBE,8CAAiBhB,aAAaE,cAAcH,oCAAoCa,gDAA/DK,sBAAwED,4BAIvFE,eAAiBC,cAAKC,KAAK,CAACZ,UAAU,GACvBL,KAAKD,cAAcH,yBAC3BsB,MAAQH,SAASI,QAC9BnB,KAAKoB,SACP,MAAOjB,yBACQkB,UAAUlB"} \ No newline at end of file diff --git a/mod/quiz/amd/src/modal_add_random_question.js b/mod/quiz/amd/src/modal_add_random_question.js index 7141de7b381..f1f7610aa41 100644 --- a/mod/quiz/amd/src/modal_add_random_question.js +++ b/mod/quiz/amd/src/modal_add_random_question.js @@ -27,18 +27,25 @@ import * as Notification from 'core/notification'; import * as Fragment from 'core/fragment'; import * as Templates from 'core/templates'; import * as FormChangeChecker from 'core_form/changechecker'; +import {call as fetchMany} from 'core/ajax'; const SELECTORS = { EXISTING_CATEGORY_CONTAINER: '[data-region="existing-category-container"]', - EXISTING_CATEGORY_FORM_ELEMENT: '#id_existingcategoryheader', + EXISTING_CATEGORY_TAB: '#id_existingcategoryheader', NEW_CATEGORY_CONTAINER: '[data-region="new-category-container"]', - NEW_CATEGORY_FORM_ELEMENT: '#id_newcategoryheader', + NEW_CATEGORY_TAB: '#id_newcategoryheader', TAB_CONTENT: '[data-region="tab-content"]', ADD_ON_PAGE_FORM_ELEMENT: '[name="addonpage"]', - SUBMIT_BUTTON_ELEMENT: 'input[type="submit"]', - CANCEL_BUTTON_ELEMENT: 'input[type="submit"][name="cancel"]', + ADD_RANDOM_BUTTON: 'input[type="submit"][name="addrandom"]', + ADD_NEW_CATEGORY_BUTTON: 'input[type="submit"][name="newcategory"]', + SUBMIT_BUTTON_ELEMENT: 'input[type="submit"][name="addrandom"], input[type="submit"][name="newcategory"]', FORM_HEADER: 'legend', - BUTTON_CONTAINER: '.fitem', + SELECT_NUMBER_TO_ADD: '#menurandomcount', + NEW_CATEGORY_ELEMENT: '#categoryname', + PARENT_CATEGORY_ELEMENT: '#parentcategory', + FILTER_CONDITION_ELEMENT: '[data-filtercondition]', + FORM_ELEMENT: '#add_random_question_form', + MESSAGE_INPUT: '[name="message"]', }; export default class ModalAddRandomQuestion extends Modal { @@ -173,24 +180,16 @@ export default class ModalAddRandomQuestion extends Modal { * Moves the submit button into a footer element at the bottom of the form * element for styling purposes. * - * @method moveFormElementIntoTab - * @param {jquery} formElement The form element to move into the tab. + * @method moveContentIntoTab + * @param {jquery} tabContent The form element to move into the tab. * @param {jquey} tabElement The tab element for the form element to move into. */ - moveFormElementIntoTab(formElement, tabElement) { - const submitButtons = formElement.find(SELECTORS.SUBMIT_BUTTON_ELEMENT); - const footer = $(''); + moveContentIntoTab(tabContent, tabElement) { // Hide the header because the tabs show us which part of the form we're // looking at. - formElement.find(SELECTORS.FORM_HEADER).addClass('hidden'); + tabContent.find(SELECTORS.FORM_HEADER).addClass('hidden'); // Move the element inside a tab. - formElement.wrap(tabElement); - // Remove the buttons container element. - submitButtons.closest(SELECTORS.BUTTON_CONTAINER).remove(); - // Put the button inside a footer. - submitButtons.appendTo(footer); - // Add the footer to the end of the category form element. - footer.appendTo(formElement); + tabContent.wrap(tabElement); } /** @@ -230,29 +229,33 @@ export default class ModalAddRandomQuestion extends Modal { * @return {promise} Resolved with form HTML and JS. */ loadForm() { + const cmid = this.getCMID(); + const cat = this.getCategory(); + const addonpage = this.getAddOnPageId(); + const returnurl = this.getReturnUrl(); + return Fragment.loadFragment( 'mod_quiz', 'add_random_question_form', this.getContextId(), { - addonpage: this.getAddOnPageId(), - cat: this.getCategory(), - returnurl: this.getReturnUrl(), - cmid: this.getCMID() + addonpage, + cat, + returnurl, + cmid, } ) .then((html, js) =>{ const form = $(html); - const existingCategoryFormElement = form.find(SELECTORS.EXISTING_CATEGORY_FORM_ELEMENT); + const existingCategoryTabContent = form.find(SELECTORS.EXISTING_CATEGORY_TAB); const existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER); - const newCategoryFormElement = form.find(SELECTORS.NEW_CATEGORY_FORM_ELEMENT); + const newCategoryTabContent = form.find(SELECTORS.NEW_CATEGORY_TAB); const newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER); // Transform the form into tabs for better rendering in the modal. - this.moveFormElementIntoTab(existingCategoryFormElement, existingCategoryTab); - this.moveFormElementIntoTab(newCategoryFormElement, newCategoryTab); + this.moveContentIntoTab(existingCategoryTabContent, existingCategoryTab); + this.moveContentIntoTab(newCategoryTabContent, newCategoryTab); this.moveTabsIntoTabContent(form); - this.moveCancelButtonToTabs(form); Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js); return; @@ -261,11 +264,82 @@ export default class ModalAddRandomQuestion extends Modal { // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the // page once the modal is hidden. FormChangeChecker.disableAllChecks(); - return; + + // Add question to quiz. + this.getBody()[0].addEventListener('click', (e) => { + const button = e.target.closest(SELECTORS.SUBMIT_BUTTON_ELEMENT); + if (!button) { + return; + } + e.preventDefault(); + + const randomcount = document.querySelector(SELECTORS.SELECT_NUMBER_TO_ADD).value; + const filtercondition = document.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition; + + // Add Random questions if the add random button was clicked. + const addRandomButton = e.target.closest(SELECTORS.ADD_RANDOM_BUTTON); + if (addRandomButton) { + this.addQuestions(cmid, addonpage, randomcount, filtercondition, '', ''); + return; + } + // Add new category if the add category button was clicked. + const addCategoryButton = e.target.closest(SELECTORS.ADD_NEW_CATEGORY_BUTTON); + if (addCategoryButton) { + this.addQuestions( + cmid, + addonpage, + randomcount, + filtercondition, + document.querySelector(SELECTORS.NEW_CATEGORY_ELEMENT).value, + document.querySelector(SELECTORS.PARENT_CATEGORY_ELEMENT).value + ); + return; + } + }); }) .catch(Notification.exception); } + /** + * Call web service function to add random questions + * + * @param {number} cmid course module id + * @param {number} addonpage the page where random questions will be added to + * @param {number} randomcount Number of random questions + * @param {string} filtercondition Filter condition + * @param {string} newcategory add new category + * @param {string} parentcategory parent category of new category + */ + async addQuestions( + cmid, + addonpage, + randomcount, + filtercondition, + newcategory, + parentcategory + ) { + const call = { + methodname: 'mod_quiz_add_random_questions', + args: { + cmid, + addonpage, + randomcount, + filtercondition, + newcategory, + parentcategory, + } + }; + try { + const response = await fetchMany([call])[0]; + const form = document.querySelector(SELECTORS.FORM_ELEMENT); + const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT); + messageInput.value = response.message; + form.submit(); + } catch (e) { + Notification.exception(e); + } + } + /** * Override the modal show function to load the form when this modal is first * shown. @@ -276,7 +350,7 @@ export default class ModalAddRandomQuestion extends Modal { super.show(this); if (!this.loadedForm) { - this.loadForm(); + this.loadForm(window.location.search); this.loadedForm = true; } } diff --git a/mod/quiz/amd/src/modal_quiz_question_bank.js b/mod/quiz/amd/src/modal_quiz_question_bank.js index 3add9296122..5ebd4661d42 100644 --- a/mod/quiz/amd/src/modal_quiz_question_bank.js +++ b/mod/quiz/amd/src/modal_quiz_question_bank.js @@ -31,9 +31,8 @@ const SELECTORS = { ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction', ANCHOR: 'a[href]', PREVIEW_CONTAINER: 'td.previewaction', - SEARCH_OPTIONS: '#advancedsearch', - DISPLAY_OPTIONS: '#displayoptions', ADD_QUESTIONS_FORM: 'form#questionsubmit', + SORTERS: '.sorters', }; export default class ModalQuizQuestionBank extends Modal { @@ -178,59 +177,6 @@ export default class ModalQuizQuestionBank extends Modal { }); } - /** - * Reload the modal body with the new display options the user has selected. - * - * A query string is built using the form elements to be used to generate the - * new body content. - * - * @method handleDisplayOptionFormEvent - * @param {event} e A JavaScript event - */ - handleDisplayOptionFormEvent(e) { - // Stop propagation to prevent other wild event handlers - // from submitting the form on change. - e.stopPropagation(); - e.preventDefault(); - - const form = $(e.target).closest(SELECTORS.DISPLAY_OPTIONS); - const queryString = '?' + form.serialize(); - this.reloadBodyContent(queryString); - } - - /** - * Listen for changes to the display options form. - * - * This handles the user changing: - * - The quiz category select box - * - The tags to filter by - * - Show/hide questions from sub categories - * - Show/hide old questions - * - * @method registerDisplayOptionListeners - */ - registerDisplayOptionListeners() { - // Listen for changes to the display options form. - this.getModal().on('change', SELECTORS.DISPLAY_OPTIONS, (e) => { - // Get the element that was changed. - const modifiedElement = $(e.target); - if (modifiedElement.attr('aria-autocomplete')) { - // If the element that was change is the autocomplete - // input then we should ignore it because that is for - // display purposes only. - return; - } - - this.handleDisplayOptionFormEvent(e); - }); - - // Listen for the display options form submission because the tags - // filter will submit the form when it is changed. - this.getModal().on('submit', SELECTORS.DISPLAY_OPTIONS, (e) => { - this.handleDisplayOptionFormEvent(e); - }); - } - /** * Set up all of the event handling for the modal. * @@ -240,9 +186,6 @@ export default class ModalQuizQuestionBank extends Modal { // Apply parent event listeners. super.registerEventListeners(this); - // Set up the event handlers for all of the display options. - this.registerDisplayOptionListeners(); - this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => { // If the user clicks on the "Add selected questions to the quiz" button to add some questions to the page // then we need to intercept the submit in order to include the correct "add on page id" before the form is @@ -270,9 +213,8 @@ export default class ModalQuizQuestionBank extends Modal { return; } - // Click on expand/collaspse search-options. Has its own handler. - // We should not interfere. - if (anchorElement.closest(SELECTORS.SEARCH_OPTIONS).length) { + // Sorting links have their own handler. + if (anchorElement.closest(SELECTORS.SORTERS).length) { return; } diff --git a/mod/quiz/amd/src/update_random_question_filter_condition.js b/mod/quiz/amd/src/update_random_question_filter_condition.js new file mode 100644 index 00000000000..eeea510f22c --- /dev/null +++ b/mod/quiz/amd/src/update_random_question_filter_condition.js @@ -0,0 +1,63 @@ +// 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 . + +/** + * Event handling for the edit random question form. + * + * Dynamically saves the new filter condition before navigating back to the quiz question list. + * + * @module mod_quiz/update_random_question_filter_condition + * @author 2022 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import ajax from 'core/ajax'; +import Notification from 'core/notification'; + +export const init = () => { + const SELECTORS = { + QUESTION_BANK_CONTAINER: '#questionbank_container', + FORM_ELEMENT: '#update_filter_condition_form', + UPDATE_BUTTON: '[name="update"]', + CANCEL_BUTTON: '[name="cancel"]', + MESSAGE_INPUT: '[name="message"]', + FILTER_CONDITION_ELEMENT: '[data-filtercondition]', + }; + + const questionBank = document.querySelector(SELECTORS.QUESTION_BANK_CONTAINER); + const form = document.querySelector(SELECTORS.FORM_ELEMENT); + const updateButton = form.querySelector(SELECTORS.UPDATE_BUTTON); + + updateButton.addEventListener("click", async(e) => { + e.preventDefault(); + const request = { + methodname: 'mod_quiz_update_filter_condition', + args: { + cmid: form.dataset?.cmid, + slotid: form.dataset?.slotid, + filtercondition: questionBank.querySelector(SELECTORS.FILTER_CONDITION_ELEMENT).dataset?.filtercondition, + } + }; + try { + const response = await ajax.call([request])[0]; + const messageInput = form.querySelector(SELECTORS.MESSAGE_INPUT); + messageInput.value = response.message; + form.submit(); + } catch (e) { + Notification.exception(e); + } + }); + +}; diff --git a/mod/quiz/classes/external/add_random_questions.php b/mod/quiz/classes/external/add_random_questions.php new file mode 100644 index 00000000000..88a62e30520 --- /dev/null +++ b/mod/quiz/classes/external/add_random_questions.php @@ -0,0 +1,158 @@ +. + +namespace mod_quiz\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->dirroot . '/question/editlib.php'); +require_once($CFG->dirroot . '/mod/quiz/locallib.php'); + +use external_function_parameters; +use external_single_structure; +use external_value; +use external_api; +use mod_quiz\question\bank\filter\custom_category_condition; +use mod_quiz\quiz_settings; +use mod_quiz\structure; + +/** + * Add random questions to a quiz. + * + * @package mod_quiz + * @copyright 2022 Catalyst IT Australia Pty Ltd + * @author Nathan Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class add_random_questions extends external_api { + + /** + * Parameters for the web service function + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters ( + [ + 'cmid' => new external_value(PARAM_INT, 'The cmid of the quiz'), + 'addonpage' => new external_value(PARAM_INT, 'The page where random questions will be added to'), + 'randomcount' => new external_value(PARAM_INT, 'Number of random questions'), + 'filtercondition' => new external_value(PARAM_TEXT, 'Filter condition'), + 'newcategory' => new external_value( + PARAM_TEXT, + '(Optional) The name of a new question category to create and use for the random questions.', + VALUE_DEFAULT, + '', + ), + 'parentcategory' => new external_value( + PARAM_TEXT, + '(Optional) The parent of the new question category, if creating one.', + VALUE_DEFAULT, + 0, + ), + ] + ); + } + + /** + * Add random questions. + * + * @param int $cmid The cmid of the quiz + * @param int $addonpage The page where random questions will be added to + * @param int $randomcount Number of random questions + * @param string $filtercondition Filter condition + * @param string $newcategory add new category + * @param string $parentcategory parent category of new category + * @return array result + */ + public static function execute( + int $cmid, + int $addonpage, + int $randomcount, + string $filtercondition, + string $newcategory = '', + string $parentcategory = '', + ): array { + [ + 'cmid' => $cmid, + 'addonpage' => $addonpage, + 'randomcount' => $randomcount, + 'filtercondition' => $filtercondition, + 'newcategory' => $newcategory, + 'parentcategory' => $parentcategory, + ] = self::validate_parameters(self::execute_parameters(), [ + 'cmid' => $cmid, + 'addonpage' => $addonpage, + 'randomcount' => $randomcount, + 'filtercondition' => $filtercondition, + 'newcategory' => $newcategory, + 'parentcategory' => $parentcategory, + ]); + + // Validate context. + $thiscontext = \context_module::instance($cmid); + self::validate_context($thiscontext); + require_capability('mod/quiz:manage', $thiscontext); + + $filtercondition = json_decode($filtercondition, true); + + // Create new category. + if (!empty($newcategory)) { + $contexts = new \core_question\local\bank\question_edit_contexts($thiscontext); + $defaultcategoryobj = question_make_default_categories($contexts->all()); + $defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid; + $qcobject = new \qbank_managecategories\question_category_object( + null, + null, + $contexts->having_one_edit_tab_cap('categories'), + $defaultcategoryobj->id, + $defaultcategory, + null, + $contexts->having_cap('moodle/question:add')); + $categoryid = $qcobject->add_category($parentcategory, $newcategory, '', true); + $filter = [ + 'category' => [ + 'jointype' => custom_category_condition::JOINTYPE_DEFAULT, + 'values' => [$categoryid], + 'filteroptions' => ['includesubcategories' => false], + ] + ]; + $filtercondition['filter'] = $filter; + } + + // Add random question to the quiz. + [$quiz, ] = get_module_from_cmid($cmid); + $settings = quiz_settings::create_for_cmid($cmid); + $structure = structure::create_for_quiz($settings); + $structure->add_random_questions($addonpage, $randomcount, $filtercondition); + quiz_delete_previews($quiz); + quiz_settings::create($quiz->id)->get_grade_calculator()->recompute_quiz_sumgrades(); + + return ['message' => get_string('addarandomquestion_success', 'mod_quiz')]; + } + + /** + * Returns description of method result value. + * + * @return external_value + */ + public static function execute_returns() { + return new external_single_structure([ + 'message' => new external_value(PARAM_TEXT, 'Message', VALUE_OPTIONAL) + ]); + } +} diff --git a/mod/quiz/classes/external/update_filter_condition.php b/mod/quiz/classes/external/update_filter_condition.php new file mode 100644 index 00000000000..70d2c5c94bf --- /dev/null +++ b/mod/quiz/classes/external/update_filter_condition.php @@ -0,0 +1,104 @@ +. + +namespace mod_quiz\external; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/externallib.php'); +require_once($CFG->dirroot . '/question/editlib.php'); +require_once($CFG->dirroot . '/mod/quiz/locallib.php'); + +use external_function_parameters; +use external_single_structure; +use external_value; +use external_api; + +/** + * Update the filter condition for a random question. + * + * @package mod_quiz + * @copyright 2022 Catalyst IT Australia Pty Ltd + * @author Nathan Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class update_filter_condition extends external_api { + + /** + * Parameters for the web service function + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters ([ + 'cmid' => new external_value(PARAM_INT, 'The cmid of the quiz'), + 'slotid' => new external_value(PARAM_INT, 'The quiz slot ID for the random question.'), + 'filtercondition' => new external_value(PARAM_TEXT, 'Filter condition'), + ]); + } + + /** + * Add random questions. + * + * @param int $cmid course module id + * @param int $slotid The quiz slot id + * @param string $filtercondition + * @return array result + */ + public static function execute( + int $cmid, + int $slotid, + string $filtercondition, + ): array { + global $DB; + + [ + 'cmid' => $cmid, + 'slotid' => $slotid, + 'filtercondition' => $filtercondition, + ] = self::validate_parameters(self::execute_parameters(), [ + 'cmid' => $cmid, + 'slotid' => $slotid, + 'filtercondition' => $filtercondition, + ]); + + // Validate context. + $thiscontext = \context_module::instance($cmid); + self::validate_context($thiscontext); + require_capability('mod/quiz:manage', $thiscontext); + + // Update filter condition. + $setparams = [ + 'itemid' => $slotid, + 'questionarea' => 'slot', + 'component' => 'mod_quiz', + ]; + $DB->set_field('question_set_references', 'filtercondition', $filtercondition, $setparams); + + return ['message' => get_string('updatefilterconditon_success', 'mod_quiz')]; + } + + /** + * Returns description of method result value. + * + * @return external_value + */ + public static function execute_returns() { + return new external_single_structure([ + 'message' => new external_value(PARAM_TEXT, 'Message', VALUE_OPTIONAL) + ]); + } +} diff --git a/mod/quiz/classes/form/add_random_form.php b/mod/quiz/classes/form/add_random_form.php index d90d2495602..e9336ea117e 100644 --- a/mod/quiz/classes/form/add_random_form.php +++ b/mod/quiz/classes/form/add_random_form.php @@ -16,6 +16,7 @@ namespace mod_quiz\form; +use core\check\performance\debugging; use core_tag_tag; use moodleform; @@ -30,10 +31,23 @@ require_once($CFG->libdir.'/formslib.php'); * @package mod_quiz * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated Moodle 4.3 MDL-72321. This form is new generated in a modal with mod_quiz/add_random_question_form.mustache + * @todo Final deprecation in Moodle 4.7 MDL-78091 */ class add_random_form extends moodleform { + /** + * Deprecated. + * + * @return void + * @deprecated Moodle 4.3 MDL-72321 + * @todo Final deprecation in Moodle 4.7 MDL-78091 + */ protected function definition() { + debugging( + 'add_random_form is deprecated. Please use mod_quiz/add_random_question_form.mustache instead.', + DEBUG_DEVELOPER + ); global $OUTPUT, $PAGE, $CFG; $mform = $this->_form; @@ -119,7 +133,20 @@ class add_random_form extends moodleform { ]); } + /** + * Deprecated. + * + * @param array $fromform + * @param array $files + * @return array + * @deprecated Moodle 4.3 MDL-72321 + * @todo Final deprecation in Moodle 4.7 MDL-78091 + */ public function validation($fromform, $files) { + debugging( + 'add_random_form is deprecated. Please use mod_quiz/add_random_question_form.mustache instead.', + DEBUG_DEVELOPER + ); $errors = parent::validation($fromform, $files); if (!empty($fromform['newcategory']) && trim($fromform['name']) == '') { diff --git a/mod/quiz/classes/local/structure/slot_random.php b/mod/quiz/classes/local/structure/slot_random.php index f2e116e63a6..a743f6eaaff 100644 --- a/mod/quiz/classes/local/structure/slot_random.php +++ b/mod/quiz/classes/local/structure/slot_random.php @@ -112,8 +112,13 @@ class slot_random { * Set some tags for this quiz slot. * * @param \core_tag_tag[] $tags + * + * @deprecated since Moodle 4.3 + * @todo Final deprecation on Moodle 4.7 MDL-78091 */ public function set_tags($tags) { + debugging('Method set_tags() is deprecated, ' . + 'please do not use this function.', DEBUG_DEVELOPER); $this->tags = []; foreach ($tags as $tag) { // We use $tag->id as the key for the array so not only it handles duplicates of the same tag being given, @@ -126,22 +131,24 @@ class slot_random { * Set some tags for this quiz slot. This function uses tag ids to find tags. * * @param int[] $tagids + * @deprecated since Moodle 4.3 + * @todo Final deprecation on Moodle 4.7 MDL-78091 */ public function set_tags_by_id($tagids) { + debugging( + 'Method set_tags_by_id() is deprecated, please do not use this function.', + DEBUG_DEVELOPER + ); $this->tags = \core_tag_tag::get_bulk($tagids, 'id, name'); } /** * Set filter condition. * - * @param \stdClass $filters + * @param \string $filters */ - public function set_filter_condition($filters) { - if (!empty($this->tags)) { - $filters->tags = $this->tags; - } - - $this->filtercondition = json_encode($filters); + public function set_filter_condition(string $filters): void { + $this->filtercondition = $filters; } /** diff --git a/mod/quiz/classes/output/edit_renderer.php b/mod/quiz/classes/output/edit_renderer.php index cc96d41c9da..498813f73b6 100644 --- a/mod/quiz/classes/output/edit_renderer.php +++ b/mod/quiz/classes/output/edit_renderer.php @@ -680,7 +680,7 @@ class edit_renderer extends \plugin_renderer_base { if ($structure->can_add_random_questions()) { $returnurl = new \moodle_url('/mod/quiz/edit.php', ['cmid' => $structure->get_cmid(), 'data-addonpage' => $page]); $params = ['returnurl' => $returnurl, 'cmid' => $structure->get_cmid(), 'appendqnumstring' => 'addarandomquestion']; - $url = new \moodle_url('/mod/quiz/addrandom.php', $params); + $url = new \moodle_url('/mod/quiz/edit.php', $params); $icon = new \pix_icon('t/add', $str->addarandomquestion, 'moodle', ['class' => 'iconsmall', 'title' => '']); $attributes = ['class' => 'cm-edit-action addarandomquestion', 'data-action' => 'addarandomquestion']; if ($page) { @@ -1079,7 +1079,6 @@ class edit_renderer extends \plugin_renderer_base { $qbankurlparams = [ 'cmid' => $structure->get_cmid(), 'cat' => $slot->category . ',' . $slot->contextid, - 'recurse' => $slot->randomrecurse, ]; $slottags = []; diff --git a/mod/quiz/classes/question/bank/custom_view.php b/mod/quiz/classes/question/bank/custom_view.php index 63f8e4ac3a3..694f3bdab7b 100644 --- a/mod/quiz/classes/question/bank/custom_view.php +++ b/mod/quiz/classes/question/bank/custom_view.php @@ -25,9 +25,15 @@ namespace mod_quiz\question\bank; +defined('MOODLE_INTERNAL') || die(); + +use core\output\datafilter; +use core_question\local\bank\condition; use core_question\local\bank\question_version_status; use mod_quiz\question\bank\filter\custom_category_condition; +use qbank_managecategories\category_condition; +require_once($CFG->dirroot . '/mod/quiz/locallib.php'); /** * Subclass to customise the view of the question bank for the quiz editing screen. * @@ -45,8 +51,10 @@ class custom_view extends \core_question\local\bank\view { /** @var \stdClass $quiz the quiz settings. */ protected $quiz = false; - /** @var int The maximum displayed length of the category info. */ - const MAX_TEXT_LENGTH = 200; + /** + * @var string $component the component the api is used from. + */ + public $component = 'mod_quiz'; /** * Constructor. @@ -56,27 +64,55 @@ class custom_view extends \core_question\local\bank\view { * @param \stdClass $cm activity settings. * @param \stdClass $quiz quiz settings. */ - public function __construct($contexts, $pageurl, $course, $cm, $quiz) { - parent::__construct($contexts, $pageurl, $course, $cm); - $this->quiz = $quiz; + public function __construct($contexts, $pageurl, $course, $cm, $params, $extraparams) { + // Default filter condition. + if (!isset($params['filter'])) { + $params['filter'] = []; + [$categoryid, $contextid] = custom_category_condition::validate_category_param($params['cat']); + if (!is_null($categoryid)) { + $category = custom_category_condition::get_category_record($categoryid, $contextid); + $params['filter']['category'] = [ + 'jointype' => custom_category_condition::JOINTYPE_DEFAULT, + 'values' => [$category->id], + 'filteroptions' => ['includesubcategories' => false], + ]; + } + } + + $this->init_required_columns(); + parent::__construct($contexts, $pageurl, $course, $cm, $params, $extraparams); + [$this->quiz, ] = get_module_from_cmid($cm->id); + $this->set_quiz_has_attempts(quiz_has_attempts($this->quiz->id)); $this->pagesize = self::DEFAULT_PAGE_SIZE; } - protected function get_question_bank_plugins(): array { - $questionbankclasscolumns = []; - $corequestionbankcolumns = [ + /** + * Init required columns. + * + * @return void + */ + protected function init_required_columns(): void { + // Override core columns. + $this->corequestionbankcolumns = [ 'add_action_column', 'checkbox_column', 'question_type_column', 'question_name_text_column', 'preview_action_column' ]; + } + /** + * Get class for each question bank columns. + * + * @return array + */ + protected function get_class_for_columns(): array { + $questionbankclasscolumns = []; if (question_get_display_preference('qbshowtext', 0, PARAM_INT, new \moodle_url(''))) { - $corequestionbankcolumns[] = 'question_text_row'; + $this->corequestionbankcolumns[] = 'question_text_row'; } - - foreach ($corequestionbankcolumns as $fullname) { + foreach ($this->corequestionbankcolumns as $fullname) { $shortname = $fullname; if (class_exists('mod_quiz\\question\\bank\\' . $fullname)) { $fullname = 'mod_quiz\\question\\bank\\' . $fullname; @@ -88,40 +124,18 @@ class custom_view extends \core_question\local\bank\view { $questionbankclasscolumns[$shortname] = ''; } } - $plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php'); - foreach ($plugins as $componentname => $plugin) { - $pluginentrypointobject = new $plugin(); - $plugincolumnobjects = $pluginentrypointobject->get_question_columns($this); - // Don't need the plugins without column objects. - if (empty($plugincolumnobjects)) { - unset($plugins[$componentname]); - continue; - } - foreach ($plugincolumnobjects as $columnobject) { - $columnname = $columnobject->get_column_name(); - foreach ($corequestionbankcolumns as $key => $corequestionbankcolumn) { - if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) { - unset($questionbankclasscolumns[$columnname]); - continue; - } - // Check if it has custom preference selector to view/hide. - if ($columnobject->has_preference() && !$columnobject->get_preference()) { - continue; - } - if ($corequestionbankcolumn === $columnname) { - $questionbankclasscolumns[$columnname] = $columnobject; - } - } - } - } - - // Mitigate the error in case of any regression. - foreach ($questionbankclasscolumns as $shortname => $questionbankclasscolumn) { - if (empty($questionbankclasscolumn)) { - unset($questionbankclasscolumns[$shortname]); - } - } + return $questionbankclasscolumns; + } + /** + * Get the list of qbank plugins with available objects for features. + * + * @return array + */ + protected function get_question_bank_plugins(): array { + // The view is too crowded, exclude some columns. + $questionbankclasscolumns = parent::get_question_bank_plugins(); + $questionbankclasscolumns = array_intersect_key($questionbankclasscolumns, array_flip($this->corequestionbankcolumns)); return $questionbankclasscolumns; } @@ -132,8 +146,8 @@ class custom_view extends \core_question\local\bank\view { protected function default_sort(): array { // Using the extended class for quiz specific sort. return [ - 'qbank_viewquestiontype\\question_type_column' => 1, - 'mod_quiz\\question\\bank\\question_name_text_column' => 1, + 'qbank_viewquestiontype__question_type_column' => SORT_ASC, + 'mod_quiz__question__bank__question_name_text_column' => SORT_ASC, ]; } @@ -144,23 +158,13 @@ class custom_view extends \core_question\local\bank\view { * * @param bool $quizhasattempts whether the quiz has attempts. */ - public function set_quiz_has_attempts($quizhasattempts): void { + private function set_quiz_has_attempts($quizhasattempts): void { $this->quizhasattempts = $quizhasattempts; if ($quizhasattempts && isset($this->visiblecolumns['addtoquizaction'])) { unset($this->visiblecolumns['addtoquizaction']); } } - /** - * Question preview url. - * - * @param \stdClass $question - * @return \moodle_url - */ - public function preview_question_url($question) { - return quiz_question_preview_url($this->quiz, $question); - } - /** * URL of add to quiz. * @@ -171,6 +175,7 @@ class custom_view extends \core_question\local\bank\view { $params = $this->baseurl->params(); $params['addquestion'] = $questionid; $params['sesskey'] = sesskey(); + $params['cmid'] = $this->cm->id; return new \moodle_url('/mod/quiz/edit.php', $params); } @@ -186,7 +191,7 @@ class custom_view extends \core_question\local\bank\view { */ public function render($pagevars, $tabname): string { ob_start(); - $this->display($pagevars, $tabname); + $this->display(); $out = ob_get_contents(); ob_end_clean(); return $out; @@ -216,8 +221,15 @@ class custom_view extends \core_question\local\bank\view { echo \html_writer::end_tag('div'); } + /** + * Override the base implementation in \core_question\local\bank\view + * because we don't want to print new question form in the fragment + * for the modal. + * + * @param false|mixed|\stdClass $category + * @param bool $canadd + */ protected function create_new_question_form($category, $canadd): void { - // Don't display this. } /** @@ -228,36 +240,6 @@ class custom_view extends \core_question\local\bank\view { protected function display_question_bank_header(): void { } - /** - * Override the base implementation in \core_question\bank\view - * because we don't want it to read from the $_POST global variables - * for the sort parameters since they are not present in a fragment. - * - * Unfortunately the best we can do is to look at the URL for - * those parameters (only marginally better really). - */ - protected function init_sort_from_params(): void { - $this->sort = []; - for ($i = 1; $i <= self::MAX_SORTS; $i++) { - if (!$sort = $this->baseurl->param('qbs' . $i)) { - break; - } - // Work out the appropriate order. - $order = 1; - if ($sort[0] == '-') { - $order = -1; - $sort = substr($sort, 1); - if (!$sort) { - break; - } - } - // Deal with subsorts. - list($colname) = $this->parse_subsort($sort); - $this->get_column_type($colname); - $this->sort[$sort] = $order; - } - } - protected function build_query(): void { // Get the required tables and fields. [$fields, $joins] = $this->get_component_requirements(array_merge($this->requiredcolumns, $this->questionactions)); @@ -265,7 +247,7 @@ class custom_view extends \core_question\local\bank\view { // Build the order by clause. $sorts = []; foreach ($this->sort as $sortname => $sortorder) { - list($colname, $subsort) = $this->parse_subsort($sortname); + [$colname, $subsort] = $this->parse_subsort($sortname); $sorts[] = $this->requiredcolumns[$colname]->sort_expression($sortorder == SORT_DESC, $subsort); } @@ -275,50 +257,50 @@ class custom_view extends \core_question\local\bank\view { JOIN {question_bank_entries} be ON be.id = v.questionbankentryid WHERE be.id = qbe.id)'; - $readyonly = "qv.status = '" . question_version_status::QUESTION_STATUS_READY . "' "; - $tests = ['q.parent = 0', $latestversion, $readyonly]; + $onlyready = '((' . "qv.status = '" . question_version_status::QUESTION_STATUS_READY . "'" .'))'; $this->sqlparams = []; + $conditions = []; foreach ($this->searchconditions as $searchcondition) { if ($searchcondition->where()) { - $tests[] = '((' . $searchcondition->where() .'))'; + $conditions[] = '((' . $searchcondition->where() .'))'; } if ($searchcondition->params()) { $this->sqlparams = array_merge($this->sqlparams, $searchcondition->params()); } } + $majorconditions = ['q.parent = 0', $latestversion, $onlyready]; + // Get higher level filter condition. + $jointype = isset($this->pagevars['jointype']) ? (int)$this->pagevars['jointype'] : condition::JOINTYPE_DEFAULT; + $nonecondition = ($jointype === datafilter::JOINTYPE_NONE) ? ' NOT ' : ''; + $separator = ($jointype === datafilter::JOINTYPE_ALL) ? ' AND ' : ' OR '; // Build the SQL. $sql = ' FROM {question} q ' . implode(' ', $joins); - $sql .= ' WHERE ' . implode(' AND ', $tests); + $sql .= ' WHERE ' . implode(' AND ', $majorconditions); + if (!empty($conditions)) { + $sql .= ' AND ' . $nonecondition . ' ( '; + $sql .= implode($separator, $conditions); + $sql .= ' ) '; + } $this->countsql = 'SELECT count(1)' . $sql; $this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts); } - public function wanted_filters($cat, $tagids, $showhidden, $recurse, $editcontexts, $showquestiontext): void { - global $CFG; - list(, $contextid) = explode(',', $cat); - $catcontext = \context::instance_by_id($contextid); - $thiscontext = $this->get_most_specific_context(); - // Category selection form. - $this->display_question_bank_header(); - - // Display tag filter if usetags setting is enabled/enablefilters is true. - if ($this->enablefilters) { - if (is_array($this->customfilterobjects)) { - foreach ($this->customfilterobjects as $filterobjects) { - $this->searchconditions[] = $filterobjects; + public function add_standard_search_conditions(): void { + foreach ($this->plugins as $componentname => $plugin) { + if (\core\plugininfo\qbank::is_plugin_enabled($componentname)) { + $pluginentrypointobject = new $plugin(); + if ($componentname === 'qbank_managecategories') { + $pluginentrypointobject = new quiz_managecategories_feature(); } - } else { - if ($CFG->usetags) { - array_unshift($this->searchconditions, - new \core_question\bank\search\tag_condition([$catcontext, $thiscontext], $tagids)); + if ($componentname === 'qbank_viewquestiontext' || $componentname === 'qbank_deletequestion') { + continue; + } + $pluginobjects = $pluginentrypointobject->get_question_filters($this); + foreach ($pluginobjects as $pluginobject) { + $this->add_searchcondition($pluginobject, $pluginobject->get_condition_key()); } - - array_unshift($this->searchconditions, new \core_question\bank\search\hidden_condition(!$showhidden)); - array_unshift($this->searchconditions, new custom_category_condition( - $cat, $recurse, $editcontexts, $this->baseurl, $this->course)); } } - $this->display_options_form($showquestiontext); } /** diff --git a/mod/quiz/classes/question/bank/filter/custom_category_condition.php b/mod/quiz/classes/question/bank/filter/custom_category_condition.php index b3544bbe52d..ba3a5144378 100644 --- a/mod/quiz/classes/question/bank/filter/custom_category_condition.php +++ b/mod/quiz/classes/question/bank/filter/custom_category_condition.php @@ -16,6 +16,8 @@ namespace mod_quiz\question\bank\filter; +use qbank_managecategories\helper; + /** * A custom filter condition for quiz to select question categories. * @@ -27,19 +29,23 @@ namespace mod_quiz\question\bank\filter; * @author Safat Shahin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class custom_category_condition extends \core_question\bank\search\category_condition { +class custom_category_condition extends \qbank_managecategories\category_condition { - public function display_options() { - global $PAGE; - $displaydata = []; - $catmenu = custom_category_condition_helper::question_category_options($this->contexts, true, 0, - true, -1, false); - $displaydata['categoryselect'] = \html_writer::select($catmenu, 'category', $this->cat, [], - ['class' => 'searchoptions custom-select', 'id' => 'id_selectacategory']); - $displaydata['categorydesc'] = ''; - if ($this->category) { - $displaydata['categorydesc'] = $this->print_category_info($this->category); + public function get_initial_values() { + $catmenu = custom_category_condition_helper::question_category_options($this->contexts, true, 0, true, -1, false); + $values = []; + foreach ($catmenu as $menu) { + foreach ($menu as $catlist) { + foreach ($catlist as $key => $value) { + $values[] = (object) [ + // Remove contextid from value. + 'value' => strpos($key, ',') === false ? $key : substr($key, 0, strpos($key, ',')), + 'title' => $value, + 'selected' => ($key === $this->cat), + ]; + } + } } - return $PAGE->get_renderer('core_question', 'bank')->render_category_condition($displaydata); + return $values; } } diff --git a/mod/quiz/classes/question/bank/qbank_helper.php b/mod/quiz/classes/question/bank/qbank_helper.php index feef74f3660..2f9c05e1847 100644 --- a/mod/quiz/classes/question/bank/qbank_helper.php +++ b/mod/quiz/classes/question/bank/qbank_helper.php @@ -18,6 +18,8 @@ namespace mod_quiz\question\bank; use core_question\local\bank\question_version_status; use core_question\local\bank\random_question_loader; +use core_question\question_reference_manager; +use qbank_tagquestion\tag_condition; use qubaid_condition; defined('MOODLE_INTERNAL') || die(); @@ -110,6 +112,7 @@ class qbank_helper { slot.displaynumber, slot.requireprevious, qsr.filtercondition, + qsr.usingcontextid, qv.status, qv.id AS versionid, qv.version, @@ -172,11 +175,12 @@ class qbank_helper { if ($slot->filtercondition) { // Unpack the information about a random question. - $filtercondition = json_decode($slot->filtercondition); $slot->questionid = 's' . $slot->id; // Sometimes this is used as an array key, so needs to be unique. - $slot->category = $filtercondition->questioncategoryid; - $slot->randomrecurse = (bool) $filtercondition->includingsubcategories; - $slot->randomtags = isset($filtercondition->tags) ? (array) $filtercondition->tags : []; + $filter = json_decode($slot->filtercondition, true); + $slot->filtercondition = question_reference_manager::convert_legacy_set_reference_filter_condition($filter); + + $slot->category = $slot->filtercondition['filter']['category']['values'][0] ?? 0; + $slot->qtype = 'random'; $slot->name = get_string('random', 'quiz'); $slot->length = 1; @@ -208,9 +212,12 @@ class qbank_helper { */ public static function get_tag_ids_for_slot(\stdClass $slotdata): array { $tagids = []; - foreach ($slotdata->randomtags as $taginfo) { - [$id] = explode(',', $taginfo, 2); - $tagids[] = $id; + if (!isset($slotdata->filtercondition['filter'])) { + return $tagids; + } + $filter = $slotdata->filtercondition['filter']; + if (isset($filter['qtagids'])) { + $tagids = $filter['qtagids']['values']; } return $tagids; } @@ -222,10 +229,19 @@ class qbank_helper { * @return string that can be used to display the random slot. */ public static function describe_random_question(\stdClass $slotdata): string { - global $DB; - $category = $DB->get_record('question_categories', ['id' => $slotdata->category]); - return \question_bank::get_qtype('random')->question_name( - $category, $slotdata->randomrecurse, $slotdata->randomtags); + $qtagids = self::get_tag_ids_for_slot($slotdata); + + if ($qtagids) { + $tagnames = []; + $tags = \core_tag_tag::get_bulk($qtagids, 'id, name'); + foreach ($tags as $tag) { + $tagnames[] = $tag->name; + } + $description = get_string('randomqnametags', 'mod_quiz', implode(",", $tagnames)); + } else { + $description = get_string('randomqname', 'mod_quiz'); + } + return shorten_text($description, 255); } /** @@ -249,8 +265,9 @@ class qbank_helper { // Random question. $randomloader = new random_question_loader($qubaids, []); - $newqusetionid = $randomloader->get_next_question_id($slotdata->category, - $slotdata->randomrecurse, self::get_tag_ids_for_slot($slotdata)); + $fitlercondition = $slotdata->filtercondition; + $filter = $fitlercondition['filter'] ?? []; + $newqusetionid = $randomloader->get_next_filtered_question_id($filter); if ($newqusetionid === null) { throw new \moodle_exception('notenoughrandomquestions', 'quiz'); diff --git a/mod/quiz/addrandomform.php b/mod/quiz/classes/question/bank/quiz_managecategories_feature.php similarity index 50% rename from mod/quiz/addrandomform.php rename to mod/quiz/classes/question/bank/quiz_managecategories_feature.php index df1c42fe9f1..6ff8744510f 100644 --- a/mod/quiz/addrandomform.php +++ b/mod/quiz/classes/question/bank/quiz_managecategories_feature.php @@ -14,12 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * File only retained to prevent fatal errors in code that tries to require/include this. - * - * @todo MDL-76612 delete this file as part of Moodle 4.6 development. - * @deprecated This file is no longer required in Moodle 4.2+. - */ -defined('MOODLE_INTERNAL') || die(); +namespace mod_quiz\question\bank; -debugging('This file is no longer required in Moodle 4.2+. Please do not include/require it.', DEBUG_DEVELOPER); +use core_question\local\bank\view; +use mod_quiz\question\bank\filter\custom_category_condition; + +/** + * Class quiz_managecategories_feature + * + * Overrides the default categories feature with a custom category condition. + * + * @package mod_quiz + * @copyright 2022 Catalyst IT EU Ltd. + * @author Mark Johnson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class quiz_managecategories_feature extends \qbank_managecategories\plugin_feature { + + public function get_question_filters(view $qbank = null): array { + return [ + new custom_category_condition($qbank), + ]; + } +} diff --git a/mod/quiz/classes/question/bank/random_question_view.php b/mod/quiz/classes/question/bank/random_question_view.php new file mode 100644 index 00000000000..57bfb70ff6a --- /dev/null +++ b/mod/quiz/classes/question/bank/random_question_view.php @@ -0,0 +1,48 @@ +. + +namespace mod_quiz\question\bank; + +/** + * Subclass to customise the view of the question bank for the quiz editing screen. + * + * @package mod_quiz + * @copyright 2022 Catalyst IT Australia Pty Ltd + * @author 2022 Nathan Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class random_question_view extends custom_view { + /** + * Init required columns. + * + * @return void + */ + protected function init_required_columns(): void { + // Override core columns. + $this->corequestionbankcolumns = [ + 'question_type_column', + 'question_name_text_column', + 'preview_action_column' + ]; + } + + /** + * Prints the table of questions in a category with interactions + * + * @param \context $catcontext*/ + protected function display_bottom_controls(\context $catcontext): void { + } +} diff --git a/mod/quiz/classes/quiz_settings.php b/mod/quiz/classes/quiz_settings.php index 12f2f17528f..d07f0fa2c91 100644 --- a/mod/quiz/classes/quiz_settings.php +++ b/mod/quiz/classes/quiz_settings.php @@ -570,12 +570,14 @@ class quiz_settings { continue; } if ($questiondata->qtype === 'random' && $includepotential) { - if (!isset($qcategories[$questiondata->category])) { - $qcategories[$questiondata->category] = false; - } - if (!empty($questiondata->filtercondition)) { - $filtercondition = json_decode($questiondata->filtercondition); - $qcategories[$questiondata->category] = !empty($filtercondition->includingsubcategories); + $filtercondition = $questiondata->filtercondition; + if (!empty($filtercondition)) { + $filter = $filtercondition['filter']; + if (isset($filter['category'])) { + foreach ($filter['category']['values'] as $catid) { + $qcategories[$catid] = $filter['category']['filteroptions']['includesubcategories']; + } + } } } else { if (!in_array($questiondata->qtype, $questiontypes)) { diff --git a/mod/quiz/classes/structure.php b/mod/quiz/classes/structure.php index 8b2f290f927..15002416706 100644 --- a/mod/quiz/classes/structure.php +++ b/mod/quiz/classes/structure.php @@ -1389,4 +1389,43 @@ class structure { // All the associated code for this method have been removed to get rid of accidental call or errors. return []; } + + /** + * Add a random question to the quiz at a given point. + * + * @param int $addonpage the page on which to add the question. + * @param int $number the number of random questions to add. + * @param array $filtercondition the filter condition. Must contain at least a category filter. + */ + public function add_random_questions(int $addonpage, int $number, array $filtercondition): void { + global $DB; + + if (!isset($filtercondition['filter']['category'])) { + throw new \invalid_parameter_exception('$filtercondition must contain at least a category filter.'); + } + $categoryid = $filtercondition['filter']['category']['values'][0]; + + $category = $DB->get_record('question_categories', ['id' => $categoryid]); + if (!$category) { + new \moodle_exception('invalidcategoryid'); + } + + $catcontext = \context::instance_by_id($category->contextid); + require_capability('moodle/question:useall', $catcontext); + + // Create the selected number of random questions. + for ($i = 0; $i < $number; $i++) { + // Slot data. + $randomslotdata = new stdClass(); + $randomslotdata->quizid = $this->get_quizid(); + $randomslotdata->usingcontextid = context_module::instance($this->get_cmid())->id; + $randomslotdata->questionscontextid = $category->contextid; + $randomslotdata->maxmark = 1; + + $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata); + $randomslot->set_quiz($this->get_quiz()); + $randomslot->set_filter_condition(json_encode($filtercondition)); + $randomslot->insert($addonpage); + } + } } diff --git a/mod/quiz/db/services.php b/mod/quiz/db/services.php index 9cbdb269e4e..2d6545bce49 100644 --- a/mod/quiz/db/services.php +++ b/mod/quiz/db/services.php @@ -215,4 +215,20 @@ $functions = [ 'capabilities' => 'mod/quiz:reopenattempts', 'ajax' => true, ], + + 'mod_quiz_add_random_questions' => [ + 'classname' => 'mod_quiz\external\add_random_questions', + 'description' => 'Add a number of random questions to a quiz.', + 'type' => 'write', + 'capabilities' => 'mod/quiz:manage', + 'ajax' => true, + ], + + 'mod_quiz_update_filter_condition' => [ + 'classname' => 'mod_quiz\external\update_filter_condition', + 'description' => 'Update filter condition for a random question slot.', + 'type' => 'write', + 'capabilities' => 'mod/quiz:manage', + 'ajax' => true, + ], ]; diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 2ae72844e4f..1a74a09b494 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -49,7 +49,7 @@ require_once($CFG->dirroot . '/question/editlib.php'); $mdlscrollto = optional_param('mdlscrollto', '', PARAM_INT); list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = - question_edit_setup('editq', '/mod/quiz/edit.php', true); + question_edit_setup('editq', '/mod/quiz/edit.php', true); $PAGE->set_url($thispageurl); $PAGE->set_secondary_active_tab("mod_quiz_edit"); @@ -166,10 +166,6 @@ $event = \mod_quiz\event\edit_page_viewed::create([ ]); $event->trigger(); -// Get the question bank view. -$questionbank = new mod_quiz\question\bank\custom_view($contexts, $thispageurl, $course, $cm, $quiz); -$questionbank->set_quiz_has_attempts($quizhasattempts); - // End of process commands =====================================================. $PAGE->set_pagelayout('incourse'); @@ -184,8 +180,13 @@ $node = $PAGE->settingsnav->find('mod_quiz_edit', navigation_node::TYPE_SETTING) if ($node) { $node->make_active(); } -echo $OUTPUT->header(); +// Add random question - result message. +if ($message = optional_param('message', '', PARAM_TEXT)) { + core\notification::add($message, core\notification::SUCCESS); +} + +echo $OUTPUT->header(); // Initialise the JavaScript. $quizeditconfig = new stdClass(); $quizeditconfig->url = $thispageurl->out(true, ['qbanktool' => '0']); diff --git a/mod/quiz/editrandom.php b/mod/quiz/editrandom.php index 155edfb9de0..b0412d16e03 100644 --- a/mod/quiz/editrandom.php +++ b/mod/quiz/editrandom.php @@ -24,9 +24,11 @@ */ use mod_quiz\quiz_settings; +use mod_quiz\question\bank\random_question_view; require_once(__DIR__ . '/../../config.php'); require_once($CFG->dirroot . '/mod/quiz/locallib.php'); +require_once($CFG->dirroot . '/mod/quiz/lib.php'); $slotid = required_param('slotid', PARAM_INT); $returnurl = optional_param('returnurl', '', PARAM_LOCALURL); @@ -53,16 +55,14 @@ $PAGE->add_body_class('limitedwidth'); $setreference = $DB->get_record('question_set_references', ['itemid' => $slot->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']); -$filterconditions = json_decode($setreference->filtercondition); +$filterconditions = json_decode($setreference->filtercondition, true); -// Validate the question category. -if (!$category = $DB->get_record('question_categories', ['id' => $filterconditions->questioncategoryid])) { - new moodle_exception('categorydoesnotexist', 'question', $returnurl); -} +$params = $filterconditions; +$params['cmid'] = $cm->id; +$extraparams['view'] = random_question_view::class; -// Check permissions. -$catcontext = context::instance_by_id($category->contextid); -require_capability('moodle/question:useall', $catcontext); +// Build required parameters. +[$contexts, $thispageurl, $cm, $pagevars, $extraparams] = build_required_parameters_for_custom_view($params, $extraparams); $thiscontext = $quizobj->get_context(); $contexts = new core_question\local\bank\question_edit_contexts($thiscontext); @@ -72,11 +72,15 @@ $mform = new mod_quiz\form\randomquestion_form(new moodle_url('/mod/quiz/editran // Set the form data. $toform = new stdClass(); -$toform->category = "{$category->id},{$category->contextid}"; -$toform->includesubcategories = $filterconditions->includingsubcategories; +$toform->category = $filterconditions['filter']['category']['values'][0]; +$includesubcategories = false; +if (!empty($filterconditions['filter']['category']['filteroptions']['includesubcategories'])) { + $includesubcategories = true; +} +$toform->includesubcategories = $includesubcategories; $toform->fromtags = []; -if (isset($filterconditions->tags)) { - $currentslottags = $filterconditions->tags; +if (isset($filterconditions['tags'])) { + $currentslottags = $filterconditions['tags']; foreach ($currentslottags as $slottag) { $toform->fromtags[] = $slottag; } @@ -130,11 +134,22 @@ $PAGE->set_title('Random question'); $PAGE->set_heading($COURSE->fullname); $PAGE->navbar->add('Random question'); +// Custom View. +$questionbank = new random_question_view($contexts, $thispageurl, $course, $cm, $params, $extraparams); + +// Output. +$renderer = $PAGE->get_renderer('mod_quiz', 'edit'); +$data = new \stdClass(); +$data->questionbank = $renderer->question_bank_contents($questionbank, $params); +$data->cmid = $cm->id; +$data->slotid = $slot->id; +$data->returnurl = $returnurl; +$updateform = $OUTPUT->render_from_template('mod_quiz/update_filter_condition_form', $data); +$PAGE->requires->js_call_amd('mod_quiz/update_random_question_filter_condition', 'init'); + // Display a heading, question editing form. echo $OUTPUT->header(); $heading = get_string('randomediting', 'mod_quiz'); echo $OUTPUT->heading_with_help($heading, 'randomquestion', 'mod_quiz'); - -$mform->display(); - +echo $updateform; echo $OUTPUT->footer(); diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index 98f3f531fda..6a1ff631508 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -32,6 +32,7 @@ $string['addaquestion'] = 'a new question'; $string['addasection'] = 'a new section heading'; $string['addarandomquestion'] = 'a random question'; $string['addarandomquestion_help'] = 'When a random question is added, it results in a randomly-chosen question from the category being inserted into the quiz. This means that different students are likely to get a different selection of questions, and when a quiz allows multiple attempts then each attempt is likely to contain a new selection of questions.'; +$string['addarandomquestion_success'] = 'Random questions have been added'; $string['addarandomselectedquestion'] = 'Add a random selected question ...'; $string['adddescriptionlabel'] = 'Add a description item'; $string['addingquestion'] = 'Adding a question'; @@ -819,6 +820,8 @@ $string['randomfromexistingcategory'] = 'Random question from an existing catego $string['randomfromunavailabletag'] = '{$a} (unavailable)'; $string['randomnumber'] = 'Number of random questions'; $string['randomnosubcat'] = 'Questions from this category only, not its subcategories.'; +$string['randomqname'] = 'Random question based on filter condition'; +$string['randomqnametags'] = 'Random question based on filter condition with tags: {$a}'; $string['randomquestion'] = 'Random question'; $string['randomquestion_help'] = 'A random question is a way of inserting a randomly-chosen question from a specified category or by a specified tag into an activity.'; $string['randomquestiontags'] = 'Tags'; @@ -1051,6 +1054,8 @@ $string['unfinished'] = 'open'; $string['ungraded'] = 'Ungraded'; $string['unit'] = 'Unit'; $string['unknowntype'] = 'Question type not supported at line {$a}. The question will be ignored'; +$string['updatefilterconditon'] = 'Update filter conditions'; +$string['updatefilterconditon_success'] = 'Successfully updated filter conditions'; $string['updateoverdueattemptstask'] = 'Updating overdue quiz attempts'; $string['updatesettings'] = 'Update quiz settings'; $string['updatequizslotswithrandomxofy'] = 'Updating quiz slots with "random" question data ({$a->done}/{$a->total})'; diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 9213c0781fe..5c3130d6b56 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -26,10 +26,11 @@ */ +use qbank_managecategories\helper; + defined('MOODLE_INTERNAL') || die(); use mod_quiz\access_manager; -use mod_quiz\form\add_random_form; use mod_quiz\grade_calculator; use mod_quiz\question\bank\custom_view; use mod_quiz\question\bank\qbank_helper; @@ -41,6 +42,7 @@ use mod_quiz\quiz_attempt; use mod_quiz\quiz_settings; require_once($CFG->dirroot . '/calendar/lib.php'); +require_once($CFG->dirroot . '/question/editlib.php'); /**#@+ * Option controlling what options are offered on the quiz settings form. @@ -2392,27 +2394,27 @@ function mod_quiz_core_calendar_event_timestart_updated(\calendar_event $event, * @param array $args The fragment arguments. * @return string The rendered mform fragment. */ -function mod_quiz_output_fragment_quiz_question_bank($args) { - global $CFG, $DB, $PAGE; - require_once($CFG->dirroot . '/mod/quiz/locallib.php'); - require_once($CFG->dirroot . '/question/editlib.php'); +function mod_quiz_output_fragment_quiz_question_bank($args): string { + global $PAGE; - $querystring = preg_replace('/^\?/', '', $args['querystring']); + // Retrieve params. $params = []; + $extraparams = []; + $querystring = parse_url($args['querystring'], PHP_URL_QUERY); parse_str($querystring, $params); - // Build the required resources. The $params are all cleaned as - // part of this process. - list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = - question_build_edit_resources('editq', '/mod/quiz/edit.php', $params, custom_view::DEFAULT_PAGE_SIZE); + $viewclass = \mod_quiz\question\bank\custom_view::class; + $extraparams['view'] = $viewclass; - // Get the course object and related bits. - $course = get_course($quiz->course); + // Build required parameters. + [$contexts, $thispageurl, $cm, $pagevars, $extraparams] = + build_required_parameters_for_custom_view($params, $extraparams); + + $course = get_course($cm->course); require_capability('mod/quiz:manage', $contexts->lowest()); - // Create quiz question bank view. - $questionbank = new custom_view($contexts, $thispageurl, $course, $cm, $quiz); - $questionbank->set_quiz_has_attempts(quiz_has_attempts($quiz->id)); + // Custom View. + $questionbank = new $viewclass($contexts, $thispageurl, $course, $cm, $pagevars, $extraparams); // Output. $renderer = $PAGE->get_renderer('mod_quiz', 'edit'); @@ -2433,32 +2435,54 @@ function mod_quiz_output_fragment_quiz_question_bank($args) { * @return string The rendered mform fragment. */ function mod_quiz_output_fragment_add_random_question_form($args) { - global $CFG; + global $PAGE, $OUTPUT; - $contexts = new \core_question\local\bank\question_edit_contexts($args['context']); - $formoptions = [ - 'contexts' => $contexts, - 'cat' => $args['cat'] - ]; - $formdata = [ - 'category' => $args['cat'], - 'addonpage' => $args['addonpage'], - 'returnurl' => $args['returnurl'], - 'cmid' => $args['cmid'] + $extraparams = []; + + // Build required parameters. + [$contexts, $thispageurl, $cm, $pagevars, $extraparams] = + build_required_parameters_for_custom_view($args, $extraparams); + + // Additional param to differentiate with other question bank view. + $extraparams['view'] = mod_quiz\question\bank\random_question_view::class; + + $course = get_course($cm->course); + require_capability('mod/quiz:manage', $contexts->lowest()); + + // Custom View. + $questionbank = new mod_quiz\question\bank\random_question_view($contexts, $thispageurl, $course, $cm, $pagevars, $extraparams); + + $renderer = $PAGE->get_renderer('mod_quiz', 'edit'); + $questionbankoutput = $renderer->question_bank_contents($questionbank, $pagevars); + + $maxrand = 100; + for ($i = 1; $i <= min(100, $maxrand); $i++) { + $randomcount[] = ['value' => $i, 'name' => $i]; + } + + // Parent category select. + $usablecontexts = $contexts->having_cap('moodle/question:useall'); + $categoriesarray = helper::question_category_options($usablecontexts); + $catoptions = []; + foreach ($categoriesarray as $group => $opts) { + // Options for each category group. + $categories = []; + foreach ($opts as $context => $name) { + $categories[] = ['value' => $context, 'name' => $name]; + } + $catoptions[] = ['label' => $group, 'options' => $categories]; + } + + // Template data. + $data = [ + 'questionbank' => $questionbankoutput, + 'randomoptions' => $randomcount, + 'questioncategoryoptions' => $catoptions, ]; - $form = new add_random_form( - new \moodle_url('/mod/quiz/addrandom.php'), - $formoptions, - 'post', - '', - null, - true, - $formdata - ); - $form->set_data($formdata); + $result = $OUTPUT->render_from_template('mod_quiz/add_random_question_form', $data); - return $form->render(); + return $result; } /** @@ -2505,6 +2529,61 @@ function quiz_delete_references($quizid): void { } } +/** + * Question data fragment to get the question html via ajax call. + * + * @param array $args + * @return string + */ +function mod_quiz_output_fragment_question_data(array $args): string { + // Return if there is no args. + if (empty($args)) { + return ''; + } + + // Retrieve params from query string. + [$params, $extraparams] = \core_question\local\bank\filter_condition_manager::extract_parameters_from_fragment_args($args); + + // Build required parameters. + $cmid = clean_param($args['cmid'], PARAM_INT); + $thispageurl = new \moodle_url('/mod/quiz/edit.php', ['cmid' => $cmid]); + $thiscontext = \context_module::instance($cmid); + $contexts = new \core_question\local\bank\question_edit_contexts($thiscontext); + $course = get_course($params['courseid']); + [, $cm] = get_module_from_cmid($cmid); + + // Custom question bank View. + $viewclass = clean_param($args['view'], PARAM_NOTAGS); + $questionbank = new $viewclass($contexts, $thispageurl, $course, $cm, $params, $extraparams); + + // Question table. + return $questionbank->display_questions_table(); +} + +/** + * Build required parameters for question bank custom view + * + * @param array $params the page parameters + * @param array $extraparams additional parameters + * @return array + */ +function build_required_parameters_for_custom_view(array $params, array $extraparams): array { + // Retrieve questions per page. + $viewclass = $extraparams['view'] ?? null; + $defaultpagesize = $viewclass ? $viewclass::DEFAULT_PAGE_SIZE : DEFAULT_QUESTIONS_PER_PAGE; + // Build the required params. + [$thispageurl, $contexts, $cmid, $cm, , $pagevars] = question_build_edit_resources( + 'editq', + '/mod/quiz/edit.php', + array_merge($params, $extraparams), + $defaultpagesize); + + // Add cmid so we can retrieve later in extra params. + $extraparams['cmid'] = $cmid; + + return [$contexts, $thispageurl, $cm, $pagevars, $extraparams]; +} + /** * Implement the calculate_question_stats callback. * diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 24140d09f20..83636df5476 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -213,8 +213,9 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time // Deal with fixed random choices for testing. if (isset($questionids[$quba->next_slot_number()])) { - if ($randomloader->is_question_available($questiondata->category, - (bool) $questiondata->questiontext, $questionids[$quba->next_slot_number()], $tagids)) { + $filtercondition = $questiondata->filtercondition; + $filters = $filtercondition['filter'] ?? []; + if ($randomloader->is_filtered_question_available($filters, $questionids[$quba->next_slot_number()])) { $questions[$slot] = question_bank::load_question( $questionids[$quba->next_slot_number()], $quizobj->get_quiz()->shuffleanswers); continue; @@ -224,8 +225,10 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time } // Normal case, pick one at random. - $questionid = $randomloader->get_next_question_id($questiondata->category, - $questiondata->randomrecurse, $tagids); + $filtercondition = $questiondata->filtercondition; + $filters = $filtercondition['filter'] ?? []; + $questionid = $randomloader->get_next_filtered_question_id($filters); + if ($questionid === null) { throw new moodle_exception('notenoughrandomquestions', 'quiz', $quizobj->view_url(), $questiondata); @@ -1884,54 +1887,18 @@ function quiz_update_section_firstslots($quizid, $direction, $afterslot, $before * @param int $addonpage the page on which to add the question. * @param int $categoryid the question category to add the question from. * @param int $number the number of random questions to add. - * @param bool $includesubcategories whether to include questoins from subcategories. - * @param int[] $tagids Array of tagids. The question that will be picked randomly should be tagged with all these tags. + * @deprecated Since Moodle 4.3 MDL-72321 + * @todo Final deprecation in Moodle 4.7 MDL-78091 */ -function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, - $includesubcategories, $tagids = []) { - global $DB; +function quiz_add_random_questions(stdClass $quiz, int $addonpage, int $categoryid, int $number): void { + debugging( + 'quiz_add_random_questions is deprecated. Please use mod_quiz\structure::add_random_questions() instead.', + DEBUG_DEVELOPER + ); - $category = $DB->get_record('question_categories', ['id' => $categoryid]); - if (!$category) { - new moodle_exception('invalidcategoryid'); - } - - $catcontext = context::instance_by_id($category->contextid); - require_capability('moodle/question:useall', $catcontext); - - // Tags for filter condition. - $tags = \core_tag_tag::get_bulk($tagids, 'id, name'); - $tagstrings = []; - foreach ($tags as $tag) { - $tagstrings[] = "{$tag->id},{$tag->name}"; - } - // Create the selected number of random questions. - for ($i = 0; $i < $number; $i++) { - // Set the filter conditions. - $filtercondition = new stdClass(); - $filtercondition->questioncategoryid = $categoryid; - $filtercondition->includingsubcategories = $includesubcategories ? 1 : 0; - if (!empty($tagstrings)) { - $filtercondition->tags = $tagstrings; - } - - if (!isset($quiz->cmid)) { - $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course); - $quiz->cmid = $cm->id; - } - - // Slot data. - $randomslotdata = new stdClass(); - $randomslotdata->quizid = $quiz->id; - $randomslotdata->usingcontextid = context_module::instance($quiz->cmid)->id; - $randomslotdata->questionscontextid = $category->contextid; - $randomslotdata->maxmark = 1; - - $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata); - $randomslot->set_quiz($quiz); - $randomslot->set_filter_condition($filtercondition); - $randomslot->insert($addonpage); - } + $settings = quiz_settings::create($quiz->id); + $structure = \mod_quiz\structure::create_for_quiz($settings); + $structure->add_random_questions($addonpage, $number, $categoryid); } /** diff --git a/mod/quiz/report/overview/tests/behat/regrading.feature b/mod/quiz/report/overview/tests/behat/regrading.feature index a31927b81e9..8f91fc11cee 100644 --- a/mod/quiz/report/overview/tests/behat/regrading.feature +++ b/mod/quiz/report/overview/tests/behat/regrading.feature @@ -197,7 +197,6 @@ Feature: Regrading quiz attempts using the Grades report And I click on "Yes" "button" in the "Confirm" "dialogue" And I click on "Add" "link" And I follow "a random question" - And I set the field "Number of random questions" to "1" And I press "Add random question" And I am on the "Quiz for testing regrading" "quiz activity" page logged in as student3 And I click on "Attempt quiz" "button" diff --git a/mod/quiz/templates/add_random_question_form.mustache b/mod/quiz/templates/add_random_question_form.mustache new file mode 100644 index 00000000000..6a95c3c3f97 --- /dev/null +++ b/mod/quiz/templates/add_random_question_form.mustache @@ -0,0 +1,90 @@ +{{! + 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 . +}} +{{! + @template mod_quiz/add_random_question_form + + From to add random questions. + + Example context (json): + { + "returnurl": "http://example.com", + "randomoptions": [ + {"name": "1", "value": "1"}, + {"name": "2", "value": "2"} + ], + "questioncategoryoptions": { + "label": "Category 1", + "options": [ + {"name": "Category 2", "value": "1"}, + {"name": "Category 3", "value": "2"} + ] + }, + "questionbank": "
Question bank HTML goes here
" + } +}} +
+
+
+ {{#str}} randomfromexistingcategory, mod_quiz {{/str}} + + {{{questionbank}}} + + + + + + + +
+ +
+ {{#str}} randomquestionusinganewcategory, mod_quiz {{/str}} + + + + + + + + + + +
+
+
diff --git a/mod/quiz/templates/random_question_form_preview.mustache b/mod/quiz/templates/random_question_container.mustache similarity index 53% rename from mod/quiz/templates/random_question_form_preview.mustache rename to mod/quiz/templates/random_question_container.mustache index f90ec9f0ec1..18b768c7876 100644 --- a/mod/quiz/templates/random_question_form_preview.mustache +++ b/mod/quiz/templates/random_question_container.mustache @@ -15,26 +15,14 @@ along with Moodle. If not, see . }} {{! - @template mod_quiz/random_question_form_preview + @template mod_quiz/random_question_container - A preview section for the list of questions that match the random - question criteria in the random question form. - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * title A cleaned string (use clean_text()) to display. - * body HTML content for the boday + Container for list of filtered questions. Example context (json): - {} + { + } + }} -
-
-
- {{> core/overlay_loading}} +
diff --git a/mod/quiz/templates/update_filter_condition_form.mustache b/mod/quiz/templates/update_filter_condition_form.mustache new file mode 100644 index 00000000000..efe499605bb --- /dev/null +++ b/mod/quiz/templates/update_filter_condition_form.mustache @@ -0,0 +1,43 @@ +{{! + 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 . +}} +{{! + @template mod_quiz/update_filter_condition_form + + From to add random questions. + + Example context (json): + { + "questionbank": "
Question bank HTML goes here
", + "returnurl": "https://example.com", + "cmid": "1234", + "slotid": "5678" + } +}} +
+ +
+ {{{questionbank}}} +
+ + + +
+ + + +
+
diff --git a/mod/quiz/tests/attempt_walkthrough_from_csv_test.php b/mod/quiz/tests/attempt_walkthrough_from_csv_test.php index 86a801d442c..4cb2e15304b 100644 --- a/mod/quiz/tests/attempt_walkthrough_from_csv_test.php +++ b/mod/quiz/tests/attempt_walkthrough_from_csv_test.php @@ -23,6 +23,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); /** * Quiz attempt walk through using data from csv file. @@ -35,6 +36,8 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php'); */ class attempt_walkthrough_from_csv_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + /** * @var string[] names of the files which contain the test data. */ @@ -134,7 +137,7 @@ class attempt_walkthrough_from_csv_test extends \advanced_testcase { if ($slotquestion['type'] !== 'random') { quiz_add_quiz_question($slotquestion['id'], $this->quiz, 0, $slotquestion['mark']); } else { - quiz_add_random_questions($this->quiz, 0, $slotquestion['catid'], 1, 0); + $this->add_random_questions($this->quiz->id, 0, $slotquestion['catid'], 1); $this->randqids[$slotno] = $qidsbycat[$slotquestion['catid']]; } } diff --git a/mod/quiz/tests/attempt_walkthrough_test.php b/mod/quiz/tests/attempt_walkthrough_test.php index c2a294f33ff..21a2c557260 100644 --- a/mod/quiz/tests/attempt_walkthrough_test.php +++ b/mod/quiz/tests/attempt_walkthrough_test.php @@ -24,6 +24,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); /** * Quiz attempt walk through. @@ -37,6 +38,8 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php'); */ class attempt_walkthrough_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + /** * Create a quiz with questions and walk through a quiz attempt. */ @@ -288,7 +291,7 @@ class attempt_walkthrough_test extends \advanced_testcase { $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); // Add random question to the quiz. - quiz_add_random_questions($quiz, 0, $cat->id, 1, false); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); // Make another category. $cat2 = $questiongenerator->create_question_category(); diff --git a/mod/quiz/tests/behat/behat_mod_quiz.php b/mod/quiz/tests/behat/behat_mod_quiz.php index e249c07e26c..ed65cd9aa4e 100644 --- a/mod/quiz/tests/behat/behat_mod_quiz.php +++ b/mod/quiz/tests/behat/behat_mod_quiz.php @@ -276,7 +276,18 @@ class behat_mod_quiz extends behat_question_base { } else { $includingsubcategories = clean_param($questiondata['includingsubcategories'], PARAM_BOOL); } - quiz_add_random_questions($quiz, $page, $question->category, 1, $includingsubcategories); + + $filter = [ + 'category' => [ + 'jointype' => \qbank_managecategories\category_condition::JOINTYPE_DEFAULT, + 'values' => [$question->category], + 'filteroptions' => ['includesubcategories' => $includingsubcategories], + ], + ]; + $filtercondition['filter'] = $filter; + $settings = quiz_settings::create($quiz->id); + $structure = \mod_quiz\structure::create_for_quiz($settings); + $structure->add_random_questions($page, 1, $filtercondition); } else { // Add the question. quiz_add_quiz_question($question->id, $quiz, $page, $maxmark); diff --git a/mod/quiz/tests/behat/editing_add.feature b/mod/quiz/tests/behat/editing_add.feature index 7fca18fe8bf..639ba3bebb8 100644 --- a/mod/quiz/tests/behat/editing_add.feature +++ b/mod/quiz/tests/behat/editing_add.feature @@ -117,7 +117,6 @@ Feature: Edit quiz page - adding things And I select "Questions" from the "Question bank tertiary navigation" singleselect And I should see "Question bank" - And I should see "Select a category" # Create the Essay 01 question. When I press "Create a new question ..." @@ -131,8 +130,6 @@ Feature: Edit quiz page - adding things And I should see "Essay 01" # Create the Essay 02 question. - And I should see "Select a category" - And I set the field "Select a category:" to "Subcat 1" When I press "Create a new question ..." And I set the field "item_qtype_essay" to "1" And I click on "Add" "button" in the "Choose a question type to add" "dialogue" @@ -144,7 +141,6 @@ Feature: Edit quiz page - adding things And I should see "Essay 02" # Create the Essay 03 question. - And I set the field "Select a category" to "Default for C1" And I wait until the page is ready When I press "Create a new question ..." And I set the field "item_qtype_essay" to "1" @@ -201,8 +197,6 @@ Feature: Edit quiz page - adding things # Add Esay 02 from question bank. And I open the "Page 1" add to quiz menu And I follow "from question bank" - And I should see "Select a category" - And I set the field "Select a category" to "Subcat 1" And I click on "Add to quiz" "link" in the "Essay 02" "table_row" And I should see "Essay 03" on quiz page "1" And I should see "Essay 01" on quiz page "1" diff --git a/mod/quiz/tests/behat/editing_add_from_question_bank.feature b/mod/quiz/tests/behat/editing_add_from_question_bank.feature index 7630ad2e845..a4140863be7 100644 --- a/mod/quiz/tests/behat/editing_add_from_question_bank.feature +++ b/mod/quiz/tests/behat/editing_add_from_question_bank.feature @@ -40,8 +40,7 @@ Feature: Adding questions to a quiz from the question bank Then I should see "foo" in the "question 01 name" "table_row" And I should see "bar" in the "question 02 name" "table_row" And I should see "qidnum" in the "question 02 name" "table_row" - And I set the field "Filter by tags..." to "foo" - And I press the enter key + When I apply question bank filter "Tag" with value "foo" And I should see "question 01 name" in the "categoryquestions" "table" And I should not see "question 02 name" in the "categoryquestions" "table" @@ -76,9 +75,8 @@ Feature: Adding questions to a quiz from the question bank Then I should see "question 21 name" in the "categoryquestions" "table" And I should see "question 22 name" in the "categoryquestions" "table" And I should not see "question 01 name" in the "categoryquestions" "table" - And I click on "Show all 22" "link" in the ".question-showall-text" "css_element" + And I click on "1" "link" in the ".pagination" "css_element" And I should see "question 01 name" in the "categoryquestions" "table" - And I should see "question 22 name" in the "categoryquestions" "table" Scenario: Questions are added in the right place with multiple sections Given the following "questions" exist: diff --git a/mod/quiz/tests/behat/editing_add_random.feature b/mod/quiz/tests/behat/editing_add_random.feature index 17113976a2a..856db6e3c4c 100644 --- a/mod/quiz/tests/behat/editing_add_random.feature +++ b/mod/quiz/tests/behat/editing_add_random.feature @@ -43,7 +43,9 @@ Feature: Adding random questions to a quiz based on category and tags Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1" When I open the "last" add to quiz menu And I follow "a random question" - And I open the autocomplete suggestions list + And I add question bank filter "Tag" + And I click on "Tag" "field" + And I press the down key Then "foo" "autocomplete_suggestions" should exist And "bar" "autocomplete_suggestions" should exist @@ -51,21 +53,16 @@ Feature: Adding random questions to a quiz based on category and tags Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1" When I open the "last" add to quiz menu And I follow "a random question" - And I set the field "Category" to "Top for Course 1" - And I wait until the page is ready - And I open the autocomplete suggestions list - And I click on "foo" item in the autocomplete list - Then I should see "question 1 name" - And I should see "question 3 name" - And I should not see "question 2 name" - And I should not see "question 4 name" - And I set the field "Category" to "Questions Category 1" + And I apply question bank filter "Category" with value "Questions Category 1" + And I apply question bank filter "Tag" with value "foo" + And I click on "Apply filters" "button" And I wait until the page is ready And I should see "question 1 name" And I should not see "question 3 name" And I should not see "question 2 name" And I should not see "question 4 name" - And I click on "Include questions from subcategories too" "checkbox" + And I set the field "Also show questions from subcategories" to "1" + And I click on "Apply filters" "button" And I wait until the page is ready And I should see "question 1 name" And I should see "question 3 name" @@ -76,10 +73,11 @@ Feature: Adding random questions to a quiz based on category and tags Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1" When I open the "last" add to quiz menu And I follow "a random question" - And I set the field "Tags" to "foo" + And I apply question bank filter "Tag" with value "foo" + And I select "1" from the "randomcount" singleselect And I press "Add random question" - And I should see "Random (Questions Category 1, tags: foo)" on quiz page "1" - And I click on "(See questions)" "link" + And I should see "Random question based on filter condition with tags: foo" on quiz page "1" + When I click on "Configure question" "link" in the "Random question based on filter condition with tags: foo" "list_item" Then I should see "Questions Category 1" And I should see "foo" And I should see "question 1 name" @@ -101,8 +99,8 @@ Feature: Adding random questions to a quiz based on category and tags And I follow "New category" And I set the following fields to these values: | Name | New Random category | - | Parent category | Top for Quiz 1 | + | Parent category | Default for Quiz 1 | And I press "Create category and add random question" - And I should see "Random (New Random category)" on quiz page "1" - And I click on "(See questions)" "link" - Then I should see "Top for Quiz 1" + And I should see "Random question based on filter condition" on quiz page "1" + And I click on "Configure question" "link" in the "Random question based on filter condition" "list_item" + Then I should see "New Random category" diff --git a/mod/quiz/tests/behat/editing_edit_random.feature b/mod/quiz/tests/behat/editing_edit_random.feature index d853b4ee0de..df05f8344a6 100644 --- a/mod/quiz/tests/behat/editing_edit_random.feature +++ b/mod/quiz/tests/behat/editing_edit_random.feature @@ -38,18 +38,17 @@ Feature: Editing random questions already in a quiz based on category and tags And I follow "a random question" # To actually reproduce MDL-68733 it would be better to set tags easy,essay here, and then below just delete one tag. # However, the state of Behat for autocomplete fields does not let us actually do that. - And I set the field "Tags" to "easy" + And I apply question bank filter "Tag" with value "easy" And I press "Add random question" And I open the "Page 1" add to quiz menu And I follow "a random question" - And I set the field "Tags" to "hard" + And I apply question bank filter "Tag" with value "hard" And I press "Add random question" And I follow "Add page break" - When I click on "Configure question" "link" in the "Random (Questions Category 1, tags: easy)" "list_item" - And I click on "easy" "autocomplete_selection" - And I set the field "Tags" to "essay" - And I press "Save changes" - Then I should see "Random (Questions Category 1, tags: essay)" on quiz page "1" - And I should see "Random (Questions Category 1, tags: hard)" on quiz page "2" - And I click on "Configure question" "link" in the "Questions Category 1, tags: hard" "list_item" + When I click on "Configure question" "link" in the "Random question based on filter condition with tags: easy" "list_item" + And I apply question bank filter "Tag" with value "essay" + And I press "Update filter conditions" + Then I should see "Random question based on filter condition with tags: essay" on quiz page "1" + And I should see "Random question based on filter condition with tags: hard" on quiz page "2" + And I click on "Configure question" "link" in the "Random question based on filter condition with tags: hard" "list_item" And "hard" "autocomplete_selection" should be visible diff --git a/mod/quiz/tests/behat/editing_remove_multiple_questions.feature b/mod/quiz/tests/behat/editing_remove_multiple_questions.feature index 1e7ff95ad03..5aa2fa6adb9 100644 --- a/mod/quiz/tests/behat/editing_remove_multiple_questions.feature +++ b/mod/quiz/tests/behat/editing_remove_multiple_questions.feature @@ -261,11 +261,9 @@ Feature: Edit quiz page - remove multiple questions And I click on "selectquestion-2" "checkbox" And I click on "Delete selected" "button" And I click on "Yes" "button" in the "Confirm" "dialogue" - # To make sure question is deleted completely. - And I reload the page - Then I should see "Random (Test questions)" on quiz page "1" - And I should not see "Random (Test questions)" on quiz page "2" - And I should not see "Random (Test questions)" on quiz page "3" + Then I should see "Random question based on filter condition" on quiz page "1" + And I should not see "Random question based on filter condition" on quiz page "2" + And I should not see "Random question based on filter condition" on quiz page "3" And I should see "Total of marks: 1.00" And I should see "Questions: 1" @@ -284,16 +282,15 @@ Feature: Edit quiz page - remove multiple questions # Delete all questions in page. Page contains multiple questions. When I open the "last" add to quiz menu And I follow "a random question" - And I set the field "Number of random questions" to "3" + And I click on "menurandomcount" "select" + And I click on "3" "option" And I press "Add random question" And I click on "Select multiple items" "button" And I press "Select all" And I click on "Delete selected" "button" And I click on "Yes" "button" in the "Confirm" "dialogue" - # To make sure question is deleted completely. - And I reload the page - Then I should not see "Random (Test questions)" on quiz page "1" - And I should not see "Random (Test questions)" on quiz page "2" - And I should not see "Random (Test questions)" on quiz page "3" + Then I should not see "Random question based on filter condition" on quiz page "1" + And I should not see "Random question based on filter condition" on quiz page "2" + And I should not see "Random question based on filter condition" on quiz page "3" And I should see "Total of marks: 0.00" And I should see "Questions: 0" diff --git a/mod/quiz/tests/behat/random_question.feature b/mod/quiz/tests/behat/random_question.feature index a6f3562640d..15a7e047b68 100644 --- a/mod/quiz/tests/behat/random_question.feature +++ b/mod/quiz/tests/behat/random_question.feature @@ -33,23 +33,24 @@ Feature: Moving a question to another category should not affect random question Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1" When I open the "last" add to quiz menu And I follow "a random question" - And I set the field "Category" to "Used category" + And I apply question bank filter "Category" with value "Used category" And I press "Add random question" - And I should see "Random (Used category)" on quiz page "1" - And I click on "(See questions)" "link" + And I should see "Random question based on filter condition" on quiz page "1" + And I click on "Configure question" "link" in the "Random question based on filter condition" "list_item" And I should see "Used category" + And I am on "Course 1" course homepage + And I navigate to "Question bank" in current page administration + And I apply question bank filter "Category" with value "Used category" And I click on "Test question to be moved" "checkbox" in the "Test question to be moved" "table_row" And I click on "With selected" "button" And I click on question bulk action "move" And I set the field "Question category" to "Subcategory" And I press "Move to" Then I should see "Test question to be moved" - And the field "Select a category" matches value "      Subcategory (1)" - And the "Select a category" select box should contain "Used category" - And the "Select a category" select box should not contain "Used category (1)" + And I should see "Subcategory (1)" And I am on the "Quiz 1" "mod_quiz > Edit" page - And I should see "Random (Used category)" on quiz page "1" - And I click on "(See questions)" "link" + And I should see "Random question based on filter condition" on quiz page "1" + And I click on "Configure question" "link" in the "Random question based on filter condition" "list_item" And I should see "Used category" @javascript @@ -57,9 +58,9 @@ Feature: Moving a question to another category should not affect random question Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1" When I open the "last" add to quiz menu And I follow "a random question" - And I set the field "Category" to "Used category" + And I apply question bank filter "Category" with value "Used category" And I press "Add random question" - And I should see "Random (Used category)" on quiz page "1" + And I should see "Random question based on filter condition" on quiz page "1" And I am on the "Course 1" "core_question > course question categories" page And I click on "Edit this category" "link" in the "Used category" "list_item" And I set the following fields to these values: @@ -69,4 +70,4 @@ Feature: Moving a question to another category should not affect random question Then I should see "Used category new" And I should see "I was edited" in the "Used category new" "list_item" And I am on the "Quiz 1" "mod_quiz > Edit" page - And I should see "Random (Used category new)" on quiz page "1" + And I should see "Random question based on filter condition" on quiz page "1" diff --git a/mod/quiz/tests/external/external_test.php b/mod/quiz/tests/external/external_test.php index 9fc37410f38..d3796d5fbf7 100644 --- a/mod/quiz/tests/external/external_test.php +++ b/mod/quiz/tests/external/external_test.php @@ -41,6 +41,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); /** * Silly class to access mod_quiz_external internal methods. @@ -86,6 +87,8 @@ class testable_mod_quiz_external extends mod_quiz_external { */ class external_test extends externallib_advanced_testcase { + use \quiz_question_helper_test_trait; + /** @var \stdClass course record. */ protected $course; @@ -1898,7 +1901,7 @@ class external_test extends externallib_advanced_testcase { $question = $questiongenerator->create_question('truefalse', null, ['category' => $cat->id]); $question = $questiongenerator->create_question('essay', null, ['category' => $cat->id]); - quiz_add_random_questions($quiz, 0, $cat->id, 1, false); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); $quizobj = quiz_settings::create($quiz->id, $this->student->id); @@ -2024,8 +2027,8 @@ class external_test extends externallib_advanced_testcase { $question = $questiongenerator->create_question('essay', null, ['category' => $anothercat->id]); // Add a couple of random questions from the same category. - quiz_add_random_questions($quiz, 0, $cat->id, 1, false); - quiz_add_random_questions($quiz, 0, $cat->id, 1, false); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); $this->setUser($this->student); @@ -2039,7 +2042,7 @@ class external_test extends externallib_advanced_testcase { // Add more questions to the quiz, this time from the other category. $this->setAdminUser(); - quiz_add_random_questions($quiz, 0, $anothercat->id, 1, false); + $this->add_random_questions($quiz->id, 0, $anothercat->id, 1); $this->setUser($this->student); $result = mod_quiz_external::get_quiz_required_qtypes($quiz->id); diff --git a/mod/quiz/tests/lib_test.php b/mod/quiz/tests/lib_test.php index 53b598a0b0d..7a8bf421408 100644 --- a/mod/quiz/tests/lib_test.php +++ b/mod/quiz/tests/lib_test.php @@ -31,12 +31,15 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/quiz/lib.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); /** * @copyright 2008 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ class lib_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + public function test_quiz_has_grades() { $quiz = new \stdClass(); $quiz->grade = '100.0000'; @@ -109,7 +112,7 @@ class lib_test extends \advanced_testcase { $standardq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); quiz_add_quiz_question($standardq->id, $quiz); - quiz_add_random_questions($quiz, 0, $cat->id, 1, false); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); // Get the random question. $randomq = $DB->get_record('question', ['qtype' => 'random']); diff --git a/mod/quiz/tests/local_structure_slot_random_test.php b/mod/quiz/tests/local_structure_slot_random_test.php index 6c4c11eecac..3a29b7e5757 100644 --- a/mod/quiz/tests/local_structure_slot_random_test.php +++ b/mod/quiz/tests/local_structure_slot_random_test.php @@ -16,8 +16,14 @@ namespace mod_quiz; +defined('MOODLE_INTERNAL') || die(); + use mod_quiz\question\bank\qbank_helper; +global $CFG; +require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); + /** * Class mod_quiz_local_structure_slot_random_test * Class for tests related to the {@link \mod_quiz\local\structure\slot_random} class. @@ -26,8 +32,12 @@ use mod_quiz\question\bank\qbank_helper; * @category test * @copyright 2018 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \mod_quiz\local\structure\slot_random */ class local_structure_slot_random_test extends \advanced_testcase { + + use \quiz_question_helper_test_trait; + /** * Constructor test. */ @@ -57,8 +67,7 @@ class local_structure_slot_random_test extends \advanced_testcase { // Set the filter conditions. $filtercondition = new \stdClass(); - $filtercondition->questioncategoryid = $category->id; - $filtercondition->includingsubcategories = 1; + $filtercondition->filters = \question_filter_test_helper::create_filters([$category->id], true); // Slot data. $randomslotdata = new \stdClass(); @@ -69,7 +78,7 @@ class local_structure_slot_random_test extends \advanced_testcase { // Insert the random question to the quiz. $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata); - $randomslot->set_filter_condition($filtercondition); + $randomslot->set_filter_condition(json_encode($filtercondition)); $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); $rcp = $rc->getProperty('filtercondition'); @@ -77,8 +86,8 @@ class local_structure_slot_random_test extends \advanced_testcase { $record = json_decode($rcp->getValue($randomslot)); $this->assertEquals($quiz->id, $randomslot->get_quiz()->id); - $this->assertEquals($category->id, $record->questioncategoryid); - $this->assertEquals(1, $record->includingsubcategories); + $this->assertEquals($category->id, $record->filters->category->values[0]); + $this->assertTrue($record->filters->category->filteroptions->includesubcategories); $rcp = $rc->getProperty('record'); $rcp->setAccessible(true); @@ -100,12 +109,11 @@ class local_structure_slot_random_test extends \advanced_testcase { $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); $category = $questiongenerator->create_question_category(); - quiz_add_random_questions($quiz, 0, $category->id, 1, false); + $this->add_random_questions($quiz->id, 0, $category->id, 1); // Set the filter conditions. $filtercondition = new \stdClass(); - $filtercondition->questioncategoryid = $category->id; - $filtercondition->includingsubcategories = 1; + $filtercondition->filters = \question_filter_test_helper::create_filters([$category->id], 1); // Slot data. $randomslotdata = new \stdClass(); @@ -115,7 +123,7 @@ class local_structure_slot_random_test extends \advanced_testcase { $randomslotdata->questionscontextid = $category->contextid; $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata); - $randomslot->set_filter_condition($filtercondition); + $randomslot->set_filter_condition(json_encode($filtercondition)); // The create_instance had injected an additional cmid propery to the quiz. Let's remove that. unset($quiz->cmid); @@ -137,12 +145,11 @@ class local_structure_slot_random_test extends \advanced_testcase { $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); $category = $questiongenerator->create_question_category(); - quiz_add_random_questions($quiz, 0, $category->id, 1, false); + $this->add_random_questions($quiz->id, 0, $category->id, 1); // Set the filter conditions. $filtercondition = new \stdClass(); - $filtercondition->questioncategoryid = $category->id; - $filtercondition->includingsubcategories = 1; + $filtercondition->filters = \question_filter_test_helper::create_filters([$category->id], 1); // Slot data. $randomslotdata = new \stdClass(); @@ -152,7 +159,7 @@ class local_structure_slot_random_test extends \advanced_testcase { $randomslotdata->questionscontextid = $category->contextid; $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata); - $randomslot->set_filter_condition($filtercondition); + $randomslot->set_filter_condition(json_encode($filtercondition)); // The create_instance had injected an additional cmid propery to the quiz. Let's remove that. unset($quiz->cmid); @@ -178,7 +185,7 @@ class local_structure_slot_random_test extends \advanced_testcase { $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); $category = $questiongenerator->create_question_category(); - quiz_add_random_questions($quiz, 0, $category->id, 1, false); + $this->add_random_questions($quiz->id, 0, $category->id, 1); // Slot data. $randomslotdata = new \stdClass(); @@ -203,147 +210,24 @@ class local_structure_slot_random_test extends \advanced_testcase { return [$randomslot, $tags]; } - public function test_set_tags() { + public function test_set_tags_filter() { $this->resetAfterTest(); $this->setAdminUser(); list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar']); + + $qtagids = [$tags['foo']->id, $tags['bar']->id]; $filtercondition = new \stdClass(); - $randomslot->set_tags([$tags['foo'], $tags['bar']]); - $randomslot->set_filter_condition($filtercondition); + $filtercondition->filters = \question_filter_test_helper::create_filters([], 0, $qtagids); + $randomslot->set_filter_condition(json_encode($filtercondition)); $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); $rcp = $rc->getProperty('filtercondition'); $rcp->setAccessible(true); $tagspropery = $rcp->getValue($randomslot); - $this->assertEquals([ - $tags['foo']->id => $tags['foo'], - $tags['bar']->id => $tags['bar'], - ], (array)json_decode($tagspropery)->tags); - } - - public function test_set_tags_twice() { - $this->resetAfterTest(); - $this->setAdminUser(); - - list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']); - - // Set tags for the first time. - $filtercondition = new \stdClass(); - $randomslot->set_tags([$tags['foo'], $tags['bar']]); - // Now set the tags again. - $randomslot->set_tags([$tags['baz']]); - $randomslot->set_filter_condition($filtercondition); - - $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); - $rcp = $rc->getProperty('filtercondition'); - $rcp->setAccessible(true); - $tagspropery = $rcp->getValue($randomslot); - - $this->assertEquals([ - $tags['baz']->id => $tags['baz'], - ], (array)json_decode($tagspropery)->tags); - } - - public function test_set_tags_duplicates() { - $this->resetAfterTest(); - $this->setAdminUser(); - - list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']); - $filtercondition = new \stdClass(); - $randomslot->set_tags([$tags['foo'], $tags['bar'], $tags['foo']]); - $randomslot->set_filter_condition($filtercondition); - - $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); - $rcp = $rc->getProperty('filtercondition'); - $rcp->setAccessible(true); - $tagspropery = $rcp->getValue($randomslot); - - $this->assertEquals([ - $tags['foo']->id => $tags['foo'], - $tags['bar']->id => $tags['bar'], - ], (array)json_decode($tagspropery)->tags); - } - - public function test_set_tags_by_id() { - $this->resetAfterTest(); - $this->setAdminUser(); - - list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']); - $filtercondition = new \stdClass(); - $randomslot->set_tags_by_id([$tags['foo']->id, $tags['bar']->id]); - $randomslot->set_filter_condition($filtercondition); - - $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); - $rcp = $rc->getProperty('tags'); - $rcp->setAccessible(true); - $tagspropery = $rcp->getValue($randomslot); - - // The set_tags_by_id function only retrieves id and name fields of the tag object. - $this->assertCount(2, $tagspropery); - $this->assertArrayHasKey($tags['foo']->id, $tagspropery); - $this->assertArrayHasKey($tags['bar']->id, $tagspropery); - $this->assertEquals( - (object)['id' => $tags['foo']->id, 'name' => $tags['foo']->name], - $tagspropery[$tags['foo']->id]->to_object() - ); - $this->assertEquals( - (object)['id' => $tags['bar']->id, 'name' => $tags['bar']->name], - $tagspropery[$tags['bar']->id]->to_object() - ); - } - - public function test_set_tags_by_id_twice() { - $this->resetAfterTest(); - $this->setAdminUser(); - - list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']); - - // Set tags for the first time. - $randomslot->set_tags_by_id([$tags['foo']->id, $tags['bar']->id]); - // Now set the tags again. - $randomslot->set_tags_by_id([$tags['baz']->id]); - - $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); - $rcp = $rc->getProperty('tags'); - $rcp->setAccessible(true); - $tagspropery = $rcp->getValue($randomslot); - - // The set_tags_by_id function only retrieves id and name fields of the tag object. - $this->assertCount(1, $tagspropery); - $this->assertArrayHasKey($tags['baz']->id, $tagspropery); - $this->assertEquals( - (object)['id' => $tags['baz']->id, 'name' => $tags['baz']->name], - $tagspropery[$tags['baz']->id]->to_object() - ); - } - - public function test_set_tags_by_id_duplicates() { - $this->resetAfterTest(); - $this->setAdminUser(); - - list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']); - - $randomslot->set_tags_by_id([$tags['foo']->id, $tags['bar']->id], $tags['foo']->id); - - $rc = new \ReflectionClass('\mod_quiz\local\structure\slot_random'); - $rcp = $rc->getProperty('tags'); - $rcp->setAccessible(true); - $tagspropery = $rcp->getValue($randomslot); - - // The set_tags_by_id function only retrieves id and name fields of the tag object. - $this->assertCount(2, $tagspropery); - $this->assertArrayHasKey($tags['foo']->id, $tagspropery); - $this->assertArrayHasKey($tags['bar']->id, $tagspropery); - $this->assertEquals( - (object)['id' => $tags['foo']->id, 'name' => $tags['foo']->name], - $tagspropery[$tags['foo']->id]->to_object() - ); - $this->assertEquals( - (object)['id' => $tags['bar']->id, 'name' => $tags['bar']->name], - $tagspropery[$tags['bar']->id]->to_object() - ); + $this->assertEquals([$tags['foo']->id, $tags['bar']->id], + (array)json_decode($tagspropery)->filters->qtagids->values); } public function test_insert() { @@ -389,8 +273,7 @@ class local_structure_slot_random_test extends \advanced_testcase { // Set the filter conditions. $filtercondition = new \stdClass(); - $filtercondition->questioncategoryid = $category->id; - $filtercondition->includingsubcategories = 1; + $filtercondition->filter = \question_filter_test_helper::create_filters([$category->id], true, [$footag->id, $bartag->id]); // Slot data. $randomslotdata = new \stdClass(); @@ -401,26 +284,26 @@ class local_structure_slot_random_test extends \advanced_testcase { // Insert the random question to the quiz. $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata); - $randomslot->set_tags([$footag, $bartag]); - $randomslot->set_filter_condition($filtercondition); + $randomslot->set_filter_condition(json_encode($filtercondition)); $randomslot->insert(1); // Put the question on the first page of the quiz. $slots = qbank_helper::get_question_structure($quiz->id, $quizcontext); $quizslot = reset($slots); - $this->assertEquals($category->id, $quizslot->category); - $this->assertEquals(1, $quizslot->randomrecurse); - $this->assertEquals(1, $quizslot->maxmark); - $tagspropery = $quizslot->randomtags; + $filter = $quizslot->filtercondition['filter']; - $this->assertCount(2, $tagspropery); + $this->assertEquals($category->id, $filter['category']['values'][0]); + $this->assertTrue($filter['category']['filteroptions']['includesubcategories']); + $this->assertEquals(1, $quizslot->maxmark); + + $this->assertCount(2, $filter['qtagids']['values']); $this->assertEqualsCanonicalizing( [ - ['tagid' => $footag->id, 'tagname' => $footag->name], - ['tagid' => $bartag->id, 'tagname' => $bartag->name] + ['tagid' => $footag->id], + ['tagid' => $bartag->id] ], - array_map(function($slottag) { - return ['tagid' => $slottag->id, 'tagname' => $slottag->name]; - }, $tagspropery)); + array_map(function($tagid) { + return ['tagid' => $tagid]; + }, $filter['qtagids']['values'])); } } diff --git a/mod/quiz/tests/locallib_test.php b/mod/quiz/tests/locallib_test.php index 83b368da141..9a79fab38d9 100644 --- a/mod/quiz/tests/locallib_test.php +++ b/mod/quiz/tests/locallib_test.php @@ -31,7 +31,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/mod/quiz/locallib.php'); - +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); /** * Unit tests for (some of) mod/quiz/locallib.php. @@ -41,6 +41,8 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php'); */ class locallib_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + public function test_quiz_rescale_grade() { $quiz = new \stdClass(); $quiz->decimalpoints = 2; @@ -523,7 +525,7 @@ class locallib_test extends \advanced_testcase { $tagids[] = $tagobjects[$tagname]->id; } } - quiz_add_random_questions($quiz, 0, $cat->id, 1, false, $tagids); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); } return [$quiz, $tagobjects]; diff --git a/mod/quiz/tests/quiz_question_bank_view_test.php b/mod/quiz/tests/quiz_question_bank_view_test.php index bcefd39deeb..dff013c9526 100644 --- a/mod/quiz/tests/quiz_question_bank_view_test.php +++ b/mod/quiz/tests/quiz_question_bank_view_test.php @@ -49,7 +49,8 @@ class quiz_question_bank_view_test extends \advanced_testcase { // Create a question in the default category. $contexts = new question_edit_contexts($context); - $cat = question_make_default_categories($contexts->all()); + question_make_default_categories($contexts->all()); + $cat = question_get_default_category($context->id); $questiondata = $questiongenerator->create_question('numerical', null, ['name' => 'Example question', 'category' => $cat->id]); @@ -58,17 +59,19 @@ class quiz_question_bank_view_test extends \advanced_testcase { $cache->delete($questiondata->id); // Generate the view. - $view = new custom_view($contexts, new \moodle_url('/'), $course, $cm, $quiz); - ob_start(); - $pagevars = [ + $params = [ 'qpage' => 0, 'qperpage' => 20, 'cat' => $cat->id . ',' . $context->id, 'recurse' => false, 'showhidden' => false, - 'qbshowtext' => false + 'qbshowtext' => false, + 'tabname' => 'editq' ]; - $view->display($pagevars, 'editq'); + $extraparams = ['cmid' => $cm->id]; + $view = new custom_view($contexts, new \moodle_url('/'), $course, $cm, $params, $extraparams); + ob_start(); + $view->display(); $html = ob_get_clean(); // Verify the output includes the expected question. diff --git a/mod/quiz/tests/quiz_question_helper_test_trait.php b/mod/quiz/tests/quiz_question_helper_test_trait.php index 174ab2adc31..198c41e9968 100644 --- a/mod/quiz/tests/quiz_question_helper_test_trait.php +++ b/mod/quiz/tests/quiz_question_helper_test_trait.php @@ -92,7 +92,7 @@ trait quiz_question_helper_test_trait { $cat = $questiongenerator->create_question_category($override); $questiongenerator->create_question('truefalse', null, ['category' => $cat->id]); $questiongenerator->create_question('essay', null, ['category' => $cat->id]); - quiz_add_random_questions($quiz, 0, $cat->id, 1, false); + $this->add_random_questions($quiz->id, 0, $cat->id, 1); } /** @@ -180,4 +180,28 @@ trait quiz_question_helper_test_trait { protected function duplicate_quiz($course, $quiz): ?\cm_info { return duplicate_module($course, get_fast_modinfo($course)->get_cm($quiz->cmid)); } + + /** + * Add random questions to a quiz, with a filter condition based on a category ID. + * + * @param int $quizid The quiz to add the questions to. + * @param int $page The page number to add the questions to. + * @param int $categoryid The category ID to use for the filter condition. + * @param int $number The number of questions to add. + * @return void + */ + protected function add_random_questions(int $quizid, int $page, int $categoryid, int $number): void { + $settings = quiz_settings::create($quizid); + $structure = \mod_quiz\structure::create_for_quiz($settings); + $filtercondition = [ + 'filter' => [ + 'category' => [ + 'jointype' => \qbank_managecategories\category_condition::JOINTYPE_DEFAULT, + 'values' => [$categoryid], + 'filteroptions' => ['includesubcategories' => false], + ], + ], + ]; + $structure->add_random_questions($page, $number, $filtercondition); + } } diff --git a/mod/quiz/tests/quiz_question_restore_test.php b/mod/quiz/tests/quiz_question_restore_test.php index afc77a713ce..1086016c217 100644 --- a/mod/quiz/tests/quiz_question_restore_test.php +++ b/mod/quiz/tests/quiz_question_restore_test.php @@ -470,7 +470,7 @@ class quiz_question_restore_test extends \advanced_testcase { quiz_add_quiz_question($saq->id, $quiz, 1, 3); quiz_add_quiz_question($numq->id, $quiz, 2, 2); quiz_add_quiz_question($matchq->id, $quiz, 3, 1); - quiz_add_random_questions($quiz, 3, $randomcat->id, 2, false); + $this->add_random_questions($quiz->id, 3, $randomcat->id, 2); $quizobj = quiz_settings::create($quiz->id, $user1->id); $originalstructure = \mod_quiz\structure::create_for_quiz($quizobj); diff --git a/mod/quiz/tests/structure_test.php b/mod/quiz/tests/structure_test.php index 759dbba4667..6e198469889 100644 --- a/mod/quiz/tests/structure_test.php +++ b/mod/quiz/tests/structure_test.php @@ -16,6 +16,11 @@ namespace mod_quiz; +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); + /** * Unit tests for quiz events. * @@ -26,6 +31,8 @@ namespace mod_quiz; */ class structure_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + /** * Create a course with an empty quiz. * @return array with three elements quiz, cm and course. @@ -691,7 +698,7 @@ class structure_test extends \advanced_testcase { $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); $cat = $questiongenerator->create_question_category(); - quiz_add_random_questions($quizobj->get_quiz(), 1, $cat->id, 1, false); + $this->add_random_questions($quizobj->get_quizid(), 1, $cat->id, 1); $structure = structure::create_for_quiz($quizobj); $sql = 'SELECT qsr.* FROM {question_set_references} qsr diff --git a/question/bank/managecategories/tests/helper_test.php b/question/bank/managecategories/tests/helper_test.php index 6dd8e861954..a5c746a046a 100644 --- a/question/bank/managecategories/tests/helper_test.php +++ b/question/bank/managecategories/tests/helper_test.php @@ -16,9 +16,14 @@ namespace qbank_managecategories; +defined('MOODLE_INTERNAL') || die(); + use moodle_url; use core_question\local\bank\question_edit_contexts; +global $CFG; +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); + /** * Unit tests for helper class. * @@ -30,6 +35,8 @@ use core_question\local\bank\question_edit_contexts; */ class helper_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + /** * @var \context_module module context. */ @@ -97,7 +104,7 @@ class helper_test extends \advanced_testcase { quiz_add_quiz_question($q2b->id, $this->quiz); // Adding a new random question does not add a new question, adds a question_set_references record. - quiz_add_random_questions($this->quiz, 0, $qcat2->id, 1, false); + $this->add_random_questions($this->quiz->id, 0, $qcat2->id, 1); // We added one random question to the quiz and we expect the quiz to have only one random question. $q2d = $DB->get_record_sql("SELECT qsr.* diff --git a/question/bank/statistics/tests/helper_test.php b/question/bank/statistics/tests/helper_test.php index 671c896d5e7..8b77b3f2cb7 100644 --- a/question/bank/statistics/tests/helper_test.php +++ b/question/bank/statistics/tests/helper_test.php @@ -16,11 +16,16 @@ namespace qbank_statistics; +defined('MOODLE_INTERNAL') || die(); + use core_question\statistics\questions\all_calculated_for_qubaid_condition; use mod_quiz\quiz_attempt; use mod_quiz\quiz_settings; use question_engine; +global $CFG; +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); + /** * Tests for question statistics. * @@ -31,6 +36,8 @@ use question_engine; */ class helper_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + /** * Test quizzes that contain a specified question. * @@ -100,7 +107,7 @@ class helper_test extends \advanced_testcase { $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q2places[0]); // Add a random question to quiz3. - quiz_add_random_questions($quiz3, 0, $cat->id, 1, false); + $this->add_random_questions($quiz3->id, 0, $cat->id, 1); $this->submit_quiz($quiz3, [1 => ['answer' => 'willbewrong']]); // Quiz 3 will now be in one of these arrays. diff --git a/question/bank/usage/tests/helper_test.php b/question/bank/usage/tests/helper_test.php index 77cef5fdd0f..efab696af7c 100644 --- a/question/bank/usage/tests/helper_test.php +++ b/question/bank/usage/tests/helper_test.php @@ -16,8 +16,13 @@ namespace qbank_usage; +defined('MOODLE_INTERNAL') || die(); + use mod_quiz\quiz_attempt; +global $CFG; +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); + /** * Helper test. * @@ -29,6 +34,8 @@ use mod_quiz\quiz_attempt; */ class helper_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + /** * @var \stdClass $quiz */ @@ -133,7 +140,7 @@ class helper_test extends \advanced_testcase { $this->setAdminUser(); $cat = $this->questiongenerator->create_question_category(); $question = $this->questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); - quiz_add_random_questions($this->quiz, 1, $cat->id, 1, false); + $this->add_random_questions($this->quiz->id, 1, $cat->id, 1); $qdef = \question_bank::load_question($question->id); $count = helper::get_question_entry_usage_count($qdef); @@ -226,7 +233,7 @@ class helper_test extends \advanced_testcase { $this->setAdminUser(); $cat = $this->questiongenerator->create_question_category(); $question = $this->questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); - quiz_add_random_questions($this->quiz, 1, $cat->id, 1, false); + $this->add_random_questions($this->quiz->id, 1, $cat->id, 1); $this->attempt_quiz(); diff --git a/question/editlib.php b/question/editlib.php index beb6f4809df..fec2787f91a 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -270,7 +270,10 @@ function question_build_edit_resources($edittab, $baseurl, $params, } if (!empty($params['filter'])) { - $cleanparams['filter'] = json_decode($params['filter'], true); + if (!is_array($params['filter'])) { + $params['filter'] = json_decode($params['filter'], true); + } + $cleanparams['filter'] = $params['filter']; } $cmid = $cleanparams['cmid']; diff --git a/question/tests/local/statistics/statistics_bulk_loader_test.php b/question/tests/local/statistics/statistics_bulk_loader_test.php index 2ce29c378b1..14c14321dba 100644 --- a/question/tests/local/statistics/statistics_bulk_loader_test.php +++ b/question/tests/local/statistics/statistics_bulk_loader_test.php @@ -16,6 +16,8 @@ namespace core_question\local\statistics; +defined('MOODLE_INTERNAL') || die(); + use advanced_testcase; use context; use context_module; @@ -27,6 +29,9 @@ use mod_quiz\quiz_settings; use question_engine; use ReflectionMethod; +global $CFG; +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); + /** * Tests for question statistics. * @@ -37,6 +42,8 @@ use ReflectionMethod; */ class statistics_bulk_loader_test extends advanced_testcase { + use \quiz_question_helper_test_trait; + /** @var float Delta used when comparing statistics values out-of 1. */ protected const DELTA = 0.00005; @@ -111,7 +118,7 @@ class statistics_bulk_loader_test extends advanced_testcase { $this->assertEquals((object) ['component' => 'mod_quiz', 'contextid' => $quiz2context->id], $q2places[0]); // Add a random question to quiz3. - quiz_add_random_questions($quiz3, 0, $cat->id, 1, false); + $this->add_random_questions($quiz3->id, 0, $cat->id, 1, false); $this->submit_quiz($quiz3, [1 => ['answer' => 'willbewrong']]); // Quiz 3 will now be in one of these arrays. diff --git a/question/tests/random_question_loader_test.php b/question/tests/random_question_loader_test.php index 1f9da267412..13cc9dec3aa 100644 --- a/question/tests/random_question_loader_test.php +++ b/question/tests/random_question_loader_test.php @@ -25,6 +25,7 @@ defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); +require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); /** * Tests for the {@see \core_question\local\bank\random_question_loader} class. @@ -35,6 +36,8 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php'); */ class random_question_loader_test extends \advanced_testcase { + use \quiz_question_helper_test_trait; + public function test_empty_category_gives_null() { $this->resetAfterTest(); $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); @@ -104,7 +107,7 @@ class random_question_loader_test extends \advanced_testcase { $cat = $generator->create_question_category(); $course = $this->getDataGenerator()->create_course(); $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course]); - quiz_add_random_questions($quiz, 1, $cat->id, 1); + $this->add_random_questions($quiz->id, 1, $cat->id, 1); $loader = new \core_question\local\bank\random_question_loader(new qubaid_list([])); $filters = question_filter_test_helper::create_filters([$cat->id]);