diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index b8da2e822d1..dc01a60e4d8 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -1943,6 +1943,7 @@ class core_plugin_manager { 'editquestion', 'exportquestions', 'importquestions', + 'previewquestion', 'viewcreator', 'viewquestionname', 'viewquestiontext', diff --git a/lib/questionlib.php b/lib/questionlib.php index f9dcf74e3fc..73659ef3918 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -759,55 +759,34 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte * @param object $context context to run the preview in (affects things like * filter settings, theme, lang, etc.) Defaults to $PAGE->context. * @return moodle_url the URL. + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\helper::question_preview_url() + * @todo MDL-71679 uncomment the debug messages after implementing the changes in mod_quiz. + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_url($questionid, $preferredbehaviour = null, $maxmark = null, $displayoptions = null, $variant = null, $context = null) { + // Debugging message will be re-added after implementing the changes in mod_quiz. + // ...debugging('Function question_preview_url() has been deprecated and moved to qbank_previewquestion plugin, + // Please use qbank_previewquestion\helper::question_preview_url() instead.', DEBUG_DEVELOPER);. - $params = array('id' => $questionid); - - if (is_null($context)) { - global $PAGE; - $context = $PAGE->context; - } - if ($context->contextlevel == CONTEXT_MODULE) { - $params['cmid'] = $context->instanceid; - } else if ($context->contextlevel == CONTEXT_COURSE) { - $params['courseid'] = $context->instanceid; - } - - if (!is_null($preferredbehaviour)) { - $params['behaviour'] = $preferredbehaviour; - } - - if (!is_null($maxmark)) { - $params['maxmark'] = format_float($maxmark, -1); - } - - if (!is_null($displayoptions)) { - $params['correctness'] = $displayoptions->correctness; - $params['marks'] = $displayoptions->marks; - $params['markdp'] = $displayoptions->markdp; - $params['feedback'] = (bool) $displayoptions->feedback; - $params['generalfeedback'] = (bool) $displayoptions->generalfeedback; - $params['rightanswer'] = (bool) $displayoptions->rightanswer; - $params['history'] = (bool) $displayoptions->history; - } - - if ($variant) { - $params['variant'] = $variant; - } - - return new moodle_url('/question/preview.php', $params); + return \qbank_previewquestion\helper::question_preview_url($questionid, $preferredbehaviour = null, + $maxmark = null, $displayoptions = null, $variant = null, $context = null); } /** * @return array that can be passed as $params to the {@link popup_action} constructor. + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\helper::question_preview_popup_params() + * @todo MDL-71679 uncomment the debug messages after implementing the changes to mod_quiz. + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_popup_params() { - return array( - 'height' => 600, - 'width' => 800, - ); + // Debugging message will be re-added after implementing the changes in mod_quiz. + // ...debugging('Function question_preview_popup_params() has been deprecated and moved to qbank_previewquestion plugin, + // Please use qbank_previewquestion\helper::question_preview_popup_params() instead.', DEBUG_DEVELOPER);. + + return \qbank_previewquestion\helper::question_preview_popup_params(); } /** @@ -2255,8 +2234,7 @@ function question_pluginfile($course, $context, $component, $filearea, $args, $f } if ($module === 'core_question_preview') { - require_once($CFG->dirroot . '/question/previewlib.php'); - return question_preview_question_pluginfile($course, $context, + return qbank_previewquestion\helper::question_preview_question_pluginfile($course, $context, $component, $filearea, $qubaid, $slot, $args, $forcedownload, $options); } else { diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 22419ffe8c1..8d5e7e8b49f 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -69,6 +69,11 @@ information provided here is intended especially for developers. rendered. The default icon for "select" types has also changed to a dropdown caret ("t/expanded"). * The function message_send() in messagelib.php now returns false if there is an error sending the message to the message processor (MDL-70046). +* Following methods are now deprecated in questionlib and moved to the new qbank_previewquestion plugin: + - question_preview_url() is moved to qbank_previewquestion\helper::question_preview_url() + - question_preview_popup_params() is moved to \qbank_previewquestion\helper::question_preview_popup_params() + Calling these functions in the question will point to the plugin, but the deprecation message will be activated in MDL-72004. + The deprecated codes are removed from the questionlib for those two methods. === 3.11.2 === * For security reasons, filelib has been updated so all requests now use emulated redirects. diff --git a/question/bank/previewquestion/amd/build/preview.min.js b/question/bank/previewquestion/amd/build/preview.min.js new file mode 100644 index 00000000000..a1668b6ede1 --- /dev/null +++ b/question/bank/previewquestion/amd/build/preview.min.js @@ -0,0 +1,2 @@ +define ("qbank_previewquestion/preview",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);a.init=function init(a){if(!a){var b=document.getElementById("close-previewquestion-page");b.onclick=function(){window.close()}}c("responseform")};var c=function(a){var b=document.getElementById(a);if(b){d(b);e(b);f(".questionflagsavebutton",b);g(b)}},d=function(a){a.setAttribute("autocomplete","off")},e=function(a){a.addEventListener("submit",function(){(0,b.default)(this).submit(function(){return!1});return!0})},f=function(a,b){b.querySelectorAll(a).forEach(function(a){return a.remove()})},g=function(a){var b=window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/,"$1");if(b){window.scrollTo(0,b[1]);a.addEventListener("DOMContentLoaded",function(){window.scrollTo(0,b[1])})}}}); +//# sourceMappingURL=preview.min.js.map diff --git a/question/bank/previewquestion/amd/build/preview.min.js.map b/question/bank/previewquestion/amd/build/preview.min.js.map new file mode 100644 index 00000000000..4d8aced4c84 --- /dev/null +++ b/question/bank/previewquestion/amd/build/preview.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/preview.js"],"names":["init","redirect","closeButton","document","getElementById","onclick","window","close","setupQuestionForm","formElement","form","autocompleteOff","preventRepeatSubmission","removeClass","scrollToSavedPos","setAttribute","addEventListener","submit","classname","querySelectorAll","forEach","e","remove","matches","location","href","match","scrollTo"],"mappings":"uJAwBA,uD,OAQoB,QAAPA,CAAAA,IAAO,CAACC,CAAD,CAAc,CAC9B,GAAI,CAACA,CAAL,CAAe,CACX,GAAIC,CAAAA,CAAW,CAAGC,QAAQ,CAACC,cAAT,CAAwB,4BAAxB,CAAlB,CACAF,CAAW,CAACG,OAAZ,CAAsB,UAAM,CACxBC,MAAM,CAACC,KAAP,EACH,CACJ,CAEDC,CAAiB,CAAC,cAAD,CACpB,C,IAQKA,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAiB,CACvC,GAAIC,CAAAA,CAAI,CAAGP,QAAQ,CAACC,cAAT,CAAwBK,CAAxB,CAAX,CACA,GAAIC,CAAJ,CAAU,CAENC,CAAe,CAACD,CAAD,CAAf,CAEAE,CAAuB,CAACF,CAAD,CAAvB,CAEAG,CAAW,CAAC,yBAAD,CAA4BH,CAA5B,CAAX,CAEAI,CAAgB,CAACJ,CAAD,CACnB,CACJ,C,CAQKC,CAAe,CAAG,SAACD,CAAD,CAAU,CAC9BA,CAAI,CAACK,YAAL,CAAkB,cAAlB,CAAkC,KAAlC,CACH,C,CAQKH,CAAuB,CAAG,SAACF,CAAD,CAAU,CACtCA,CAAI,CAACM,gBAAL,CAAsB,QAAtB,CAAgC,UAAW,CACvC,cAAE,IAAF,EAAQC,MAAR,CAAe,UAAW,CACtB,QACH,CAFD,EAGA,QACH,CALD,CAMH,C,CASKJ,CAAW,CAAG,SAACK,CAAD,CAAYR,CAAZ,CAAqB,CACrCA,CAAI,CAACS,gBAAL,CAAsBD,CAAtB,EAAiCE,OAAjC,CAAyC,SAAAC,CAAC,QAAIA,CAAAA,CAAC,CAACC,MAAF,EAAJ,CAA1C,CACH,C,CASKR,CAAgB,CAAG,SAACJ,CAAD,CAAU,CAC/B,GAAIa,CAAAA,CAAO,CAAGjB,MAAM,CAACkB,QAAP,CAAgBC,IAAhB,CAAqBC,KAArB,CAA2B,oCAA3B,CAAiE,IAAjE,CAAd,CACA,GAAIH,CAAJ,CAAa,CAGTjB,MAAM,CAACqB,QAAP,CAAgB,CAAhB,CAAmBJ,CAAO,CAAC,CAAD,CAA1B,EACAb,CAAI,CAACM,gBAAL,CAAsB,kBAAtB,CAA0C,UAAM,CAC5CV,MAAM,CAACqB,QAAP,CAAgB,CAAhB,CAAmBJ,CAAO,CAAC,CAAD,CAA1B,CACH,CAFD,CAGH,CACJ,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for preview.\n *\n * @module qbank_preview/preview\n * @copyright 2021 Catalyst IT Australia Pty Ltd\n * @author Safat Shahin \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\n\n/**\n * Set up the actions.\n *\n * @method init\n * @param {bool} redirect Redirect.\n */\nexport const init = (redirect) => {\n if (!redirect) {\n let closeButton = document.getElementById('close-previewquestion-page');\n closeButton.onclick = () => {\n window.close();\n };\n }\n // Set up the form to be displayed.\n setupQuestionForm('responseform');\n};\n\n/**\n * Set up the form element to be displayed.\n *\n * @method setupQuestionForm\n * @param {string} formElement The form element.\n */\nconst setupQuestionForm = (formElement) => {\n let form = document.getElementById(formElement);\n if (form) {\n // Turning off browser autocomplete.\n autocompleteOff(form);\n // Stop a question form being submitted more than once.\n preventRepeatSubmission(form);\n // Removes any '.questionflagsavebutton's, since we have JavaScript to toggle.\n removeClass('.questionflagsavebutton', form);\n // Scroll to the position indicated by scrollpos= in the URL, if it is there.\n scrollToSavedPos(form);\n }\n};\n\n/**\n * Set the autocomplete off.\n *\n * @method autocompleteOff\n * @param {object} form The form element.\n */\nconst autocompleteOff = (form) => {\n form.setAttribute(\"autocomplete\", \"off\");\n};\n\n/**\n * Event handler to stop a question form being submitted more than once.\n *\n * @method preventRepeatSubmission\n * @param {object} form The form element.\n */\nconst preventRepeatSubmission = (form) => {\n form.addEventListener(\"submit\", function() {\n $(this).submit(function() {\n return false;\n });\n return true;\n });\n};\n\n/**\n * Removes a class inside an element.\n *\n * @method removeClass\n * @param {string} classname Class name.\n * @param {object} form The form element.\n */\nconst removeClass = (classname, form) => {\n form.querySelectorAll(classname).forEach(e => e.remove());\n};\n\n/**\n * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position.\n * (Note: Moodle 4.0 and above do NOT support Internet Explorer 11 and below.)\n *\n * @method scrollToSavedPos\n * @param {object} form The form element.\n */\nconst scrollToSavedPos = (form) => {\n let matches = window.location.href.match(/^.*[?&]scrollpos=(\\d*)(?:&|$|#).*$/, '$1');\n if (matches) {\n // DOMContentLoaded is the effective one here. I am leaving the immediate call to\n // window.scrollTo in case it reduces flicker.\n window.scrollTo(0, matches[1]);\n form.addEventListener(\"DOMContentLoaded\", () => {\n window.scrollTo(0, matches[1]);\n });\n }\n};\n"],"file":"preview.min.js"} \ No newline at end of file diff --git a/question/bank/previewquestion/amd/src/preview.js b/question/bank/previewquestion/amd/src/preview.js new file mode 100644 index 00000000000..bef98bef3c8 --- /dev/null +++ b/question/bank/previewquestion/amd/src/preview.js @@ -0,0 +1,117 @@ +// 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 . + +/** + * Javascript for preview. + * + * @module qbank_preview/preview + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import $ from 'jquery'; + +/** + * Set up the actions. + * + * @method init + * @param {bool} redirect Redirect. + */ +export const init = (redirect) => { + if (!redirect) { + let closeButton = document.getElementById('close-previewquestion-page'); + closeButton.onclick = () => { + window.close(); + }; + } + // Set up the form to be displayed. + setupQuestionForm('responseform'); +}; + +/** + * Set up the form element to be displayed. + * + * @method setupQuestionForm + * @param {string} formElement The form element. + */ +const setupQuestionForm = (formElement) => { + let form = document.getElementById(formElement); + if (form) { + // Turning off browser autocomplete. + autocompleteOff(form); + // Stop a question form being submitted more than once. + preventRepeatSubmission(form); + // Removes any '.questionflagsavebutton's, since we have JavaScript to toggle. + removeClass('.questionflagsavebutton', form); + // Scroll to the position indicated by scrollpos= in the URL, if it is there. + scrollToSavedPos(form); + } +}; + +/** + * Set the autocomplete off. + * + * @method autocompleteOff + * @param {object} form The form element. + */ +const autocompleteOff = (form) => { + form.setAttribute("autocomplete", "off"); +}; + +/** + * Event handler to stop a question form being submitted more than once. + * + * @method preventRepeatSubmission + * @param {object} form The form element. + */ +const preventRepeatSubmission = (form) => { + form.addEventListener("submit", function() { + $(this).submit(function() { + return false; + }); + return true; + }); +}; + +/** + * Removes a class inside an element. + * + * @method removeClass + * @param {string} classname Class name. + * @param {object} form The form element. + */ +const removeClass = (classname, form) => { + form.querySelectorAll(classname).forEach(e => e.remove()); +}; + +/** + * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position. + * (Note: Moodle 4.0 and above do NOT support Internet Explorer 11 and below.) + * + * @method scrollToSavedPos + * @param {object} form The form element. + */ +const scrollToSavedPos = (form) => { + let matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1'); + if (matches) { + // DOMContentLoaded is the effective one here. I am leaving the immediate call to + // window.scrollTo in case it reduces flicker. + window.scrollTo(0, matches[1]); + form.addEventListener("DOMContentLoaded", () => { + window.scrollTo(0, matches[1]); + }); + } +}; diff --git a/question/bank/previewquestion/classes/form/preview_options_form.php b/question/bank/previewquestion/classes/form/preview_options_form.php new file mode 100644 index 00000000000..40e24c6db40 --- /dev/null +++ b/question/bank/previewquestion/classes/form/preview_options_form.php @@ -0,0 +1,96 @@ +. + +namespace qbank_previewquestion\form; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/formslib.php'); + +use moodleform; +use question_display_options; +use question_engine; + +/** + * Settings form for the preview options. + * + * @package qbank_previewquestion + * @copyright 2009 The Open University + * @author 2021 Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preview_options_form extends moodleform { + + public function definition() { + $mform = $this->_form; + + $hiddenorvisible = [ + question_display_options::HIDDEN => get_string('notshown', 'question'), + question_display_options::VISIBLE => get_string('shown', 'question'), + ]; + + $mform->addElement('header', 'attemptoptionsheader', get_string('attemptoptions', 'question')); + + $behaviours = question_engine::get_behaviour_options( + $this->_customdata['quba']->get_preferred_behaviour()); + $mform->addElement('select', 'behaviour', + get_string('howquestionsbehave', 'question'), $behaviours); + $mform->addHelpButton('behaviour', 'howquestionsbehave', 'question'); + + $mform->addElement('float', 'maxmark', get_string('markedoutof', 'question'), ['size' => '5']); + + if ($this->_customdata['maxvariant'] > 1) { + $variants = range(1, $this->_customdata['maxvariant']); + $mform->addElement('select', 'variant', get_string('questionvariant', 'question'), + array_combine($variants, $variants)); + } + $mform->setType('variant', PARAM_INT); + + $mform->addElement('submit', 'saverestart', + get_string('restartwiththeseoptions', 'question')); + + $mform->addElement('header', 'displayoptionsheader', get_string('displayoptions', 'question')); + + $mform->addElement('select', 'correctness', get_string('whethercorrect', 'question'), + $hiddenorvisible); + + $marksoptions = [ + question_display_options::HIDDEN => get_string('notshown', 'question'), + question_display_options::MAX_ONLY => get_string('showmaxmarkonly', 'question'), + question_display_options::MARK_AND_MAX => get_string('showmarkandmax', 'question'), + ]; + $mform->addElement('select', 'marks', get_string('marks', 'question'), $marksoptions); + + $mform->addElement('select', 'markdp', get_string('decimalplacesingrades', 'question'), + question_engine::get_dp_options()); + + $mform->addElement('select', 'feedback', + get_string('specificfeedback', 'question'), $hiddenorvisible); + + $mform->addElement('select', 'generalfeedback', + get_string('generalfeedback', 'question'), $hiddenorvisible); + + $mform->addElement('select', 'rightanswer', + get_string('rightanswer', 'question'), $hiddenorvisible); + + $mform->addElement('select', 'history', + get_string('responsehistory', 'question'), $hiddenorvisible); + + $mform->addElement('submit', 'saveupdate', + get_string('updatedisplayoptions', 'question')); + } + +} diff --git a/question/bank/previewquestion/classes/helper.php b/question/bank/previewquestion/classes/helper.php new file mode 100644 index 00000000000..3e7e2e7631f --- /dev/null +++ b/question/bank/previewquestion/classes/helper.php @@ -0,0 +1,250 @@ +. + +namespace qbank_previewquestion; + +use context; +use moodle_url; +use question_display_options; +use question_engine; +use stdClass; + +/** + * Class helper contains all the helper functions. + * + * @package qbank_previewquestion + * @copyright 2010 The Open University + * @author 2021 Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class helper { + + /** + * Called via pluginfile.php -> question_pluginfile to serve files belonging to + * a question in a question_attempt when that attempt is a preview. + * + * @param stdClass $course course settings object + * @param stdClass $context context object + * @param string $component the name of the component we are serving files for. + * @param string $filearea the name of the file area. + * @param int $qubaid the question_usage this image belongs to. + * @param int $slot the relevant slot within the usage. + * @param array $args the remaining bits of the file path. + * @param bool $forcedownload whether the user must be forced to download the file. + * @param array $fileoptions + * @return void false if file not found, does not return if found - justsend the file + */ + public static function question_preview_question_pluginfile($course, $context, $component, + $filearea, $qubaid, $slot, $args, $forcedownload, $fileoptions): void { + global $USER, $DB, $CFG; + + list($context, $course, $cm) = get_context_info_array($context->id); + require_login($course, false, $cm); + + $quba = question_engine::load_questions_usage_by_activity($qubaid); + + if (!question_has_capability_on($quba->get_question($slot, false), 'use')) { + send_file_not_found(); + } + + $options = new question_display_options(); + $options->feedback = question_display_options::VISIBLE; + $options->numpartscorrect = question_display_options::VISIBLE; + $options->generalfeedback = question_display_options::VISIBLE; + $options->rightanswer = question_display_options::VISIBLE; + $options->manualcomment = question_display_options::VISIBLE; + $options->history = question_display_options::VISIBLE; + if (!$quba->check_file_access($slot, $options, $component, + $filearea, $args, $forcedownload)) { + send_file_not_found(); + } + + $fs = get_file_storage(); + $relativepath = implode('/', $args); + $fullpath = "/{$context->id}/{$component}/{$filearea}/{$relativepath}"; + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + send_stored_file($file, 0, 0, $forcedownload, $fileoptions); + } + + /** + * The the URL to use for actions relating to this preview. + * + * @param int $questionid the question being previewed. + * @param int $qubaid the id of the question usage for this preview. + * @param question_preview_options $options the options in use. + * @param context $context + * @param moodle_url $returnurl + * @return moodle_url + */ + public static function question_preview_action_url($questionid, $qubaid, + question_preview_options $options, $context, $returnurl = null): moodle_url { + $params = [ + 'id' => $questionid, + 'previewid' => $qubaid, + ]; + if ($context->contextlevel == CONTEXT_MODULE) { + $params['cmid'] = $context->instanceid; + } else if ($context->contextlevel == CONTEXT_COURSE) { + $params['courseid'] = $context->instanceid; + } + if ($returnurl !== null) { + $params['returnurl'] = $returnurl; + } + $params = array_merge($params, $options->get_url_params()); + return new moodle_url('/question/bank/previewquestion/preview.php', $params); + } + + /** + * The the URL to use for actions relating to this preview. + * @param int $questionid the question being previewed. + * @param context $context the current moodle context. + * @param int $previewid optional previewid to sign post saved previewed answers. + * @param moodle_url $returnurl + * @return moodle_url + */ + public static function question_preview_form_url($questionid, $context, $previewid = null, $returnurl = null): moodle_url { + $params = [ + 'id' => $questionid, + ]; + if ($context->contextlevel == CONTEXT_MODULE) { + $params['cmid'] = $context->instanceid; + } else if ($context->contextlevel == CONTEXT_COURSE) { + $params['courseid'] = $context->instanceid; + } + if ($previewid) { + $params['previewid'] = $previewid; + } + if ($returnurl !== null) { + $params['returnurl'] = $returnurl; + } + return new moodle_url('/question/bank/previewquestion/preview.php', $params); + } + + /** + * Delete the current preview, if any, and redirect to start a new preview. + * @param int $previewid + * @param int $questionid + * @param object $displayoptions + * @param object $context + * @param moodle_url $returnurl + */ + public static function restart_preview($previewid, $questionid, $displayoptions, $context, $returnurl = null): void { + global $DB; + + if ($previewid) { + $transaction = $DB->start_delegated_transaction(); + question_engine::delete_questions_usage_by_activity($previewid); + $transaction->allow_commit(); + } + redirect(self::question_preview_url($questionid, $displayoptions->behaviour, + $displayoptions->maxmark, $displayoptions, $displayoptions->variant, $context, $returnurl)); + } + + /** + * Generate the URL for starting a new preview of a given question with the given options. + * @param integer $questionid the question to preview. + * @param string $preferredbehaviour the behaviour to use for the preview. + * @param float $maxmark the maximum to mark the question out of. + * @param question_display_options $displayoptions the display options to use. + * @param int $variant the variant of the question to preview. If null, one will + * be picked randomly. + * @param object $context context to run the preview in (affects things like + * filter settings, theme, lang, etc.) Defaults to $PAGE->context. + * @param moodle_url $returnurl + * @return moodle_url the URL. + */ + public static function question_preview_url($questionid, $preferredbehaviour = null, + $maxmark = null, $displayoptions = null, $variant = null, $context = null, $returnurl = null): moodle_url { + + $params = ['id' => $questionid]; + + if (is_null($context)) { + global $PAGE; + $context = $PAGE->context; + } + if ($context->contextlevel == CONTEXT_MODULE) { + $params['cmid'] = $context->instanceid; + } else if ($context->contextlevel == CONTEXT_COURSE) { + $params['courseid'] = $context->instanceid; + } + + if (!is_null($preferredbehaviour)) { + $params['behaviour'] = $preferredbehaviour; + } + + if (!is_null($maxmark)) { + $params['maxmark'] = format_float($maxmark, -1); + } + + if (!is_null($displayoptions)) { + $params['correctness'] = $displayoptions->correctness; + $params['marks'] = $displayoptions->marks; + $params['markdp'] = $displayoptions->markdp; + $params['feedback'] = (bool) $displayoptions->feedback; + $params['generalfeedback'] = (bool) $displayoptions->generalfeedback; + $params['rightanswer'] = (bool) $displayoptions->rightanswer; + $params['history'] = (bool) $displayoptions->history; + } + + if (!is_null($returnurl)) { + $params['returnurl'] = $returnurl; + } + + if ($variant) { + $params['variant'] = $variant; + } + + return new moodle_url('/question/bank/previewquestion/preview.php', $params); + } + + /** + * Popup params for the question preview. + * @return array that can be passed as $params to the {@see popup_action} constructor. + */ + public static function question_preview_popup_params(): array { + return [ + 'height' => 600, + 'width' => 800, + ]; + } + + /** + * Get the extra elements for preview from qbank plugins. + * + * @param \question_definition $question + * @param int $courseid + * @return array + */ + public static function get_preview_extra_elements(\question_definition $question, int $courseid): array { + $plugintype = 'qbank'; + $functionname = 'preview_display'; + $extrahtml = []; + $comment = ''; + $plugins = get_plugin_list_with_function($plugintype, $functionname); + foreach ($plugins as $componentname => $plugin) { + $pluginhtml = component_callback($componentname, $functionname, [$question, $courseid]); + if ($componentname === 'qbank_comment') { + $comment = $pluginhtml; + continue; + } + $extrahtml[] = $pluginhtml; + } + return [$comment, $extrahtml]; + } +} diff --git a/question/bank/previewquestion/classes/output/renderer.php b/question/bank/previewquestion/classes/output/renderer.php new file mode 100644 index 00000000000..38af4566a39 --- /dev/null +++ b/question/bank/previewquestion/classes/output/renderer.php @@ -0,0 +1,68 @@ +. + +namespace qbank_previewquestion\output; + +use context; +use qbank_previewquestion\helper; + +/** + * Class renderer for rendering preview url + * + * @package qbank_previewquestion + * @copyright 2009 The Open University + * @author 2021 Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends \plugin_renderer_base { + + /** + * Render an icon, optionally with the word 'Preview' beside it, to preview a given question. + * + * @param int $questionid the id of the question to be previewed. + * @param context $context the context in which the preview is happening. + * Must be a course or category context. + * @param bool $showlabel if true, show the word 'Preview' after the icon. + * If false, just show the icon. + */ + public function question_preview_link($questionid, context $context, $showlabel) { + if ($showlabel) { + $alt = ''; + $label = get_string('preview'); + $attributes = []; + } else { + $alt = get_string('preview'); + $label = ''; + $attributes = ['title' => $alt]; + } + + $image = $this->pix_icon('t/preview', $alt, '', ['class' => 'iconsmall']); + $link = helper::question_preview_url($questionid, null, null, null, null, $context); + $action = new \popup_action('click', $link, 'questionpreview', helper::question_preview_popup_params()); + + return $this->action_link($link, $image . $label, $action, $attributes); + } + + /** + * Render the preview page. + * + * @param array $previewdata + */ + public function render_preview_page($previewdata) { + return $this->render_from_template('qbank_previewquestion/preview_question', $previewdata); + } + +} diff --git a/question/bank/previewquestion/classes/plugin_feature.php b/question/bank/previewquestion/classes/plugin_feature.php new file mode 100644 index 00000000000..1414dc49046 --- /dev/null +++ b/question/bank/previewquestion/classes/plugin_feature.php @@ -0,0 +1,34 @@ +. + +namespace qbank_previewquestion; + +/** + * Class columns is the entrypoint for the columns. + * + * @package qbank_previewquestion + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class plugin_feature extends \core_question\local\bank\plugin_features_base{ + + public function get_question_columns($qbank): array { + return [ + new preview_action_column($qbank) + ]; + } +} diff --git a/question/bank/previewquestion/classes/preview_action_column.php b/question/bank/previewquestion/classes/preview_action_column.php new file mode 100644 index 00000000000..b191fe3de71 --- /dev/null +++ b/question/bank/previewquestion/classes/preview_action_column.php @@ -0,0 +1,62 @@ +. + +namespace qbank_previewquestion; + +use core_question\local\bank\menu_action_column_base; + +/** + * Question bank columns for the preview action icon. + * + * @package qbank_previewquestion + * @copyright 2009 Tim Hunt + * @author 2021 Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class preview_action_column extends menu_action_column_base { + + /** + * @var string store this lang string for performance. + */ + protected $strpreview; + + public function init(): void { + parent::init(); + $this->strpreview = get_string('preview'); + } + + public function get_name(): string { + return 'previewaction'; + } + + protected function get_url_icon_and_label(\stdClass $question): array { + if (!\question_bank::is_qtype_installed($question->qtype)) { + // It sometimes happens that people end up with junk questions + // in their question bank of a type that is no longer installed. + // We cannot do most actions on them, because that leads to errors. + return [null, null, null]; + } + + if (question_has_capability_on($question, 'use')) { + $context = $this->qbank->get_most_specific_context(); + $url = helper::question_preview_url($question->id, null, null, + null, null, $context, $this->qbank->returnurl); + return [$url, 't/preview', $this->strpreview]; + } else { + return [null, null, null]; + } + } +} diff --git a/question/bank/previewquestion/classes/privacy/provider.php b/question/bank/previewquestion/classes/privacy/provider.php new file mode 100644 index 00000000000..9378360a7c8 --- /dev/null +++ b/question/bank/previewquestion/classes/privacy/provider.php @@ -0,0 +1,32 @@ +. + +namespace qbank_previewquestion\privacy; + +/** + * Privacy Subsystem for qbank_previewquestion implementing null_provider. + * + * @package qbank_previewquestion + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\null_provider { + + public static function get_reason(): string { + return 'privacy:metadata'; + } +} diff --git a/question/bank/previewquestion/classes/question_preview_options.php b/question/bank/previewquestion/classes/question_preview_options.php new file mode 100644 index 00000000000..77c9cd4e628 --- /dev/null +++ b/question/bank/previewquestion/classes/question_preview_options.php @@ -0,0 +1,141 @@ +. + +namespace qbank_previewquestion; + +use question_display_options; + +/** + * Displays question preview options as default and set the options. + * + * Setting default, getting and setting user preferences in question preview options. + * + * @package qbank_previewquestion + * @copyright 2010 The Open University + * @author 2021 Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class question_preview_options extends question_display_options { + + /** @var string the behaviour to use for this preview. */ + public $behaviour; + + /** @var number the maximum mark to use for this preview. */ + public $maxmark; + + /** @var int the variant of the question to preview. */ + public $variant; + + /** @var string prefix to append to field names to get user_preference names. */ + const OPTIONPREFIX = 'question_preview_options_'; + + /** + * Constructor. + * @param \stdClass $question + */ + public function __construct($question) { + $this->behaviour = 'deferredfeedback'; + $this->maxmark = $question->defaultmark; + $this->variant = null; + $this->correctness = self::VISIBLE; + $this->marks = self::MARK_AND_MAX; + $this->markdp = get_config('quiz', 'decimalpoints'); + $this->feedback = self::VISIBLE; + $this->numpartscorrect = $this->feedback; + $this->generalfeedback = self::VISIBLE; + $this->rightanswer = self::VISIBLE; + $this->history = self::HIDDEN; + $this->flags = self::HIDDEN; + $this->manualcomment = self::HIDDEN; + } + + /** + * Names of the options we store in the user preferences table. + * @return array + */ + protected function get_user_pref_fields(): array { + return ['behaviour', 'correctness', 'marks', 'markdp', 'feedback', 'generalfeedback', 'rightanswer', 'history']; + } + + /** + * Names and param types of the options we read from the request. + * @return array + */ + protected function get_field_types(): array { + return [ + 'behaviour' => PARAM_ALPHA, + 'maxmark' => PARAM_LOCALISEDFLOAT, + 'variant' => PARAM_INT, + 'correctness' => PARAM_BOOL, + 'marks' => PARAM_INT, + 'markdp' => PARAM_INT, + 'feedback' => PARAM_BOOL, + 'generalfeedback' => PARAM_BOOL, + 'rightanswer' => PARAM_BOOL, + 'history' => PARAM_BOOL, + ]; + } + + /** + * Load the value of the options from the user_preferences table. + */ + public function load_user_defaults(): void { + $defaults = get_config('question_preview'); + foreach ($this->get_user_pref_fields() as $field) { + $this->$field = get_user_preferences( + self::OPTIONPREFIX . $field, $defaults->$field); + } + $this->numpartscorrect = $this->feedback; + } + + /** + * Save a change to the user's preview options to the database. + * @param object $newoptions + */ + public function save_user_preview_options($newoptions): void { + foreach ($this->get_user_pref_fields() as $field) { + if (isset($newoptions->$field)) { + set_user_preference(self::OPTIONPREFIX . $field, $newoptions->$field); + } + } + } + + /** + * Set the value of any fields included in the request. + */ + public function set_from_request(): void { + foreach ($this->get_field_types() as $field => $type) { + $this->$field = optional_param($field, $this->$field, $type); + } + $this->numpartscorrect = $this->feedback; + } + + /** + * Parameters needed in the URL when continuing this preview. + * + * @return array URL fragment. + */ + public function get_url_params(): array { + $params = []; + foreach ($this->get_field_types() as $field => $notused) { + if ($field === 'behaviour' || $field === 'maxmark' || is_null($this->$field)) { + continue; + } + $params[$field] = $this->$field; + } + return $params; + } +} diff --git a/question/bank/previewquestion/lang/en/qbank_previewquestion.php b/question/bank/previewquestion/lang/en/qbank_previewquestion.php new file mode 100644 index 00000000000..b101c55e6e1 --- /dev/null +++ b/question/bank/previewquestion/lang/en/qbank_previewquestion.php @@ -0,0 +1,27 @@ +. + +/** + * Strings for component qbank_previewquestion, language 'en' + * + * @package qbank_previewquestion + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['pluginname'] = 'Preview question'; +$string['privacy:metadata'] = 'Preview question plugin does not store any user data.'; diff --git a/question/bank/previewquestion/preview.php b/question/bank/previewquestion/preview.php new file mode 100644 index 00000000000..4dab1afdc46 --- /dev/null +++ b/question/bank/previewquestion/preview.php @@ -0,0 +1,315 @@ +. + +/** + * This page displays a preview of a question + * + * The preview uses the option settings from the activity within which the question + * is previewed or the default settings if no activity is specified. The question session + * information is stored in the session as an array of subsequent states rather + * than in the database. + * + * @package qbank_previewquestion + * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} + * @author 2021 Safat Shahin and numerous contributors. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/questionlib.php'); + +use qbank_previewquestion\form\preview_options_form; +use qbank_previewquestion\question_preview_options; +use qbank_previewquestion\helper; + +/** + * The maximum number of variants previewable. If there are more variants than this for a question + * then we only allow the selection of the first x variants. + * + * @var integer + */ +define('QUESTION_PREVIEW_MAX_VARIANTS', 100); + +\core_question\local\bank\helper::require_plugin_enabled('qbank_previewquestion'); + +// Get and validate question id. +$id = required_param('id', PARAM_INT); +$returnurl = optional_param('returnurl', null, PARAM_RAW); +$question = question_bank::load_question($id); + +if ($returnurl) { + $returnurl = new moodle_url($returnurl); +} + +// Were we given a particular context to run the question in? +// This affects things like filter settings, or forced theme or language. +if ($cmid = optional_param('cmid', 0, PARAM_INT)) { + $cm = get_coursemodule_from_id(false, $cmid); + require_login($cm->course, false, $cm); + $context = context_module::instance($cmid); + +} else if ($courseid = optional_param('courseid', 0, PARAM_INT)) { + require_login($courseid); + $context = context_course::instance($courseid); + +} else { + require_login(); + $category = $DB->get_record('question_categories', ['id' => $question->category], '*', MUST_EXIST); + $context = context::instance_by_id($category->contextid); + $PAGE->set_context($context); + // Note that in the other cases, require_login will set the correct page context. +} +question_require_capability_on($question, 'use'); +$PAGE->set_pagelayout('popup'); + +// Get and validate display options. +$maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS); +$options = new question_preview_options($question); +$options->load_user_defaults(); +$options->set_from_request(); +$PAGE->set_url(helper::question_preview_url($id, $options->behaviour, $options->maxmark, + $options, $options->variant, $context)); + +// Get and validate existing preview, or start a new one. +$previewid = optional_param('previewid', 0, PARAM_INT); + +if ($previewid) { + try { + $quba = question_engine::load_questions_usage_by_activity($previewid); + + } catch (Exception $e) { + // This may not seem like the right error message to display, but + // actually from the user point of view, it makes sense. + throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', + helper::question_preview_url($question->id, $options->behaviour, + $options->maxmark, $options, $options->variant, $context), null, $e); + } + + if ($quba->get_owning_context()->instanceid != $USER->id) { + throw new moodle_exception('notyourpreview', 'question'); + } + + $slot = $quba->get_first_question_number(); + $usedquestion = $quba->get_question($slot, false); + if ($usedquestion->id != $question->id) { + throw new moodle_exception('questionidmismatch', 'question'); + } + $question = $usedquestion; + $options->variant = $quba->get_variant($slot); + +} else { + $quba = question_engine::make_questions_usage_by_activity( + 'core_question_preview', context_user::instance($USER->id)); + $quba->set_preferred_behaviour($options->behaviour); + $slot = $quba->add_question($question, $options->maxmark); + + if ($options->variant) { + $options->variant = min($maxvariant, max(1, $options->variant)); + } else { + $options->variant = rand(1, $maxvariant); + } + + $quba->start_question($slot, $options->variant); + + $transaction = $DB->start_delegated_transaction(); + question_engine::save_questions_usage_by_activity($quba); + $transaction->allow_commit(); +} +$options->behaviour = $quba->get_preferred_behaviour(); +$options->maxmark = $quba->get_question_max_mark($slot); + +// Create the settings form, and initialise the fields. +$optionsform = new preview_options_form(helper::question_preview_form_url($question->id, $context, $previewid, $returnurl), + ['quba' => $quba, 'maxvariant' => $maxvariant]); +$optionsform->set_data($options); + +// Process change of settings, if that was requested. +if ($newoptions = $optionsform->get_submitted_data()) { + // Set user preferences. + $options->save_user_preview_options($newoptions); + if (!isset($newoptions->variant)) { + $newoptions->variant = $options->variant; + } + if (isset($newoptions->saverestart)) { + helper::restart_preview($previewid, $question->id, $newoptions, $context, $returnurl); + } +} + +// Prepare a URL that is used in various places. +$actionurl = helper::question_preview_action_url($question->id, $quba->get_id(), $options, $context, $returnurl); + +// Process any actions from the buttons at the bottom of the form. +if (data_submitted() && confirm_sesskey()) { + + try { + + if (optional_param('restart', false, PARAM_BOOL)) { + helper::restart_preview($previewid, $question->id, $options, $context, $returnurl); + + } else if (optional_param('fill', null, PARAM_BOOL)) { + $correctresponse = $quba->get_correct_response($slot); + if (!is_null($correctresponse)) { + $quba->process_action($slot, $correctresponse); + + $transaction = $DB->start_delegated_transaction(); + question_engine::save_questions_usage_by_activity($quba); + $transaction->allow_commit(); + } + redirect($actionurl); + + } else if (optional_param('finish', null, PARAM_BOOL)) { + $quba->process_all_actions(); + $quba->finish_all_questions(); + + $transaction = $DB->start_delegated_transaction(); + question_engine::save_questions_usage_by_activity($quba); + $transaction->allow_commit(); + redirect($actionurl); + + } else { + $quba->process_all_actions(); + + $transaction = $DB->start_delegated_transaction(); + question_engine::save_questions_usage_by_activity($quba); + $transaction->allow_commit(); + + $scrollpos = optional_param('scrollpos', '', PARAM_RAW); + if ($scrollpos !== '') { + $actionurl->param('scrollpos', (int) $scrollpos); + } + redirect($actionurl); + } + + } catch (question_out_of_sequence_exception $e) { + throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', $actionurl); + + } catch (Exception $e) { + // This sucks, if we display our own custom error message, there is no way + // to display the original stack trace. + $debuginfo = ''; + if (!empty($e->debuginfo)) { + $debuginfo = $e->debuginfo; + } + throw new moodle_exception('errorprocessingresponses', 'question', $actionurl, + $e->getMessage(), $debuginfo); + } +} + +if ($question->length) { + $displaynumber = '1'; +} else { + $displaynumber = 'i'; +} +$restartdisabled = []; +$finishdisabled = []; +$filldisabled = []; +if ($quba->get_question_state($slot)->is_finished()) { + $finishdisabled = ['disabled' => 'disabled']; + $filldisabled = ['disabled' => 'disabled']; +} +// If question type cannot give us a correct response, disable this button. +if (is_null($quba->get_correct_response($slot))) { + $filldisabled = ['disabled' => 'disabled']; +} +if (!$previewid) { + $restartdisabled = ['disabled' => 'disabled']; +} + +// Prepare technical info to be output. +$qa = $quba->get_question_attempt($slot); +$technical = []; +$technical[] = get_string('behaviourbeingused', 'question', + question_engine::get_behaviour_name($qa->get_behaviour_name())); +$technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction()); +$technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction()); +$technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant()); +$technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary())); +$technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary())); +$technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary())); +$technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state()); + +// Start output. +$title = get_string('previewquestion', 'question', format_string($question->name)); +$headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot); +$PAGE->set_title($title); +$PAGE->set_heading($title); +echo $OUTPUT->header(); + +$previewdata = []; +$previewdata['actionurl'] = $actionurl; +$previewdata['session'] = sesskey(); +$previewdata['slot'] = $slot; +// Output of the question. +$previewdata['question'] = $quba->render_question($slot, $options, $displaynumber); +$previewdata['restartdisabled'] = html_writer::attributes($restartdisabled); +$previewdata['finishdisabled'] = html_writer::attributes($finishdisabled); +$previewdata['filldisabled'] = html_writer::attributes($filldisabled); +// Output the technical info. +$previewdata['techinfo'] = print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'), + 'core_question_preview_techinfo_collapsed', true, true, $OUTPUT->help_icon('technicalinfo', 'question')); +foreach ($technical as $info) { + $previewdata['techinfo'] .= html_writer::tag('p', $info, ['class' => 'notifytiny']); +} +$previewdata['techinfo'] .= print_collapsible_region_end(true); + +// Output a link to export this single question. +if (question_has_capability_on($question, 'view')) { + if (class_exists('qbank_exporttoxml\\exporttoxml_helper')) { + if (\core\plugininfo\qbank::is_plugin_enabled('qbank_exporttoxml')) { + $exportfunction = '\\qbank_exporttoxml\\exporttoxml_helper::question_get_export_single_question_url'; + $previewdata['exporttoxml'] = html_writer::link($exportfunction($question), + get_string('exportonequestion', 'question')); + } + } else { + $exportfunction = 'question_get_export_single_question_url'; + $previewdata['exporttoxml'] = html_writer::link($exportfunction($question), + get_string('exportonequestion', 'question')); + } +} + +// Display the settings form. +$previewdata['options'] = $optionsform->render(); + +list($comment, $extraelements) = helper::get_preview_extra_elements($question, $COURSE->id); + +if (!empty($comment)) { + $previewdata['comments'] = $comment; +} + +if (!empty($extraelements)) { + $elements = []; + foreach ($extraelements as $extraelement) { + $element = new stdClass(); + $element->extrapreviewelements = $extraelement; + $elements[] = $element; + } + $previewdata['extrapreviewelements'] = $elements; +} + +$previewdata['redirect'] = false; +if (!is_null($returnurl)) { + $previewdata['redirect'] = true; + $previewdata['redirecturl'] = $returnurl; +} + +echo $PAGE->get_renderer('qbank_previewquestion')->render_preview_page($previewdata); + +// Log the preview of this question. +$event = \core\event\question_viewed::create_from_question_instance($question, $context); +$event->trigger(); + +$PAGE->requires->js_call_amd('qbank_previewquestion/preview', 'init', [$previewdata['redirect']]); +echo $OUTPUT->footer(); diff --git a/question/bank/previewquestion/templates/preview_question.mustache b/question/bank/previewquestion/templates/preview_question.mustache new file mode 100644 index 00000000000..1f2bdf05de0 --- /dev/null +++ b/question/bank/previewquestion/templates/preview_question.mustache @@ -0,0 +1,89 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template qbank_previewquestion/preview_question + + The preview page for the question type preview. + * actionurl - Url to post to + * session - Moodle session + * slot - The identifying number of the first question that was added to this usage + * question - The html of the actual question from the engine + * restartdisabled - The attributes to enable or disable the button, same for finishdisabled and filldisabled + * techinfo - Technical information like fraction, state, behaviour etc + * exporttoxml - Link to export the question to xml + * redirecturl - Url to the base view + * comments - Comments api html + * extrapreviewelements - Any plugin implementing the callback and sending extra html to view something in the preview page + + Example context (json): + { + "previewdata": [ + { + "actionurl": "/", + "session": "E2PwCfrnzz", + "slot": "1", + "question": "
question html
", + "restartdisabled": "disabled='disabled'", + "finishdisabled": "disabled='disabled'", + "filldisabled": "disabled='disabled'", + "techinfo": "
Behaviour being used: Deferred feedback
", + "redirecturl": "/", + "exporttoxml": "Download this question in Moodle XML format", + "comments": "html from comments api", + "extrapreviewelements": "
callback to get html from plugins need to show info in preview
" + } + ] + } +}} +
+
+ + + +
+ {{{question}}} +
+ + + + + {{^redirect}} + + {{/redirect}} + {{#redirect}} + {{#str}} closepreview, question{{/str}} + {{/redirect}} +
+
+{{{techinfo}}} +{{{exporttoxml}}} +{{#comments}} +
+
+ {{{options}}} +
+
+ {{{comments}}} +
+
+{{/comments}} +{{^comments}} + {{{options}}} +{{/comments}} +{{#extrapreviewelements}} + {{{extrapreviewelements}}} +{{/extrapreviewelements}} diff --git a/question/tests/behat/preview_question.feature b/question/bank/previewquestion/tests/behat/preview_question.feature similarity index 93% rename from question/tests/behat/preview_question.feature rename to question/bank/previewquestion/tests/behat/preview_question.feature index e3630e34a39..9d1c6903c75 100644 --- a/question/tests/behat/preview_question.feature +++ b/question/bank/previewquestion/tests/behat/preview_question.feature @@ -1,4 +1,4 @@ -@core @core_question @javascript @_switch_window +@qbank @qbank_previewquestion @javascript Feature: A teacher can preview questions in the question bank In order to ensure the questions are properly created As a teacher @@ -11,6 +11,9 @@ Feature: A teacher can preview questions in the question bank And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | weeks | + And the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | @@ -21,10 +24,9 @@ Feature: A teacher can preview questions in the question bank | questioncategory | qtype | name | | Test questions | numerical | Test question to be previewed | And I log in as "teacher1" - And I am on "Course 1" course homepage + And I am on the "Test quiz" "quiz activity" page And I navigate to "Question bank > Questions" in current page administration When I choose "Preview" action for "Test question to be previewed" in the question bank - And I switch to "questionpreview" window Scenario: Question preview shows the question and other information Then the state of "What is pi to two d.p.?" question is shown as "Not yet answered" diff --git a/question/bank/previewquestion/tests/behat/preview_question_action.feature b/question/bank/previewquestion/tests/behat/preview_question_action.feature new file mode 100644 index 00000000000..c720b06a845 --- /dev/null +++ b/question/bank/previewquestion/tests/behat/preview_question_action.feature @@ -0,0 +1,50 @@ +@qbank @qbank_previewquestion +Feature: Use the qbank plugin manager page for previewquestion + In order to check the plugin behaviour with enable and disable + + Background: + Given the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "activities" exist: + | activity | name | course | idnumber | + | quiz | Test quiz | C1 | quiz1 | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "questions" exist: + | questioncategory | qtype | name | questiontext | + | Test questions | truefalse | First question | Answer the first question | + + @javascript + Scenario: Enable/disable previewquestion column from the base view + Given I log in as "admin" + When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I should see "Preview question" + And I click on "Disable" "link" in the "Preview question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "#action-menu-toggle-2" "css_element" in the "First question" "table_row" + Then I should not see "Preview" in the "region-main" "region" + And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I click on "Enable" "link" in the "Preview question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I click on "#action-menu-toggle-2" "css_element" in the "First question" "table_row" + And I should see "Preview" in the "region-main" "region" + + Scenario: Enable/disable preview button from question edit form + Given I log in as "admin" + When I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I should see "Preview question" + And I click on "Disable" "link" in the "Preview question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I choose "Edit question" action for "First question" in the question bank + Then I should not see "Preview" in the "region-main" "region" + And I navigate to "Plugins > Question bank plugins > Manage question bank plugins" in site administration + And I click on "Enable" "link" in the "Preview question" "table_row" + And I am on the "Test quiz" "quiz activity" page + And I navigate to "Question bank > Questions" in current page administration + And I choose "Edit question" action for "First question" in the question bank + And I should see "Preview" in the "region-main" "region" diff --git a/question/bank/previewquestion/tests/helper_test.php b/question/bank/previewquestion/tests/helper_test.php new file mode 100644 index 00000000000..33214dcc110 --- /dev/null +++ b/question/bank/previewquestion/tests/helper_test.php @@ -0,0 +1,159 @@ +. + +namespace qbank_previewquestion; + +use core\plugininfo\qbank; + +/** + * Helper tests for question preview. + * + * @package qbank_previewquestion + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \qbank_previewquestion\helper + */ +class helper_test extends \advanced_testcase { + + /** + * @var bool|\context|\context_course $context + */ + public $context; + + /** + * @var object $questiondata; + */ + public $questiondata; + + /** + * @var \question_usage_by_activity $quba + */ + public $quba; + + /** + * @var question_preview_options $options + */ + public $options; + + /** + * @var \moodle_url $returnurl + */ + public $returnurl; + + /** + * Test set up. + * + * This is executed before running any test in this file. + */ + public function setUp(): void { + global $USER; + $this->resetAfterTest(); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $questiongenerator = $generator->get_plugin_generator('core_question'); + // Create a course. + $course = $generator->create_course(); + $this->context = \context_course::instance($course->id); + // Create a question in the default category. + $contexts = new \question_edit_contexts($this->context); + $cat = question_make_default_categories($contexts->all()); + $this->questiondata = $questiongenerator->create_question('numerical', null, + ['name' => 'Example question', 'category' => $cat->id]); + $this->quba = \question_engine::make_questions_usage_by_activity('core_question_preview', \context_user::instance($USER->id)); + $this->options = new question_preview_options($this->questiondata); + $this->options->load_user_defaults(); + $this->options->set_from_request(); + $this->returnurl = new \moodle_url('/question/edit.php'); + } + + /** + * Test the preview action url from the helper class. + * + * @covers ::question_preview_action_url + */ + public function test_question_preview_action_url() { + $actionurl = helper::question_preview_action_url($this->questiondata->id, $this->quba->get_id(), $this->options, + $this->context, $this->returnurl); + $params = [ + 'id' => $this->questiondata->id, + 'previewid' => $this->quba->get_id(), + 'returnurl' => $this->returnurl, + 'courseid' => $this->context->instanceid + ]; + $params = array_merge($params, $this->options->get_url_params()); + $expectedurl = new \moodle_url('/question/bank/previewquestion/preview.php', $params); + $this->assertEquals($expectedurl, $actionurl); + } + + /** + * Test the preview form url from the helper class. + * + * @covers ::question_preview_form_url + */ + public function test_question_preview_form_url() { + $formurl = helper::question_preview_form_url($this->questiondata->id, $this->context, $this->quba->get_id(), $this->returnurl); + $params = [ + 'id' => $this->questiondata->id, + 'previewid' => $this->quba->get_id(), + 'returnurl' => $this->returnurl, + 'courseid' => $this->context->instanceid + ]; + $expectedurl = new \moodle_url('/question/bank/previewquestion/preview.php', $params); + $this->assertEquals($expectedurl, $formurl); + } + + /** + * Test the preview url from the helper class. + * + * @covers ::question_preview_url + */ + public function test_question_preview_url() { + $previewurl = helper::question_preview_url($this->questiondata->id, $this->options->behaviour, $this->options->maxmark, + $this->options, $this->options->variant, $this->context); + $params = [ + 'id' => $this->questiondata->id, + 'behaviour' => $this->options->behaviour, + 'maxmark' => $this->options->maxmark, + 'courseid' => $this->context->instanceid + ]; + // Extra params for options. + $params['correctness'] = $this->options->correctness; + $params['marks'] = $this->options->marks; + $params['markdp'] = $this->options->markdp; + $params['feedback'] = (bool) $this->options->feedback; + $params['generalfeedback'] = (bool) $this->options->generalfeedback; + $params['rightanswer'] = (bool) $this->options->rightanswer; + $params['history'] = (bool) $this->options->history; + $expectedurl = new \moodle_url('/question/bank/previewquestion/preview.php', $params); + $this->assertEquals($expectedurl, $previewurl); + } + + /** + * Test the preview comment callback if available. + * + * @covers ::get_preview_extra_elements + */ + public function test_get_preview_extra_elements() { + $question = \question_bank::load_question($this->questiondata->id); + list($comment, $extraelements) = helper::get_preview_extra_elements($question, $this->context->instanceid); + if (qbank::is_plugin_enabled('qbank_comment')) { + $this->assertEquals("
", $comment); + } else { + $this->assertEquals('', $comment); + } + } +} diff --git a/question/bank/previewquestion/version.php b/question/bank/previewquestion/version.php new file mode 100644 index 00000000000..26ff9eb295f --- /dev/null +++ b/question/bank/previewquestion/version.php @@ -0,0 +1,31 @@ +. + +/** + * Version information for qbank_previewquestion. + * + * @package qbank_previewquestion + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->component = 'qbank_previewquestion'; +$plugin->version = 2021072200; +$plugin->requires = 2021052500; +$plugin->maturity = MATURITY_STABLE; diff --git a/question/classes/local/bank/view.php b/question/classes/local/bank/view.php index 5865cacde30..859abe5df32 100644 --- a/question/classes/local/bank/view.php +++ b/question/classes/local/bank/view.php @@ -673,8 +673,13 @@ class view { * Get the URL to preview a question. * @param \stdClass $questiondata the data defining the question. * @return \moodle_url the URL. + * @deprecated since Moodle 4.0 + * @see \qbank_previewquestion\helper::question_preview_url() + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function preview_question_url($questiondata) { + debugging('Function preview_question_url() has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\helper::question_preview_url() instead.', DEBUG_DEVELOPER); return question_preview_url($questiondata->id, null, null, null, null, $this->get_most_specific_context()); } diff --git a/question/editlib.php b/question/editlib.php index 0d5fabab652..84d36949b73 100644 --- a/question/editlib.php +++ b/question/editlib.php @@ -242,7 +242,7 @@ class_alias('qbank_editquestion\copy_action_column', 'question_bank_copy_action_ * @deprecated since Moodle 2.7 MDL-40457 * @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins. */ -class_alias('core_question\bank\preview_action_column', 'question_bank_preview_action_column', true); +class_alias('qbank_previewquestion\preview_action_column', 'question_bank_preview_action_column', true); /** diff --git a/question/engine/renderer.php b/question/engine/renderer.php index 44009073c86..9c98fca4bce 100644 --- a/question/engine/renderer.php +++ b/question/engine/renderer.php @@ -48,24 +48,19 @@ class core_question_renderer extends plugin_renderer_base { * Must be a course or category context. * @param bool $showlabel if true, show the word 'Preview' after the icon. * If false, just show the icon. + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\output\renderer + * @todo MDL-71679 uncomment the debugging message after implementing the changes in mod_quiz + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ public function question_preview_link($questionid, context $context, $showlabel) { - if ($showlabel) { - $alt = ''; - $label = get_string('preview'); - $attributes = array(); - } else { - $alt = get_string('preview'); - $label = ''; - $attributes = array('title' => $alt); - } + // Debugging message will be re-added after implementing the changes in mod_quiz. + // ...debugging('Function question_preview_link() has been deprecated and moved to qbank_previewquestion plugin, + // Please use qbank_previewquestion renderer.', DEBUG_DEVELOPER);. - $image = $this->pix_icon('t/preview', $alt, '', array('class' => 'iconsmall')); - $link = question_preview_url($questionid, null, null, null, null, $context); - $action = new popup_action('click', $link, 'questionpreview', - question_preview_popup_params()); - - return $this->action_link($link, $image . $label, $action, $attributes); + return $this->page->get_renderer('qbank_previewquestion')->question_preview_link( + $questionid, $context, $showlabel + ); } /** diff --git a/question/preview.php b/question/preview.php index 899da99c711..924cbcf98a3 100644 --- a/question/preview.php +++ b/question/preview.php @@ -69,7 +69,7 @@ $PAGE->set_pagelayout('popup'); // Get and validate display options. $maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS); -$options = new question_preview_options($question); +$options = new qbank_previewquestion\question_preview_options($question); $options->load_user_defaults(); $options->set_from_request(); $PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, @@ -136,7 +136,7 @@ if ($newoptions = $optionsform->get_submitted_data()) { $newoptions->variant = $options->variant; } if (isset($newoptions->saverestart)) { - restart_preview($previewid, $question->id, $newoptions, $context); + qbank_previewquestion\helper::restart_preview($previewid, $question->id, $newoptions, $context); } } @@ -149,7 +149,7 @@ if (data_submitted() && confirm_sesskey()) { try { if (optional_param('restart', false, PARAM_BOOL)) { - restart_preview($previewid, $question->id, $options, $context); + qbank_previewquestion\helper::restart_preview($previewid, $question->id, $options, $context); } else if (optional_param('fill', null, PARAM_BOOL)) { $correctresponse = $quba->get_correct_response($slot); diff --git a/question/previewlib.php b/question/previewlib.php index 5c842a2d5bd..ae447ed57fd 100644 --- a/question/previewlib.php +++ b/question/previewlib.php @@ -17,7 +17,7 @@ /** * Library functions used by question/preview.php. * - * @package moodlecore + * @package core_question * @subpackage questionengine * @copyright 2010 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -34,9 +34,15 @@ require_once($CFG->libdir . '/formslib.php'); * * @copyright 2009 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\form\preview_options_form + * @todo MDL-71679 class renaming */ class preview_options_form extends moodleform { + public function definition() { + debugging('Class preview_options_form has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\form\preview_options_form instead.', DEBUG_DEVELOPER); $mform = $this->_form; $hiddenofvisible = array( @@ -99,11 +105,15 @@ class preview_options_form extends moodleform { /** - * Displays question preview options as default and set the options + * Displays question preview options as default and set the options. + * * Setting default, getting and setting user preferences in question preview options. * * @copyright 2010 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\output\question_preview_options + * @todo MDL-71679 class renaming */ class question_preview_options extends question_display_options { /** @var string the behaviour to use for this preview. */ @@ -120,8 +130,11 @@ class question_preview_options extends question_display_options { /** * Constructor. + * @param stdClass $question */ public function __construct($question) { + debugging('Class question_preview_options has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\question_preview_options instead.', DEBUG_DEVELOPER); $this->behaviour = 'deferredfeedback'; $this->maxmark = $question->defaultmark; $this->variant = null; @@ -228,42 +241,18 @@ class question_preview_options extends question_display_options { * @param int $slot the relevant slot within the usage. * @param array $args the remaining bits of the file path. * @param bool $forcedownload whether the user must be forced to download the file. - * @param array $options additional options affecting the file serving - * @return bool false if file not found, does not return if found - justsend the file + * @param array $fileoptions + * @return void false if file not found, does not return if found - justsend the file + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\helper::question_preview_question_pluginfile() + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_question_pluginfile($course, $context, $component, $filearea, $qubaid, $slot, $args, $forcedownload, $fileoptions) { - global $USER, $DB, $CFG; - - list($context, $course, $cm) = get_context_info_array($context->id); - require_login($course, false, $cm); - - $quba = question_engine::load_questions_usage_by_activity($qubaid); - - if (!question_has_capability_on($quba->get_question($slot, false), 'use')) { - send_file_not_found(); - } - - $options = new question_display_options(); - $options->feedback = question_display_options::VISIBLE; - $options->numpartscorrect = question_display_options::VISIBLE; - $options->generalfeedback = question_display_options::VISIBLE; - $options->rightanswer = question_display_options::VISIBLE; - $options->manualcomment = question_display_options::VISIBLE; - $options->history = question_display_options::VISIBLE; - if (!$quba->check_file_access($slot, $options, $component, - $filearea, $args, $forcedownload)) { - send_file_not_found(); - } - - $fs = get_file_storage(); - $relativepath = implode('/', $args); - $fullpath = "/{$context->id}/{$component}/{$filearea}/{$relativepath}"; - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - send_file_not_found(); - } - - send_stored_file($file, 0, 0, $forcedownload, $fileoptions); + debugging('Function question_preview_question_pluginfile() has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\helper::question_preview_question_pluginfile() instead.', DEBUG_DEVELOPER); + qbank_previewquestion\helper::question_preview_question_pluginfile($course, $context, + $component, $filearea, $qubaid, $slot, $args, $forcedownload, $fileoptions); } /** @@ -271,20 +260,16 @@ function question_preview_question_pluginfile($course, $context, $component, * @param int $questionid the question being previewed. * @param int $qubaid the id of the question usage for this preview. * @param question_preview_options $options the options in use. + * @param context $context + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\helper::question_preview_action_url() + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_action_url($questionid, $qubaid, question_preview_options $options, $context) { - $params = array( - 'id' => $questionid, - 'previewid' => $qubaid, - ); - if ($context->contextlevel == CONTEXT_MODULE) { - $params['cmid'] = $context->instanceid; - } else if ($context->contextlevel == CONTEXT_COURSE) { - $params['courseid'] = $context->instanceid; - } - $params = array_merge($params, $options->get_url_params()); - return new moodle_url('/question/preview.php', $params); + debugging('Function question_preview_action_url() has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\helper::question_preview_action_url() instead.', DEBUG_DEVELOPER); + qbank_previewquestion\helper::question_preview_action_url($questionid, $qubaid, $options, $context); } /** @@ -292,20 +277,14 @@ function question_preview_action_url($questionid, $qubaid, * @param int $questionid the question being previewed. * @param context $context the current moodle context. * @param int $previewid optional previewid to sign post saved previewed answers. + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\helper::question_preview_form_url() + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function question_preview_form_url($questionid, $context, $previewid = null) { - $params = array( - 'id' => $questionid, - ); - if ($context->contextlevel == CONTEXT_MODULE) { - $params['cmid'] = $context->instanceid; - } else if ($context->contextlevel == CONTEXT_COURSE) { - $params['courseid'] = $context->instanceid; - } - if ($previewid) { - $params['previewid'] = $previewid; - } - return new moodle_url('/question/preview.php', $params); + debugging('Function question_preview_form_url() has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\helper::question_preview_form_url() instead.', DEBUG_DEVELOPER); + qbank_previewquestion\helper::question_preview_form_url($questionid, $context, $previewid); } /** @@ -314,15 +293,12 @@ function question_preview_form_url($questionid, $context, $previewid = null) { * @param int $questionid * @param object $displayoptions * @param object $context + * @deprecated since Moodle 4.0 + * @see qbank_previewquestion\helper::restart_preview() + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ function restart_preview($previewid, $questionid, $displayoptions, $context) { - global $DB; - - if ($previewid) { - $transaction = $DB->start_delegated_transaction(); - question_engine::delete_questions_usage_by_activity($previewid); - $transaction->allow_commit(); - } - redirect(question_preview_url($questionid, $displayoptions->behaviour, - $displayoptions->maxmark, $displayoptions, $displayoptions->variant, $context)); + debugging('Function restart_preview() has been deprecated and moved to qbank_previewquestion plugin, + please use qbank_previewquestion\helper::restart_preview() instead.', DEBUG_DEVELOPER); + qbank_previewquestion\helper::restart_preview($previewid, $questionid, $displayoptions, $context); } diff --git a/question/qengine.js b/question/qengine.js index f8e0cafe930..6a89679e730 100644 --- a/question/qengine.js +++ b/question/qengine.js @@ -80,8 +80,14 @@ M.core_scroll_manager.save_scroll_action = function(e) { /** * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position. + * @deprecated since Moodle 4.0 + * @see question\bank\qbank_previewquestion\amd\src + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ M.core_scroll_manager.scroll_to_saved_pos = function(Y) { + Y.log("The scroll_to_saved_pos function has been deprecated. " + + "Please use scrollToSavedPos() in qbank_preview/preview.js instead.", 'moodle-core-notification', 'warn'); + var matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1'); if (matches) { // onDOMReady is the effective one here. I am leaving the immediate call to @@ -153,8 +159,14 @@ M.core_question_engine.init_submit_button = function(Y, button) { * 5. Prevent the user from repeatedly submitting the form. * @param Y the Yahoo object. Needs to have the DOM and Event modules loaded. * @param form something that can be passed to Y.one, to find the form element. + * @deprecated since Moodle 4.0 + * @see question\bank\qbank_previewquestion\amd\src + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ M.core_question_engine.init_form = function(Y, form) { + Y.log("The core_question_engine.init_form function has been deprecated. " + + "Please use setupQuestionForm() in qbank_preview/preview.js instead.", 'moodle-core-notification', 'warn'); + Y.one(form).setAttribute('autocomplete', 'off'); Y.on('submit', M.core_question_engine.prevent_repeat_submission, form, form, Y); @@ -175,8 +187,14 @@ M.core_question_engine.init_form = function(Y, form) { * Event handler to stop a question form being submitted more than once. * @param e the form submit event. * @param form the form element. + * @deprecated since Moodle 4.0 + * @see question\bank\qbank_previewquestion\amd\src + * @todo Final deprecation on Moodle 4.4 MDL-72438 */ M.core_question_engine.prevent_repeat_submission = function(e, Y) { + Y.log("The prevent_repeat_submission function has been deprecated. " + + "Please use preventRepeatSubmission in qbank_preview/preview.js instead.", 'moodle-core-notification', 'warn'); + if (M.core_question_engine.questionformalreadysubmitted) { e.halt(); return; diff --git a/question/type/ddimageortext/tests/behat/preview.feature b/question/type/ddimageortext/tests/behat/preview.feature index 4a286418840..cff7bfc8bf3 100644 --- a/question/type/ddimageortext/tests/behat/preview.feature +++ b/question/type/ddimageortext/tests/behat/preview.feature @@ -27,7 +27,6 @@ Feature: Preview a drag-drop onto image question @javascript @_bug_phantomjs Scenario: Preview a question using the mouse. When I choose "Preview" action for "Drag onto image" in the question bank - And I switch to "questionpreview" window # Increase window size and wait 2 seconds to ensure elements are placed properly by js. # Keep window large else drag will scroll the window to find element. And I change window size to "medium" @@ -44,15 +43,12 @@ Feature: Preview a drag-drop onto image question And I press "Submit and finish" Then the state of "Identify the features" question is shown as "Correct" And I should see "Mark 1.00 out of 1.00" - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a question using the keyboard. When I choose "Preview" action for "Drag onto image" in the question bank - And I switch to "questionpreview" window # Increase window size and wait 2 seconds to ensure elements are placed properly by js. - And I change window size to "medium" - And I wait "2" seconds And I type " " on place "1" in the drag and drop onto image question And I type " " on place "2" in the drag and drop onto image question And I type " " on place "3" in the drag and drop onto image question @@ -64,4 +60,4 @@ Feature: Preview a drag-drop onto image question And I press "Submit and finish" Then the state of "Identify the features" question is shown as "Correct" And I should see "Mark 1.00 out of 1.00" - And I switch to the main window + And I press "Close preview" diff --git a/question/type/ddmarker/tests/behat/preview.feature b/question/type/ddmarker/tests/behat/preview.feature index 2bf86b85e82..a57b440a291 100644 --- a/question/type/ddmarker/tests/behat/preview.feature +++ b/question/type/ddmarker/tests/behat/preview.feature @@ -27,11 +27,6 @@ Feature: Preview a drag-drop marker question @javascript @_bug_phantomjs Scenario: Preview a question using the mouse. When I choose "Preview" action for "Drag markers" in the question bank - And I switch to "questionpreview" window - # Increase window size and wait 2 seconds to ensure elements are placed properly by js. - # Keep window large else drag will scroll the window to find element. - And I change window size to "large" - And I wait "2" seconds # Odd, but the
s go to nothing, not a space. And I drag "OU" to "345,230" in the drag and drop markers question And I drag "Railway station" to "262,197" in the drag and drop markers question @@ -40,28 +35,21 @@ Feature: Preview a drag-drop marker question And I press "Submit and finish" Then the state of "Please place the markers on the map of Milton Keynes" question is shown as "Correct" And I should see "Mark 1.00 out of 1.00" - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a question using the keyboard. When I choose "Preview" action for "Drag markers" in the question bank - And I switch to "questionpreview" window - # Increase window size and wait 2 seconds to ensure elements are placed properly by js. - # Keep window large else drag will scroll the window to find element. - And I change window size to "medium" - And I wait "2" seconds And I type "up" "88" times on marker "Railway station" in the drag and drop markers question And I type "right" "26" times on marker "Railway station" in the drag and drop markers question And I press "Submit and finish" Then the state of "Please place the markers on the map of Milton Keynes" question is shown as "Partially correct" And I should see "Mark 0.25 out of 1.00" - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a question in responsive mode. When I choose "Preview" action for "Drag markers" in the question bank - And I switch to "questionpreview" window - And I change window size to "large" And I drag "OU" to "345,230" in the drag and drop markers question And I drag "Railway station" to "262,197" in the drag and drop markers question And I drag "Railway station" to "334,319" in the drag and drop markers question diff --git a/question/type/ddwtos/tests/behat/preview.feature b/question/type/ddwtos/tests/behat/preview.feature index 3f2bb01bbe4..46cd2ef6a02 100644 --- a/question/type/ddwtos/tests/behat/preview.feature +++ b/question/type/ddwtos/tests/behat/preview.feature @@ -28,42 +28,35 @@ Feature: Preview a drag-drop into text question @javascript @_bug_phantomjs Scenario: Preview a question using the mouse. When I choose "Preview" action for "Drag to text" in the question bank - And I switch to "questionpreview" window - # Increase window size and wait 2 seconds to ensure elements are placed properly by js. - # Keep window large else drag will scroll the window to find element. - And I change window size to "medium" - And I wait "2" seconds And I drag "quick" to space "1" in the drag and drop into text question And I drag "fox" to space "2" in the drag and drop into text question And I drag "assiduous" to space "3" in the drag and drop into text question And I press "Submit and finish" Then the state of "The" question is shown as "Partially correct" And I should see "Mark 0.67 out of 1.00" - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a question using the keyboard & submit incomplete. When I choose "Preview" action for "Drag to text" in the question bank - And I switch to "questionpreview" window And I type " " into space "1" in the drag and drop onto image question And I type " " into space "2" in the drag and drop onto image question And I type " " into space "3" in the drag and drop onto image question And I press "Save" Then the state of "The" question is shown as "Incomplete answer" And I should see "Please put an answer in each box." - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a question using the keyboard. When I choose "Preview" action for "Drag to text" in the question bank - And I switch to "questionpreview" window And I type " " into space "1" in the drag and drop onto image question And I type " " into space "2" in the drag and drop onto image question And I type " " into space "3" in the drag and drop onto image question And I press "Submit and finish" Then the state of "The" question is shown as "Incorrect" And I should see "Mark 0.00 out of 1.00" - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a question that uses strange group numbers using the keyboard. @@ -72,24 +65,19 @@ Feature: Preview a drag-drop into text question | Test questions | ddwtos | Funny groups | oddgroups | And I reload the page When I choose "Preview" action for "Funny groups" in the question bank - And I switch to "questionpreview" window And I type " " into space "1" in the drag and drop onto image question And I type " " into space "2" in the drag and drop onto image question And I type " " into space "3" in the drag and drop onto image question And I press "Submit and finish" Then the state of "The" question is shown as "Correct" And I should see "Mark 1.00 out of 1.00" - And I switch to the main window + And I press "Close preview" @javascript Scenario: Preview a infinite question. When I choose "Preview" action for "Drag to text infinite" in the question bank - And I switch to "questionpreview" window - # Increase window size. - # Keep window large else drag will scroll the window to find element. - And I change window size to "medium" And I press "Fill in correct responses" Then I should see "Option1" in the home area of drag and drop into text question And I should see "Option2" in the home area of drag and drop into text question And I should see "Option3" in the home area of drag and drop into text question - And I switch to the main window + And I press "Close preview" diff --git a/question/type/description/tests/behat/preview.feature b/question/type/description/tests/behat/preview.feature index 13b4660369c..e4698a1c10b 100644 --- a/question/type/description/tests/behat/preview.feature +++ b/question/type/description/tests/behat/preview.feature @@ -27,8 +27,7 @@ Feature: Preview a Description question @javascript @_switch_window Scenario: Preview a Description question and submit a correct response. When I choose "Preview" action for "description-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I should see "Here is some information about the questions you are about to attempt." - And I switch to the main window + And I press "Close preview" diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php index 384e689128b..c4d2969dfa7 100644 --- a/question/type/edit_question_form.php +++ b/question/type/edit_question_form.php @@ -256,17 +256,11 @@ abstract class question_edit_form extends question_wizard_form { $buttonarray[] = $mform->createElement('submit', 'updatebutton', get_string('savechangesandcontinueediting', 'question')); if ($this->can_preview()) { - // Todo MDL-72004 changes for class renaming and default sort. - if (class_exists('qbank_previewquestion\\preview_action_column')) { - if (\core\plugininfo\qbank::is_plugin_enabled('qbank_previewquestion')) { - $previewlink = $PAGE->get_renderer('qbank_previewquestion')->question_preview_link( - $this->question->id, $this->context, true); - } - } else { - $previewlink = $PAGE->get_renderer('core_question')->question_preview_link( + if (\core\plugininfo\qbank::is_plugin_enabled('qbank_previewquestion')) { + $previewlink = $PAGE->get_renderer('qbank_previewquestion')->question_preview_link( $this->question->id, $this->context, true); + $buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink); } - $buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink); } $mform->addGroup($buttonarray, 'updatebuttonar', '', array(' '), false); diff --git a/question/type/essay/tests/behat/max_file_size.feature b/question/type/essay/tests/behat/max_file_size.feature index 6f2f75b08da..584ec92db2a 100644 --- a/question/type/essay/tests/behat/max_file_size.feature +++ b/question/type/essay/tests/behat/max_file_size.feature @@ -27,15 +27,13 @@ I need to choose the appropriate maxbytes for attachments @javascript @_switch_window Scenario: Preview an Essay question and see the allowed maximum file sizes and number of attachments. When I choose "Preview" action for "essay-1-512KB" in the question bank - And I switch to "questionpreview" window Then I should see "Please write a story about a frog." And I should see "Maximum file size: 512KB, maximum number of files: 1" - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview an Essay question with Course upload limit and see the allowed maximum file size. When I choose "Preview" action for "essay-1-max" in the question bank - And I switch to "questionpreview" window Then I should see "Please write a story about a frog." And I should see "Maximum file size: 1MB, maximum number of files: 1" - And I switch to the main window + And I press "Close preview" diff --git a/question/type/essay/tests/behat/preview.feature b/question/type/essay/tests/behat/preview.feature index 142425c67f5..acd0272ffc2 100644 --- a/question/type/essay/tests/behat/preview.feature +++ b/question/type/essay/tests/behat/preview.feature @@ -29,27 +29,24 @@ Feature: Preview Essay questions @javascript @_switch_window Scenario: Preview an Essay question that uses the HTML editor. When I choose "Preview" action for "essay-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I should see "Please write a story about a frog." - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview an Essay question that uses the HTML editor with embedded files. When I choose "Preview" action for "essay-002" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I should see "Please write a story about a frog." And I should see "You can drag and drop files here to add them." - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview an Essay question that uses a plain text area. When I choose "Preview" action for "essay-003" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I should see "Please write a story about a frog." - And I switch to the main window + And I press "Close preview" diff --git a/question/type/gapselect/tests/behat/basic_test.feature b/question/type/gapselect/tests/behat/basic_test.feature index b2301ad35f0..6b6a4acacc5 100644 --- a/question/type/gapselect/tests/behat/basic_test.feature +++ b/question/type/gapselect/tests/behat/basic_test.feature @@ -39,7 +39,6 @@ Feature: Test all the basic functionality of this question type # Preview it. When I choose "Preview" action for "Select missing words 001" in the question bank - And I switch to "questionpreview" window # Gaps (drop-down menus) do not have labels. ids and names are generated # dynamically and therefore not reliable, i.e. this is an accessibility bug @@ -122,7 +121,7 @@ Feature: Test all the basic functionality of this question type Then I should see "Your answer is incorrect" And I should see "The cat sat on the mat" And I should see "The correct answer is: The [cat] [sat] on the [mat]." - And I switch to the main window + And I press "Close preview" # Backup the course and restore it. When I log out diff --git a/question/type/match/tests/behat/edit.feature b/question/type/match/tests/behat/edit.feature index 3f6e383d12c..0033ab46ae2 100644 --- a/question/type/match/tests/behat/edit.feature +++ b/question/type/match/tests/behat/edit.feature @@ -43,7 +43,6 @@ Feature: Test editing a Matching question And I press "id_submitbutton" Then I should see "Edited Matching name" When I choose "Preview" action for "Edited Matching name" in the question bank - And I switch to "questionpreview" window Then I should see "frog" And I should see "dog" And I should see "newt" diff --git a/question/type/match/tests/behat/preview.feature b/question/type/match/tests/behat/preview.feature index fc2b90c0d1f..9294014f0a4 100644 --- a/question/type/match/tests/behat/preview.feature +++ b/question/type/match/tests/behat/preview.feature @@ -31,7 +31,6 @@ Feature: Preview a Matching question | Shuffle | 0 | And I press "id_submitbutton" When I choose "Preview" action for "matching-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I set the field with xpath "//table[@class='answer']//td[@class='control']//select[contains(@id, '1_sub0')]" to "amphibian" @@ -40,7 +39,7 @@ Feature: Preview a Matching question And I press "Check" Then I should see "Well done!" And I should see "General feedback." - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview a Matching question and submit a partially correct response. @@ -49,7 +48,6 @@ Feature: Preview a Matching question | Shuffle | 0 | And I press "id_submitbutton" When I choose "Preview" action for "matching-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I set the field with xpath "//table[@class='answer']//td[@class='control']//select[contains(@id, '1_sub0')]" to "amphibian" @@ -58,7 +56,7 @@ Feature: Preview a Matching question And I press "Check" Then I should see "Parts, but only parts, of your response are correct." And I should see "General feedback." - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview a Matching question and submit an incorrect response. @@ -67,7 +65,6 @@ Feature: Preview a Matching question | Shuffle | 0 | And I press "id_submitbutton" When I choose "Preview" action for "matching-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I set the field with xpath "//table[@class='answer']//td[@class='control']//select[contains(@id, '1_sub0')]" to "mammal" @@ -76,4 +73,4 @@ Feature: Preview a Matching question And I press "Check" Then I should see "That is not right at all." And I should see "General feedback." - And I switch to the main window + And I press "Close preview" diff --git a/question/type/multichoice/tests/behat/preview.feature b/question/type/multichoice/tests/behat/preview.feature index 39e5a1775b5..f0d6d402e74 100644 --- a/question/type/multichoice/tests/behat/preview.feature +++ b/question/type/multichoice/tests/behat/preview.feature @@ -28,7 +28,6 @@ Feature: Preview a Multiple choice question @javascript @_switch_window Scenario: Preview a Multiple choice question and submit a partially correct response. When I choose "Preview" action for "Multi-choice-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I click on "One" "qtype_multichoice > Answer" @@ -38,12 +37,11 @@ Feature: Preview a Multiple choice question And I should see "Two is even" And I should see "Mark 0.50 out of 1.00" And I should see "Parts, but only parts, of your response are correct." - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview a Multiple choice question and submit a correct response. When I choose "Preview" action for "Multi-choice-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I click on "One" "qtype_multichoice > Answer" @@ -55,12 +53,11 @@ Feature: Preview a Multiple choice question And I should see "Well done!" And I should see "The odd numbers are One and Three." And I should see "The correct answers are: One, Three" - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview a Multiple choice question and submit a correct response. When I choose "Preview" action for "Multi-choice-002" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I click on "One" "qtype_multichoice > Answer" @@ -69,16 +66,15 @@ Feature: Preview a Multiple choice question And I should see "Mark 1.00 out of 1.00" And I should see "Well done!" And I should see "The correct answer is: One" - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview a multiple choice question (single response) and clear a previous selected option. When I choose "Preview" action for "Multi-choice-002" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I click on "One" "qtype_multichoice > Answer" Then I should see "Clear my choice" And I click on "Clear my choice" "text" And I should not see "Clear my choice" - And I switch to the main window + And I press "Close preview" diff --git a/question/type/numerical/tests/behat/preview.feature b/question/type/numerical/tests/behat/preview.feature index 747b9c360c2..78544fc7a65 100644 --- a/question/type/numerical/tests/behat/preview.feature +++ b/question/type/numerical/tests/behat/preview.feature @@ -31,7 +31,6 @@ Feature: Preview a Numerical question @javascript @_switch_window Scenario: Preview a Numerical question and submit a correct response. When I choose "Preview" action for "Numerical-001" in the question bank - And I switch to "questionpreview" window Then I should see "What is pi to two d.p.?" When I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" @@ -48,4 +47,4 @@ Feature: Preview a Numerical question And I press "Check" Then I should see "Very good." And I should see "Mark 1#00 out of 1#00" - And I switch to the main window + And I press "Close preview" diff --git a/question/type/shortanswer/tests/behat/edit.feature b/question/type/shortanswer/tests/behat/edit.feature index 996eb12003e..ccca1e979fa 100644 --- a/question/type/shortanswer/tests/behat/edit.feature +++ b/question/type/shortanswer/tests/behat/edit.feature @@ -43,7 +43,6 @@ Feature: Test editing a Short answer question And I press "id_submitbutton" Then I should see "Edited shortanswer-001 name" When I choose "Preview" action for "Edited shortanswer-001" in the question bank - And I switch to "questionpreview" window Then I should see "Name an amphibian:" # Set behaviour options And I set the following fields to these values: diff --git a/question/type/shortanswer/tests/behat/preview.feature b/question/type/shortanswer/tests/behat/preview.feature index 2772c38cfc5..b0e7d63ca1e 100644 --- a/question/type/shortanswer/tests/behat/preview.feature +++ b/question/type/shortanswer/tests/behat/preview.feature @@ -27,7 +27,6 @@ Feature: Preview a Short answer question @javascript @_switch_window Scenario: Preview a Short answer question with correct answer When I choose "Preview" action for "shortanswer-001" in the question bank - And I switch to "questionpreview" window Then I should see "Name an amphibian:" # Set behaviour options And I set the following fields to these values: @@ -42,7 +41,6 @@ Feature: Preview a Short answer question @javascript @_switch_window Scenario: Preview a Short answer question with almost correct answer When I choose "Preview" action for "shortanswer-001" in the question bank - And I switch to "questionpreview" window Then I should see "Name an amphibian:" # Set behaviour options And I set the following fields to these values: @@ -57,7 +55,6 @@ Feature: Preview a Short answer question @javascript @_switch_window Scenario: Preview a Short answer question with incorrect answer When I choose "Preview" action for "shortanswer-001" in the question bank - And I switch to "questionpreview" window Then I should see "Name an amphibian:" # Set behaviour options And I set the following fields to these values: diff --git a/question/type/truefalse/tests/behat/preview.feature b/question/type/truefalse/tests/behat/preview.feature index 83baede4749..b0300d6e4b3 100644 --- a/question/type/truefalse/tests/behat/preview.feature +++ b/question/type/truefalse/tests/behat/preview.feature @@ -27,19 +27,17 @@ Feature: Preview a Trtue/False question @javascript @_switch_window Scenario: Preview a True/False question and submit a correct response. When I choose "Preview" action for "true-false-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I click on "True" "radio" And I press "Check" And I should see "This is the right answer." And I should see "The correct answer is 'True'." - And I switch to the main window + And I press "Close preview" @javascript @_switch_window Scenario: Preview a True/False question and submit an incorrect response. When I choose "Preview" action for "true-false-001" in the question bank - And I switch to "questionpreview" window And I set the field "How questions behave" to "Immediate feedback" And I press "Start again with these options" And I click on "False" "radio" @@ -47,4 +45,4 @@ Feature: Preview a Trtue/False question And I should see "This is the wrong answer." And I should see "You should have selected true." And I should see "The correct answer is 'True'." - And I switch to the main window + And I press "Close preview" diff --git a/question/upgrade.txt b/question/upgrade.txt index f2b7fedbf61..56d076b6e17 100644 --- a/question/upgrade.txt +++ b/question/upgrade.txt @@ -6,6 +6,11 @@ This files describes API changes for code that uses the question API. are divided in two different parts, base classes and feature classes. All the base classes are moved classes/local/bank and all the feature classes will be moved to the plugin for that feature. +2) The following methods are deprecated in previewlib and moved to the previewquestion plugin: + restart_preview(), question_preview_form_url(), question_preview_action_url(), + question_preview_question_pluginfile() + The following classes are deprecated in previewlib and moves the previewquestion plugin: + question_preview_options, preview_options_form. === 3.9 == diff --git a/question/yui/build/moodle-question-preview/moodle-question-preview-debug.js b/question/yui/build/moodle-question-preview/moodle-question-preview-debug.js index d338ff09477..28e4a52e1f5 100644 --- a/question/yui/build/moodle-question-preview/moodle-question-preview-debug.js +++ b/question/yui/build/moodle-question-preview/moodle-question-preview-debug.js @@ -23,9 +23,14 @@ YUI.add('moodle-question-preview', function (Y, NAME) { /** * JavaScript required by the question preview pop-up. * + * @deprecated since Moodle 4.0 + * @todo Final deprecation on Moodle 4.4 MDL-72438 * @module moodle-question-preview */ +Y.log("The moodle-question-preview module has been deprecated. " + + "Please use moodle-qbank_previewquestion-preview instead.", 'moodle-core-notification', 'warn'); + M.question = M.question || {}; M.question.preview = M.question.preview || {}; diff --git a/question/yui/build/moodle-question-preview/moodle-question-preview.js b/question/yui/build/moodle-question-preview/moodle-question-preview.js index d338ff09477..797d83a9c35 100644 --- a/question/yui/build/moodle-question-preview/moodle-question-preview.js +++ b/question/yui/build/moodle-question-preview/moodle-question-preview.js @@ -23,9 +23,12 @@ YUI.add('moodle-question-preview', function (Y, NAME) { /** * JavaScript required by the question preview pop-up. * + * @deprecated since Moodle 4.0 + * @todo Final deprecation on Moodle 4.4 MDL-72438 * @module moodle-question-preview */ + M.question = M.question || {}; M.question.preview = M.question.preview || {}; diff --git a/question/yui/src/preview/js/preview.js b/question/yui/src/preview/js/preview.js index ebfd0f42c56..e1a49be5745 100644 --- a/question/yui/src/preview/js/preview.js +++ b/question/yui/src/preview/js/preview.js @@ -21,9 +21,14 @@ /** * JavaScript required by the question preview pop-up. * + * @deprecated since Moodle 4.0 + * @todo Final deprecation on Moodle 4.4 MDL-72438 * @module moodle-question-preview */ +Y.log("The moodle-question-preview module has been deprecated. " + + "Please use moodle-qbank_previewquestion-preview instead.", 'moodle-core-notification', 'warn'); + M.question = M.question || {}; M.question.preview = M.question.preview || {};