Differences Between: [Versions 39 and 310]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This page displays a preview of a question 19 * 20 * The preview uses the option settings from the activity within which the question 21 * is previewed or the default settings if no activity is specified. The question session 22 * information is stored in the session as an array of subsequent states rather 23 * than in the database. 24 * 25 * @package moodlecore 26 * @subpackage questionengine 27 * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} and 28 * numerous contributors. 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 */ 31 32 33 require_once(__DIR__ . '/../config.php'); 34 require_once($CFG->libdir . '/questionlib.php'); 35 require_once (__DIR__ . '/previewlib.php'); 36 37 /** 38 * The maximum number of variants previewable. If there are more variants than this for a question 39 * then we only allow the selection of the first x variants. 40 * @var integer 41 */ 42 define('QUESTION_PREVIEW_MAX_VARIANTS', 100); 43 44 // Get and validate question id. 45 $id = required_param('id', PARAM_INT); 46 $question = question_bank::load_question($id); 47 48 // Were we given a particular context to run the question in? 49 // This affects things like filter settings, or forced theme or language. 50 if ($cmid = optional_param('cmid', 0, PARAM_INT)) { 51 $cm = get_coursemodule_from_id(false, $cmid); 52 require_login($cm->course, false, $cm); 53 $context = context_module::instance($cmid); 54 55 } else if ($courseid = optional_param('courseid', 0, PARAM_INT)) { 56 require_login($courseid); 57 $context = context_course::instance($courseid); 58 59 } else { 60 require_login(); 61 $category = $DB->get_record('question_categories', 62 array('id' => $question->category), '*', MUST_EXIST); 63 $context = context::instance_by_id($category->contextid); 64 $PAGE->set_context($context); 65 // Note that in the other cases, require_login will set the correct page context. 66 } 67 question_require_capability_on($question, 'use'); 68 $PAGE->set_pagelayout('popup'); 69 70 // Get and validate display options. 71 $maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS); 72 $options = new question_preview_options($question); 73 $options->load_user_defaults(); 74 $options->set_from_request(); 75 $PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, 76 $options, $options->variant, $context)); 77 78 // Get and validate existing preview, or start a new one. 79 $previewid = optional_param('previewid', 0, PARAM_INT); 80 81 if ($previewid) { 82 try { 83 $quba = question_engine::load_questions_usage_by_activity($previewid); 84 85 } catch (Exception $e) { 86 // This may not seem like the right error message to display, but 87 // actually from the user point of view, it makes sense. 88 print_error('submissionoutofsequencefriendlymessage', 'question', 89 question_preview_url($question->id, $options->behaviour, 90 $options->maxmark, $options, $options->variant, $context), null, $e); 91 } 92 93 if ($quba->get_owning_context()->instanceid != $USER->id) { 94 print_error('notyourpreview', 'question'); 95 } 96 97 $slot = $quba->get_first_question_number(); 98 $usedquestion = $quba->get_question($slot, false); 99 if ($usedquestion->id != $question->id) { 100 print_error('questionidmismatch', 'question'); 101 } 102 $question = $usedquestion; 103 $options->variant = $quba->get_variant($slot); 104 105 } else { 106 $quba = question_engine::make_questions_usage_by_activity( 107 'core_question_preview', context_user::instance($USER->id)); 108 $quba->set_preferred_behaviour($options->behaviour); 109 $slot = $quba->add_question($question, $options->maxmark); 110 111 if ($options->variant) { 112 $options->variant = min($maxvariant, max(1, $options->variant)); 113 } else { 114 $options->variant = rand(1, $maxvariant); 115 } 116 117 $quba->start_question($slot, $options->variant); 118 119 $transaction = $DB->start_delegated_transaction(); 120 question_engine::save_questions_usage_by_activity($quba); 121 $transaction->allow_commit(); 122 } 123 $options->behaviour = $quba->get_preferred_behaviour(); 124 $options->maxmark = $quba->get_question_max_mark($slot); 125 126 // Create the settings form, and initialise the fields. 127 $optionsform = new preview_options_form(question_preview_form_url($question->id, $context, $previewid), 128 array('quba' => $quba, 'maxvariant' => $maxvariant)); 129 $optionsform->set_data($options); 130 131 // Process change of settings, if that was requested. 132 if ($newoptions = $optionsform->get_submitted_data()) { 133 // Set user preferences. 134 $options->save_user_preview_options($newoptions); 135 if (!isset($newoptions->variant)) { 136 $newoptions->variant = $options->variant; 137 } 138 if (isset($newoptions->saverestart)) { 139 restart_preview($previewid, $question->id, $newoptions, $context); 140 } 141 } 142 143 // Prepare a URL that is used in various places. 144 $actionurl = question_preview_action_url($question->id, $quba->get_id(), $options, $context); 145 146 // Process any actions from the buttons at the bottom of the form. 147 if (data_submitted() && confirm_sesskey()) { 148 149 try { 150 151 if (optional_param('restart', false, PARAM_BOOL)) { 152 restart_preview($previewid, $question->id, $options, $context); 153 154 } else if (optional_param('fill', null, PARAM_BOOL)) { 155 $correctresponse = $quba->get_correct_response($slot); 156 if (!is_null($correctresponse)) { 157 $quba->process_action($slot, $correctresponse); 158 159 $transaction = $DB->start_delegated_transaction(); 160 question_engine::save_questions_usage_by_activity($quba); 161 $transaction->allow_commit(); 162 } 163 redirect($actionurl); 164 165 } else if (optional_param('finish', null, PARAM_BOOL)) { 166 $quba->process_all_actions(); 167 $quba->finish_all_questions(); 168 169 $transaction = $DB->start_delegated_transaction(); 170 question_engine::save_questions_usage_by_activity($quba); 171 $transaction->allow_commit(); 172 redirect($actionurl); 173 174 } else { 175 $quba->process_all_actions(); 176 177 $transaction = $DB->start_delegated_transaction(); 178 question_engine::save_questions_usage_by_activity($quba); 179 $transaction->allow_commit(); 180 181 $scrollpos = optional_param('scrollpos', '', PARAM_RAW); 182 if ($scrollpos !== '') { 183 $actionurl->param('scrollpos', (int) $scrollpos); 184 } 185 redirect($actionurl); 186 } 187 188 } catch (question_out_of_sequence_exception $e) { 189 print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl); 190 191 } catch (Exception $e) { 192 // This sucks, if we display our own custom error message, there is no way 193 // to display the original stack trace. 194 $debuginfo = ''; 195 if (!empty($e->debuginfo)) { 196 $debuginfo = $e->debuginfo; 197 } 198 print_error('errorprocessingresponses', 'question', $actionurl, 199 $e->getMessage(), $debuginfo); 200 } 201 } 202 203 if ($question->length) { 204 $displaynumber = '1'; 205 } else { 206 $displaynumber = 'i'; 207 } 208 $restartdisabled = array(); 209 $finishdisabled = array(); 210 $filldisabled = array(); 211 if ($quba->get_question_state($slot)->is_finished()) { 212 $finishdisabled = array('disabled' => 'disabled'); 213 $filldisabled = array('disabled' => 'disabled'); 214 } 215 // If question type cannot give us a correct response, disable this button. 216 if (is_null($quba->get_correct_response($slot))) { 217 $filldisabled = array('disabled' => 'disabled'); 218 } 219 if (!$previewid) { 220 $restartdisabled = array('disabled' => 'disabled'); 221 } 222 223 // Prepare technical info to be output. 224 $qa = $quba->get_question_attempt($slot); 225 $technical = array(); 226 $technical[] = get_string('behaviourbeingused', 'question', 227 question_engine::get_behaviour_name($qa->get_behaviour_name())); 228 $technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction()); 229 $technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction()); 230 $technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant()); 231 $technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary())); 232 $technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary())); 233 $technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary())); 234 $technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state()); 235 236 // Start output. 237 $title = get_string('previewquestion', 'question', format_string($question->name)); 238 $headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot); 239 $PAGE->set_title($title); 240 $PAGE->set_heading($title); 241 echo $OUTPUT->header(); 242 243 // Start the question form. 244 echo html_writer::start_tag('form', array('method' => 'post', 'action' => $actionurl, 245 'enctype' => 'multipart/form-data', 'id' => 'responseform')); 246 echo html_writer::start_tag('div'); 247 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); 248 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'slots', 'value' => $slot)); 249 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'scrollpos', 'value' => '', 'id' => 'scrollpos')); 250 echo html_writer::end_tag('div'); 251 252 // Output the question. 253 echo $quba->render_question($slot, $options, $displaynumber); 254 255 // Finish the question form. 256 echo html_writer::start_tag('div', array('id' => 'previewcontrols', 'class' => 'controls')); 257 echo html_writer::empty_tag('input', $restartdisabled + array('type' => 'submit', 258 'name' => 'restart', 'value' => get_string('restart', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1', 259 'id' => 'id_restart_question_preview')); 260 echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit', 261 'name' => 'save', 'value' => get_string('save', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1', 262 'id' => 'id_save_question_preview')); 263 echo html_writer::empty_tag('input', $filldisabled + array('type' => 'submit', 264 'name' => 'fill', 'value' => get_string('fillincorrect', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1')); 265 echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit', 266 'name' => 'finish', 'value' => get_string('submitandfinish', 'question'), 'class' => 'btn btn-secondary mr-1 mb-1', 267 'id' => 'id_finish_question_preview')); 268 echo html_writer::end_tag('div'); 269 echo html_writer::end_tag('form'); 270 271 // Output the technical info. 272 print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'), 273 'core_question_preview_techinfo_collapsed', true, false, $OUTPUT->help_icon('technicalinfo', 'question')); 274 foreach ($technical as $info) { 275 echo html_writer::tag('p', $info, array('class' => 'notifytiny')); 276 } 277 print_collapsible_region_end(); 278 279 // Output a link to export this single question. 280 if (question_has_capability_on($question, 'view')) { 281 echo html_writer::link(question_get_export_single_question_url($question), 282 get_string('exportonequestion', 'question')); 283 } 284 285 // Log the preview of this question. 286 $event = \core\event\question_viewed::create_from_question_instance($question, $context); 287 $event->trigger(); 288 289 // Display the settings form. 290 $optionsform->display(); 291 292 $PAGE->requires->js_module('core_question_engine'); 293 $PAGE->requires->strings_for_js(array( 294 'closepreview', 295 ), 'question'); 296 $PAGE->requires->yui_module('moodle-question-preview', 'M.question.preview.init'); 297 $PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_save_question_preview']); 298 $PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_finish_question_preview']); 299 $PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_restart_question_preview']); 300 $PAGE->requires->yui_module('moodle-core-formchangechecker', 301 'M.core_formchangechecker.init', [['formid' => 'responseform']]); 302 echo $OUTPUT->footer(); 303
title
Description
Body
title
Description
Body
title
Description
Body
title
Body