mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-72321 mod_quiz: Use new datafilters for random questions
This commit is contained in:
parent
823af3a8f1
commit
e3bd166b3d
@ -1,141 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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)("<input />").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)("<input />").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
|
File diff suppressed because one or more lines are too long
12
mod/quiz/amd/build/update_random_question_filter_condition.min.js
vendored
Normal file
12
mod/quiz/amd/build/update_random_question_filter_condition.min.js
vendored
Normal file
@ -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 <nathannguyen@catalyst-au.net>
|
||||
* @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
|
@ -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 <http://www.gnu.org/licenses/>.\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 <nathannguyen@catalyst-au.net>\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"}
|
@ -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 = $('<div class="modal-footer mt-1" data-region="footer"></div>');
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
63
mod/quiz/amd/src/update_random_question_filter_condition.js
Normal file
63
mod/quiz/amd/src/update_random_question_filter_condition.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* 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 <nathannguyen@catalyst-au.net>
|
||||
* @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);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
158
mod/quiz/classes/external/add_random_questions.php
vendored
Normal file
158
mod/quiz/classes/external/add_random_questions.php
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace 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 <nathannguyen@catalyst-au.net>
|
||||
* @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)
|
||||
]);
|
||||
}
|
||||
}
|
104
mod/quiz/classes/external/update_filter_condition.php
vendored
Normal file
104
mod/quiz/classes/external/update_filter_condition.php
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace 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 <nathannguyen@catalyst-au.net>
|
||||
* @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)
|
||||
]);
|
||||
}
|
||||
}
|
@ -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']) == '') {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 = [];
|
||||
|
@ -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'
|
||||
];
|
||||
|
||||
if (question_get_display_preference('qbshowtext', 0, PARAM_INT, new \moodle_url(''))) {
|
||||
$corequestionbankcolumns[] = 'question_text_row';
|
||||
}
|
||||
|
||||
foreach ($corequestionbankcolumns as $fullname) {
|
||||
/**
|
||||
* 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(''))) {
|
||||
$this->corequestionbankcolumns[] = 'question_text_row';
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 <safatshahin@catalyst-au.net>
|
||||
* @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);
|
||||
}
|
||||
return $PAGE->get_renderer('core_question', 'bank')->render_category_condition($displaydata);
|
||||
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 $values;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -14,12 +14,26 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* 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 <mark.johnson@catalyst-eu.net>
|
||||
* @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),
|
||||
];
|
||||
}
|
||||
}
|
48
mod/quiz/classes/question/bank/random_question_view.php
Normal file
48
mod/quiz/classes/question/bank/random_question_view.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace 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 <nathannguyen@catalyst-au.net>
|
||||
* @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 {
|
||||
}
|
||||
}
|
@ -570,12 +570,14 @@ class quiz_settings {
|
||||
continue;
|
||||
}
|
||||
if ($questiondata->qtype === 'random' && $includepotential) {
|
||||
if (!isset($qcategories[$questiondata->category])) {
|
||||
$qcategories[$questiondata->category] = false;
|
||||
$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'];
|
||||
}
|
||||
}
|
||||
if (!empty($questiondata->filtercondition)) {
|
||||
$filtercondition = json_decode($questiondata->filtercondition);
|
||||
$qcategories[$questiondata->category] = !empty($filtercondition->includingsubcategories);
|
||||
}
|
||||
} else {
|
||||
if (!in_array($questiondata->qtype, $questiontypes)) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
],
|
||||
];
|
||||
|
@ -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']);
|
||||
|
@ -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();
|
||||
|
@ -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})';
|
||||
|
153
mod/quiz/lib.php
153
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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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"
|
||||
|
90
mod/quiz/templates/add_random_question_form.mustache
Normal file
90
mod/quiz/templates/add_random_question_form.mustache
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@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": "<pre>Question bank HTML goes here</pre>"
|
||||
}
|
||||
}}
|
||||
<div class="mt-2 position-relative" data-region="add_random_question_form-container">
|
||||
<form action="{{returnurl}}" method="POST" id="add_random_question_form">
|
||||
<fieldset id="id_existingcategoryheader">
|
||||
<legend>{{#str}} randomfromexistingcategory, mod_quiz {{/str}}</legend>
|
||||
<!-- Question bank -->
|
||||
{{{questionbank}}}
|
||||
|
||||
<!-- Select number of random question -->
|
||||
<label>
|
||||
{{#str}}randomnumber, mod_quiz{{/str}}
|
||||
<select id="menurandomcount" name="randomcount" class="form-control custom-select">
|
||||
{{#randomoptions}}
|
||||
<option value="{{value}}">{{name}}</option>
|
||||
{{/randomoptions}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="modal-footer mt-1" data-region="footer">
|
||||
<input type="hidden" name="message" value="">
|
||||
<input type="submit" class="btn btn-primary" name="addrandom" value="{{#str}} addrandomquestion, mod_quiz {{/str}}">
|
||||
<input type="submit" class="btn btn-secondary" name="cancel" value="{{#str}} cancel {{/str}}">
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="id_newcategoryheader">
|
||||
<legend>{{#str}} randomquestionusinganewcategory, mod_quiz {{/str}}</legend>
|
||||
<!-- New categoryname -->
|
||||
<label class="mr-md-2 mb-md-0" for="categoryname">{{#str}} name {{/str}} </label>
|
||||
<input type="text" class="form-control" id="categoryname" name="categoryname" maxlength="254" size="50">
|
||||
|
||||
<!-- Select number of random question -->
|
||||
<label class="mr-md-2 mb-md-0" for="parentcategory">{{#str}} parentcategory, question {{/str}} </label>
|
||||
<select id="parentcategory" name="parentcategory" class="form-control">
|
||||
{{#questioncategoryoptions}}
|
||||
<optgroup label="{{label}}">
|
||||
{{#options}}
|
||||
<option value="{{value}}">{{{name}}}</option>
|
||||
{{/options}}
|
||||
</optgroup>
|
||||
{{/questioncategoryoptions}}
|
||||
</select>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="modal-footer mt-1" data-region="footer">
|
||||
<input type="submit" class="btn btn-primary" name="newcategory" value="{{#str}} createcategoryandaddrandomquestion, mod_quiz {{/str}}">
|
||||
<input type="submit" class="btn btn-secondary" name="cancel" value="{{#str}} cancel {{/str}}">
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
@ -15,26 +15,14 @@
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@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):
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="mt-2 position-relative" data-region="random-question-preview-container">
|
||||
<div data-region="question-count-container"></div>
|
||||
<div data-region="question-list-container"></div>
|
||||
{{> core/overlay_loading}}
|
||||
<div class="categoryquestionscontainer" id="questionscontainer">
|
||||
</div>
|
43
mod/quiz/templates/update_filter_condition_form.mustache
Normal file
43
mod/quiz/templates/update_filter_condition_form.mustache
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_quiz/update_filter_condition_form
|
||||
|
||||
From to add random questions.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"questionbank": "<pre>Question bank HTML goes here</pre>",
|
||||
"returnurl": "https://example.com",
|
||||
"cmid": "1234",
|
||||
"slotid": "5678"
|
||||
}
|
||||
}}
|
||||
<div>
|
||||
<!-- Question bank -->
|
||||
<div id="questionbank_container">
|
||||
{{{questionbank}}}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- submit buttons -->
|
||||
<form action="{{returnurl}}" method="POST" id="update_filter_condition_form" data-cmid="{{cmid}}" data-slotid="{{slotid}}">
|
||||
<input type="hidden" name="message" value="">
|
||||
<input type="submit" class="btn btn-primary" name="update" value="{{#str}} updatefilterconditon, mod_quiz {{/str}}">
|
||||
<input type="submit" class="btn btn-secondary" name="cancel" value="{{#str}} cancel {{/str}}">
|
||||
</form>
|
||||
</div>
|
@ -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']];
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
11
mod/quiz/tests/external/external_test.php
vendored
11
mod/quiz/tests/external/external_test.php
vendored
@ -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);
|
||||
|
@ -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']);
|
||||
|
@ -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 <shamim@moodle.com>
|
||||
* @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']));
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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.*
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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'];
|
||||
|
@ -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.
|
||||
|
@ -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]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user