diff --git a/mod/quiz/classes/output/edit_renderer.php b/mod/quiz/classes/output/edit_renderer.php index 451c07b17ac..ce376dfb9ba 100644 --- a/mod/quiz/classes/output/edit_renderer.php +++ b/mod/quiz/classes/output/edit_renderer.php @@ -28,6 +28,7 @@ use core_question\local\bank\question_version_status; use mod_quiz\question\bank\qbank_helper; use \mod_quiz\structure; use \html_writer; +use qbank_previewquestion\question_preview_options; use renderable; /** @@ -838,11 +839,12 @@ class edit_renderer extends \plugin_renderer_base { public function get_action_icon(structure $structure, int $slot, \moodle_url $pageurl) : string { // Action icons. $qtype = $structure->get_question_type_for_slot($slot); + $slotinfo = $structure->get_slot_by_number($slot); $questionicons = ''; if ($qtype !== 'random') { $questionicons .= $this->question_preview_icon($structure->get_quiz(), $structure->get_question_in_slot($slot), - null, null, $qtype); + null, null, $slotinfo->requestedversion ?: question_preview_options::ALWAYS_LATEST); } if ($structure->can_be_edited() && $structure->has_use_capability($slot)) { $questionicons .= $this->question_remove_icon($structure, $slot, $pageurl); @@ -890,16 +892,17 @@ class edit_renderer extends \plugin_renderer_base { * If ->questionid is set, that is used instead of ->id. * @param bool $label if true, show the preview question label after the icon * @param int $variant which question variant to preview (optional). + * @param int $restartversion version to use when restarting the preview * @return string HTML to output. */ - public function question_preview_icon($quiz, $questiondata, $label = null, $variant = null) { + public function question_preview_icon($quiz, $questiondata, $label = null, $variant = null, $restartversion = null) { $question = clone($questiondata); if (isset($question->questionid)) { $question->id = $question->questionid; } - $url = quiz_question_preview_url($quiz, $question, $variant); + $url = quiz_question_preview_url($quiz, $question, $variant, $restartversion); // Do we want a label? $strpreviewlabel = ''; diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index faaa83f9351..5d227bfeaf2 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -1049,9 +1049,10 @@ function quiz_question_edit_button($cmid, $question, $returnurl, $contentafteric * @param stdClass $quiz the quiz settings * @param stdClass $question the question * @param int $variant which question variant to preview (optional). + * @param int $restartversion version of the question to use when restarting the preview. * @return moodle_url to preview this question with the options from this quiz. */ -function quiz_question_preview_url($quiz, $question, $variant = null) { +function quiz_question_preview_url($quiz, $question, $variant = null, $restartversion = null) { // Get the appropriate display options. $displayoptions = display_options::make_from_quiz($quiz, display_options::DURING); @@ -1063,7 +1064,7 @@ function quiz_question_preview_url($quiz, $question, $variant = null) { // Work out the correcte preview URL. return \qbank_previewquestion\helper::question_preview_url($question->id, $quiz->preferredbehaviour, - $maxmark, $displayoptions, $variant); + $maxmark, $displayoptions, $variant, null, null, $restartversion); } /** @@ -1079,7 +1080,10 @@ function quiz_question_preview_button($quiz, $question, $label = false, $variant if (!question_has_capability_on($question, 'use')) { return ''; } - return $PAGE->get_renderer('mod_quiz', 'edit')->question_preview_icon($quiz, $question, $label, $variant, null); + $slotinfo = quiz_settings::create($quiz->id)->get_structure()->get_slot_by_number($question->slot); + return $PAGE->get_renderer('mod_quiz', 'edit') + ->question_preview_icon($quiz, $question, $label, $variant, + $slotinfo->requestedversion ?: \qbank_previewquestion\question_preview_options::ALWAYS_LATEST); } /** diff --git a/mod/quiz/tests/behat/quiz_question_versions.feature b/mod/quiz/tests/behat/quiz_question_versions.feature index 356f5ec9473..d26065a120d 100644 --- a/mod/quiz/tests/behat/quiz_question_versions.feature +++ b/mod/quiz/tests/behat/quiz_question_versions.feature @@ -121,3 +121,19 @@ Feature: Quiz question versioning And I set the field "question_status_dropdown" in the "First question" "table_row" to "Draft" When I am on the "Quiz 1" "mod_quiz > Edit" page Then I should see "This question has all versions in Draft status. The quiz will not work with this question in place." + + @javascript + Scenario: Previewing a question set to use always latest version will set the preview to always latest version + When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher" + And the field "version" in the "First question" "list_item" matches value "Always latest" + When I follow "Preview question" + And I expand all fieldsets + Then the field "Question version" matches value "Always latest" + + @javascript + Scenario: Previewing a question set to use a specific version will set the preview to that version + When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher" + And I set the field "version" to "v1 (latest)" + When I follow "Preview question" + And I expand all fieldsets + Then the field "Question version" matches value "1" diff --git a/question/bank/history/classes/question_history_view.php b/question/bank/history/classes/question_history_view.php index 4d0d47c9cce..6d672de2c0d 100644 --- a/question/bank/history/classes/question_history_view.php +++ b/question/bank/history/classes/question_history_view.php @@ -196,4 +196,8 @@ class question_history_view extends view { echo $PAGE->get_renderer('qbank_history')->render_history_header($historydata); } + public function is_listing_specific_versions(): bool { + return true; + } + } diff --git a/question/bank/previewquestion/classes/form/preview_options_form.php b/question/bank/previewquestion/classes/form/preview_options_form.php index f4129e5d1ac..cd0fec65334 100644 --- a/question/bank/previewquestion/classes/form/preview_options_form.php +++ b/question/bank/previewquestion/classes/form/preview_options_form.php @@ -23,6 +23,7 @@ require_once($CFG->libdir . '/formslib.php'); use moodleform; use question_display_options; use question_engine; +use qbank_previewquestion\question_preview_options; /** * Settings form for the preview options. @@ -45,8 +46,9 @@ class preview_options_form extends moodleform { $mform->addElement('header', 'attemptoptionsheader', get_string('previewoptions', 'qbank_previewquestion')); $mform->setExpanded('attemptoptionsheader', false); $versions = $this->_customdata['versions']; - $currentversion = $this->_customdata['questionversion']; - $select = $mform->addElement('select', 'version', get_string('questionversion', 'qbank_previewquestion'), $versions); + $versions[question_preview_options::ALWAYS_LATEST] = get_string('alwayslatest', 'qbank_previewquestion'); + $currentversion = $this->_customdata['restartversion']; + $select = $mform->addElement('select', 'restartversion', get_string('questionversion', 'qbank_previewquestion'), $versions); $select->setSelected($currentversion); $behaviours = question_engine::get_behaviour_options( $this->_customdata['quba']->get_preferred_behaviour()); diff --git a/question/bank/previewquestion/classes/helper.php b/question/bank/previewquestion/classes/helper.php index f135a53ed68..84aa19efd85 100644 --- a/question/bank/previewquestion/classes/helper.php +++ b/question/bank/previewquestion/classes/helper.php @@ -103,10 +103,11 @@ class helper { * @param question_preview_options $options the options in use * @param context $context context for the question preview * @param moodle_url $returnurl url of the page to return to + * @param int|null $restartversion version of the question to use when next restarting the preview. * @return moodle_url */ public static function question_preview_action_url($questionid, $qubaid, - question_preview_options $options, $context, $returnurl = null): moodle_url { + question_preview_options $options, $context, $returnurl = null, $restartversion = null): moodle_url { $params = [ 'id' => $questionid, 'previewid' => $qubaid, @@ -119,6 +120,9 @@ class helper { if ($returnurl !== null) { $params['returnurl'] = $returnurl; } + if ($restartversion !== null) { + $params['restartversion'] = $restartversion; + } $params = array_merge($params, $options->get_url_params()); return new moodle_url('/question/bank/previewquestion/preview.php', $params); } @@ -158,10 +162,11 @@ class helper { * @param object $displayoptions display options for the question in preview * @param object $context context of the question for preview * @param moodle_url $returnurl url of the page to return to - * @param int|null $version version of the question in preview + * @param int|null $restartversion version of the question to use when next restarting the preview. + * @return void */ public static function restart_preview($previewid, $questionid, $displayoptions, $context, - $returnurl = null, $version = null): void { + $returnurl = null, $restartversion = null): void { global $DB; if ($previewid) { @@ -170,7 +175,8 @@ class helper { $transaction->allow_commit(); } redirect(self::question_preview_url($questionid, $displayoptions->behaviour, - $displayoptions->maxmark, $displayoptions, $displayoptions->variant, $context, $returnurl, $version)); + $displayoptions->maxmark, $displayoptions, $displayoptions->variant, + $context, $returnurl, $restartversion)); } /** @@ -185,17 +191,17 @@ class helper { * @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 url of the page to return to - * @param int $version version of the question + * @param int $restartversion The version of the question to use when restarting the preview. * @return moodle_url the URL */ public static function question_preview_url($questionid, $preferredbehaviour = null, $maxmark = null, $displayoptions = null, $variant = null, $context = null, $returnurl = null, - $version = null): moodle_url { + $restartversion = null): moodle_url { $params = ['id' => $questionid]; - if (!is_null($version)) { - $params['id'] = $version; + if (!is_null($restartversion)) { + $params['restartversion'] = $restartversion; } if (is_null($context)) { global $PAGE; @@ -313,4 +319,21 @@ class helper { } return $questionids; } + + /** + * Return the question ID from the array of id => version that corresponds to the requested version. + * + * If the requested version is question_preview_options::ALWAYS_LATEST, this will return the latest version. + * + * @param array $versions + * @param int $restartversion + * @return ?int + */ + public static function get_restart_id(array $versions, int $restartversion): ?int { + if ($restartversion === question_preview_options::ALWAYS_LATEST) { + return array_key_last($versions); + } else { + return array_search($restartversion, $versions) ?: null; + } + } } diff --git a/question/bank/previewquestion/classes/output/renderer.php b/question/bank/previewquestion/classes/output/renderer.php index 38af4566a39..1cf2038e987 100644 --- a/question/bank/previewquestion/classes/output/renderer.php +++ b/question/bank/previewquestion/classes/output/renderer.php @@ -18,6 +18,7 @@ namespace qbank_previewquestion\output; use context; use qbank_previewquestion\helper; +use qbank_previewquestion\question_preview_options; /** * Class renderer for rendering preview url @@ -50,7 +51,8 @@ class renderer extends \plugin_renderer_base { } $image = $this->pix_icon('t/preview', $alt, '', ['class' => 'iconsmall']); - $link = helper::question_preview_url($questionid, null, null, null, null, $context); + $link = helper::question_preview_url($questionid, null, null, null, null, $context, null, + question_preview_options::ALWAYS_LATEST); $action = new \popup_action('click', $link, 'questionpreview', helper::question_preview_popup_params()); return $this->action_link($link, $image . $label, $action, $attributes); diff --git a/question/bank/previewquestion/classes/preview_action_column.php b/question/bank/previewquestion/classes/preview_action_column.php index b191fe3de71..cc4d620ec7f 100644 --- a/question/bank/previewquestion/classes/preview_action_column.php +++ b/question/bank/previewquestion/classes/preview_action_column.php @@ -52,8 +52,15 @@ class preview_action_column extends menu_action_column_base { if (question_has_capability_on($question, 'use')) { $context = $this->qbank->get_most_specific_context(); + // Default previews to always use the latest question version, unless we are previewing specific versions from the + // question history. + if ($this->qbank->is_listing_specific_versions()) { + $requestedversion = $question->version; + } else { + $requestedversion = question_preview_options::ALWAYS_LATEST; + } $url = helper::question_preview_url($question->id, null, null, - null, null, $context, $this->qbank->returnurl); + null, null, $context, $this->qbank->returnurl, $requestedversion); return [$url, 't/preview', $this->strpreview]; } else { return [null, null, null]; diff --git a/question/bank/previewquestion/classes/question_preview_options.php b/question/bank/previewquestion/classes/question_preview_options.php index 77c9cd4e628..d380a3f1810 100644 --- a/question/bank/previewquestion/classes/question_preview_options.php +++ b/question/bank/previewquestion/classes/question_preview_options.php @@ -42,6 +42,9 @@ class question_preview_options extends question_display_options { /** @var string prefix to append to field names to get user_preference names. */ const OPTIONPREFIX = 'question_preview_options_'; + /** @var int Special value for question version ID to indicate that we should always use the latest version */ + const ALWAYS_LATEST = 0; + /** * Constructor. * @param \stdClass $question diff --git a/question/bank/previewquestion/lang/en/qbank_previewquestion.php b/question/bank/previewquestion/lang/en/qbank_previewquestion.php index 9947a3e41c0..e41c4101dd0 100644 --- a/question/bank/previewquestion/lang/en/qbank_previewquestion.php +++ b/question/bank/previewquestion/lang/en/qbank_previewquestion.php @@ -23,6 +23,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['alwayslatest'] = 'Always latest'; +$string['newerversion'] = 'This preview is using version {$a->currentversion} of this question. ' + . 'The latest version is {$a->latestversion}. {$a->restartbutton}?'; $string['pluginname'] = 'Preview question'; $string['privacy:metadata'] = 'The Preview question question bank plugin does not store any personal data.'; // Tag related errors. @@ -32,6 +35,7 @@ $string['tagsnotfound'] = 'Tags not found'; // Form string(s). $string['previewoptions'] = 'Preview options'; $string['questionversion'] = 'Question version'; +$string['restartnow'] = 'Restart now'; // Preview title. $string['versiontitle'] = 'Version {$a}'; $string['versiontitlelatest'] = 'Version {$a} (latest)'; diff --git a/question/bank/previewquestion/preview.php b/question/bank/previewquestion/preview.php index a21585c5aad..437ba77a688 100644 --- a/question/bank/previewquestion/preview.php +++ b/question/bank/previewquestion/preview.php @@ -49,6 +49,7 @@ define('QUESTION_PREVIEW_MAX_VARIANTS', 100); // Get and validate question id. $id = required_param('id', PARAM_INT); $returnurl = optional_param('returnurl', null, PARAM_LOCALURL); +$restartversion = optional_param('restartversion', question_preview_options::ALWAYS_LATEST, PARAM_INT); $question = question_bank::load_question($id); @@ -83,7 +84,7 @@ $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)); + $options, $options->variant, $context, null, $restartversion)); // Get and validate existing preview, or start a new one. $previewid = optional_param('previewid', 0, PARAM_INT); @@ -97,7 +98,7 @@ if ($previewid) { // 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); + $options->maxmark, $options, $options->variant, $context, null, $restartversion), null, $e); } if ($quba->get_owning_context()->instanceid != $USER->id) { @@ -133,11 +134,15 @@ if ($previewid) { $options->behaviour = $quba->get_preferred_behaviour(); $options->maxmark = $quba->get_question_max_mark($slot); -// Create the settings form, and initialise the fields. $versionids = helper::load_versions($question->questionbankentryid); -$optionsform = new preview_options_form(helper:: -question_preview_form_url($question->id, $context, $previewid, $returnurl), - ['quba' => $quba, 'maxvariant' => $maxvariant, 'versions' => $versionids, 'questionversion' => $id]); +// 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, + 'versions' => array_combine(array_values($versionids), array_values($versionids)), + 'restartversion' => $restartversion, + ]); $optionsform->set_data($options); // Process change of settings, if that was requested. @@ -147,13 +152,14 @@ if ($newoptions = $optionsform->get_submitted_data()) { if (!isset($newoptions->variant)) { $newoptions->variant = $options->variant; } + $questionid = helper::get_restart_id($versionids, $restartversion); if (isset($newoptions->saverestart)) { - helper::restart_preview($previewid, $question->id, $newoptions, $context, $returnurl, $newoptions->version); + helper::restart_preview($previewid, $questionid, $newoptions, $context, $returnurl, $newoptions->restartversion); } } // Prepare a URL that is used in various places. -$actionurl = helper::question_preview_action_url($question->id, $quba->get_id(), $options, $context, $returnurl); +$actionurl = helper::question_preview_action_url($question->id, $quba->get_id(), $options, $context, $returnurl, $restartversion); // Process any actions from the buttons at the bottom of the form. if (data_submitted() && confirm_sesskey()) { @@ -161,7 +167,8 @@ if (data_submitted() && confirm_sesskey()) { try { if (optional_param('restart', false, PARAM_BOOL)) { - helper::restart_preview($previewid, $question->id, $options, $context, $returnurl); + $questionid = helper::get_restart_id($versionids, $restartversion); + helper::restart_preview($previewid, $questionid, $options, $context, $returnurl, $restartversion); } else if (optional_param('fill', null, PARAM_BOOL)) { $correctresponse = $quba->get_correct_response($slot); @@ -262,6 +269,17 @@ if ($islatestversion) { $previewdata['versiontitle'] = get_string('versiontitlelatest', 'qbank_previewquestion', $question->version); } else { $previewdata['versiontitle'] = get_string('versiontitle', 'qbank_previewquestion', $question->version); + if ($restartversion == question_preview_options::ALWAYS_LATEST) { + $newerversionparams = (object) [ + 'currentversion' => $question->version, + 'latestversion' => max($versionids), + 'restartbutton' => $OUTPUT->render_from_template('qbank_previewquestion/restartbutton', []), + ]; + $newversionurl = clone $actionurl; + $newversionurl->param('restart', 1); + $previewdata['newerversionurl'] = $newversionurl; + $previewdata['newerversion'] = get_string('newerversion', 'qbank_previewquestion', $newerversionparams); + } } $previewdata['actionurl'] = $actionurl; diff --git a/question/bank/previewquestion/templates/preview_question.mustache b/question/bank/previewquestion/templates/preview_question.mustache index 02b718fbc73..68f0d919dde 100644 --- a/question/bank/previewquestion/templates/preview_question.mustache +++ b/question/bank/previewquestion/templates/preview_question.mustache @@ -61,6 +61,11 @@ {{versiontitle}} + {{#newerversion}} +