See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * 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 <safatshahin@catalyst-au.net> 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 \core\notification; 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_LOCALURL); $restartversion = optional_param('restartversion', question_preview_options::ALWAYS_LATEST, PARAM_INT); $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, null, $restartversion)); // 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, $restartversion), 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); $versionids = helper::load_versions($question->questionbankentryid); // 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. if ($newoptions = $optionsform->get_submitted_data()) { // Set user preferences. $options->save_user_preview_options($newoptions); if (!isset($newoptions->variant)) { $newoptions->variant = $options->variant; } $questionid = helper::get_restart_id($versionids, $restartversion); if (isset($newoptions->saverestart)) { 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, $restartversion); // 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)) { $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); 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);> $mdlscrollto = optional_param('mdlscrollto', '', PARAM_RAW); > if ($mdlscrollto !== '') { > $actionurl->param('mdlscrollto', (int) $mdlscrollto);} 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['questionicon'] = print_question_icon($question); $previewdata['questionidumber'] = $question->idnumber; $previewdata['questiontitle'] = $question->name; $islatestversion = is_latest($question->version, $question->questionbankentryid); 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; $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); // 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; } $closeurl = new moodle_url('/question/edit.php', ['courseid' => $COURSE->id]); 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'], $closeurl->__toString()]); echo $OUTPUT->footer();