See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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 qbank_previewquestion 26 * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} 27 * @author 2021 Safat Shahin <safatshahin@catalyst-au.net> and numerous contributors. 28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 29 */ 30 31 require_once(__DIR__ . '/../../../config.php'); 32 require_once($CFG->libdir . '/questionlib.php'); 33 34 use \core\notification; 35 use qbank_previewquestion\form\preview_options_form; 36 use qbank_previewquestion\question_preview_options; 37 use qbank_previewquestion\helper; 38 39 /** 40 * The maximum number of variants previewable. If there are more variants than this for a question 41 * then we only allow the selection of the first x variants. 42 * 43 * @var integer 44 */ 45 define('QUESTION_PREVIEW_MAX_VARIANTS', 100); 46 47 \core_question\local\bank\helper::require_plugin_enabled('qbank_previewquestion'); 48 49 // Get and validate question id. 50 $id = required_param('id', PARAM_INT); 51 $returnurl = optional_param('returnurl', null, PARAM_LOCALURL); 52 $restartversion = optional_param('restartversion', question_preview_options::ALWAYS_LATEST, PARAM_INT); 53 54 $question = question_bank::load_question($id); 55 56 if ($returnurl) { 57 $returnurl = new moodle_url($returnurl); 58 } 59 60 // Were we given a particular context to run the question in? 61 // This affects things like filter settings, or forced theme or language. 62 if ($cmid = optional_param('cmid', 0, PARAM_INT)) { 63 $cm = get_coursemodule_from_id(false, $cmid); 64 require_login($cm->course, false, $cm); 65 $context = context_module::instance($cmid); 66 67 } else if ($courseid = optional_param('courseid', 0, PARAM_INT)) { 68 require_login($courseid); 69 $context = context_course::instance($courseid); 70 71 } else { 72 require_login(); 73 $category = $DB->get_record('question_categories', ['id' => $question->category], '*', MUST_EXIST); 74 $context = context::instance_by_id($category->contextid); 75 $PAGE->set_context($context); 76 // Note that in the other cases, require_login will set the correct page context. 77 } 78 question_require_capability_on($question, 'use'); 79 $PAGE->set_pagelayout('popup'); 80 81 // Get and validate display options. 82 $maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS); 83 $options = new question_preview_options($question); 84 $options->load_user_defaults(); 85 $options->set_from_request(); 86 $PAGE->set_url(helper::question_preview_url($id, $options->behaviour, $options->maxmark, 87 $options, $options->variant, $context, null, $restartversion)); 88 89 // Get and validate existing preview, or start a new one. 90 $previewid = optional_param('previewid', 0, PARAM_INT); 91 92 if ($previewid) { 93 try { 94 $quba = question_engine::load_questions_usage_by_activity($previewid); 95 96 } catch (Exception $e) { 97 // This may not seem like the right error message to display, but 98 // actually from the user point of view, it makes sense. 99 throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', 100 helper::question_preview_url($question->id, $options->behaviour, 101 $options->maxmark, $options, $options->variant, $context, null, $restartversion), null, $e); 102 } 103 104 if ($quba->get_owning_context()->instanceid != $USER->id) { 105 throw new moodle_exception('notyourpreview', 'question'); 106 } 107 108 $slot = $quba->get_first_question_number(); 109 $usedquestion = $quba->get_question($slot, false); 110 if ($usedquestion->id != $question->id) { 111 throw new moodle_exception('questionidmismatch', 'question'); 112 } 113 $question = $usedquestion; 114 $options->variant = $quba->get_variant($slot); 115 116 } else { 117 $quba = question_engine::make_questions_usage_by_activity( 118 'core_question_preview', context_user::instance($USER->id)); 119 $quba->set_preferred_behaviour($options->behaviour); 120 $slot = $quba->add_question($question, $options->maxmark); 121 122 if ($options->variant) { 123 $options->variant = min($maxvariant, max(1, $options->variant)); 124 } else { 125 $options->variant = rand(1, $maxvariant); 126 } 127 128 $quba->start_question($slot, $options->variant); 129 130 $transaction = $DB->start_delegated_transaction(); 131 question_engine::save_questions_usage_by_activity($quba); 132 $transaction->allow_commit(); 133 } 134 $options->behaviour = $quba->get_preferred_behaviour(); 135 $options->maxmark = $quba->get_question_max_mark($slot); 136 137 $versionids = helper::load_versions($question->questionbankentryid); 138 // Create the settings form, and initialise the fields. 139 $optionsform = new preview_options_form(helper::question_preview_form_url($question->id, $context, $previewid, $returnurl), 140 [ 141 'quba' => $quba, 142 'maxvariant' => $maxvariant, 143 'versions' => array_combine(array_values($versionids), array_values($versionids)), 144 'restartversion' => $restartversion, 145 ]); 146 $optionsform->set_data($options); 147 148 // Process change of settings, if that was requested. 149 if ($newoptions = $optionsform->get_submitted_data()) { 150 // Set user preferences. 151 $options->save_user_preview_options($newoptions); 152 if (!isset($newoptions->variant)) { 153 $newoptions->variant = $options->variant; 154 } 155 $questionid = helper::get_restart_id($versionids, $restartversion); 156 if (isset($newoptions->saverestart)) { 157 helper::restart_preview($previewid, $questionid, $newoptions, $context, $returnurl, $newoptions->restartversion); 158 } 159 } 160 161 // Prepare a URL that is used in various places. 162 $actionurl = helper::question_preview_action_url($question->id, $quba->get_id(), $options, $context, $returnurl, $restartversion); 163 164 // Process any actions from the buttons at the bottom of the form. 165 if (data_submitted() && confirm_sesskey()) { 166 167 try { 168 169 if (optional_param('restart', false, PARAM_BOOL)) { 170 $questionid = helper::get_restart_id($versionids, $restartversion); 171 helper::restart_preview($previewid, $questionid, $options, $context, $returnurl, $restartversion); 172 173 } else if (optional_param('fill', null, PARAM_BOOL)) { 174 $correctresponse = $quba->get_correct_response($slot); 175 if (!is_null($correctresponse)) { 176 $quba->process_action($slot, $correctresponse); 177 178 $transaction = $DB->start_delegated_transaction(); 179 question_engine::save_questions_usage_by_activity($quba); 180 $transaction->allow_commit(); 181 } 182 redirect($actionurl); 183 184 } else if (optional_param('finish', null, PARAM_BOOL)) { 185 $quba->process_all_actions(); 186 $quba->finish_all_questions(); 187 188 $transaction = $DB->start_delegated_transaction(); 189 question_engine::save_questions_usage_by_activity($quba); 190 $transaction->allow_commit(); 191 redirect($actionurl); 192 193 } else { 194 $quba->process_all_actions(); 195 196 $transaction = $DB->start_delegated_transaction(); 197 question_engine::save_questions_usage_by_activity($quba); 198 $transaction->allow_commit(); 199 200 $scrollpos = optional_param('scrollpos', '', PARAM_RAW); 201 if ($scrollpos !== '') { 202 $actionurl->param('scrollpos', (int) $scrollpos); 203 } 204 redirect($actionurl); 205 } 206 207 } catch (question_out_of_sequence_exception $e) { 208 throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', $actionurl); 209 210 } catch (Exception $e) { 211 // This sucks, if we display our own custom error message, there is no way 212 // to display the original stack trace. 213 $debuginfo = ''; 214 if (!empty($e->debuginfo)) { 215 $debuginfo = $e->debuginfo; 216 } 217 throw new moodle_exception('errorprocessingresponses', 'question', $actionurl, 218 $e->getMessage(), $debuginfo); 219 } 220 } 221 222 if ($question->length) { 223 $displaynumber = '1'; 224 } else { 225 $displaynumber = 'i'; 226 } 227 $restartdisabled = []; 228 $finishdisabled = []; 229 $filldisabled = []; 230 if ($quba->get_question_state($slot)->is_finished()) { 231 $finishdisabled = ['disabled' => 'disabled']; 232 $filldisabled = ['disabled' => 'disabled']; 233 } 234 // If question type cannot give us a correct response, disable this button. 235 if (is_null($quba->get_correct_response($slot))) { 236 $filldisabled = ['disabled' => 'disabled']; 237 } 238 if (!$previewid) { 239 $restartdisabled = ['disabled' => 'disabled']; 240 } 241 242 // Prepare technical info to be output. 243 $qa = $quba->get_question_attempt($slot); 244 $technical = []; 245 $technical[] = get_string('behaviourbeingused', 'question', 246 question_engine::get_behaviour_name($qa->get_behaviour_name())); 247 $technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction()); 248 $technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction()); 249 $technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant()); 250 $technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary())); 251 $technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary())); 252 $technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary())); 253 $technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state()); 254 255 // Start output. 256 $title = get_string('previewquestion', 'question', format_string($question->name)); 257 $headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot); 258 $PAGE->set_title($title); 259 $PAGE->set_heading($title); 260 echo $OUTPUT->header(); 261 262 $previewdata = []; 263 264 $previewdata['questionicon'] = print_question_icon($question); 265 $previewdata['questionidumber'] = $question->idnumber; 266 $previewdata['questiontitle'] = $question->name; 267 $islatestversion = is_latest($question->version, $question->questionbankentryid); 268 if ($islatestversion) { 269 $previewdata['versiontitle'] = get_string('versiontitlelatest', 'qbank_previewquestion', $question->version); 270 } else { 271 $previewdata['versiontitle'] = get_string('versiontitle', 'qbank_previewquestion', $question->version); 272 if ($restartversion == question_preview_options::ALWAYS_LATEST) { 273 $newerversionparams = (object) [ 274 'currentversion' => $question->version, 275 'latestversion' => max($versionids), 276 'restartbutton' => $OUTPUT->render_from_template('qbank_previewquestion/restartbutton', []), 277 ]; 278 $newversionurl = clone $actionurl; 279 $newversionurl->param('restart', 1); 280 $previewdata['newerversionurl'] = $newversionurl; 281 $previewdata['newerversion'] = get_string('newerversion', 'qbank_previewquestion', $newerversionparams); 282 } 283 } 284 285 $previewdata['actionurl'] = $actionurl; 286 $previewdata['session'] = sesskey(); 287 $previewdata['slot'] = $slot; 288 // Output of the question. 289 $previewdata['question'] = $quba->render_question($slot, $options, $displaynumber); 290 $previewdata['restartdisabled'] = html_writer::attributes($restartdisabled); 291 $previewdata['finishdisabled'] = html_writer::attributes($finishdisabled); 292 $previewdata['filldisabled'] = html_writer::attributes($filldisabled); 293 // Output the technical info. 294 $previewdata['techinfo'] = print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'), 295 'core_question_preview_techinfo_collapsed', true, true, $OUTPUT->help_icon('technicalinfo', 'question')); 296 foreach ($technical as $info) { 297 $previewdata['techinfo'] .= html_writer::tag('p', $info, ['class' => 'notifytiny']); 298 } 299 $previewdata['techinfo'] .= print_collapsible_region_end(true); 300 301 // Display the settings form. 302 $previewdata['options'] = $optionsform->render(); 303 304 list($comment, $extraelements) = helper::get_preview_extra_elements($question, $COURSE->id); 305 306 if (!empty($comment)) { 307 $previewdata['comments'] = $comment; 308 } 309 310 if (!empty($extraelements)) { 311 $elements = []; 312 foreach ($extraelements as $extraelement) { 313 $element = new stdClass(); 314 $element->extrapreviewelements = $extraelement; 315 $elements[] = $element; 316 } 317 $previewdata['extrapreviewelements'] = $elements; 318 } 319 320 $previewdata['redirect'] = false; 321 if (!is_null($returnurl)) { 322 $previewdata['redirect'] = true; 323 $previewdata['redirecturl'] = $returnurl; 324 } 325 $closeurl = new moodle_url('/question/edit.php', ['courseid' => $COURSE->id]); 326 echo $PAGE->get_renderer('qbank_previewquestion')->render_preview_page($previewdata); 327 328 // Log the preview of this question. 329 $event = \core\event\question_viewed::create_from_question_instance($question, $context); 330 $event->trigger(); 331 332 $PAGE->requires->js_call_amd('qbank_previewquestion/preview', 'init', [$previewdata['redirect'], $closeurl->__toString()]); 333 echo $OUTPUT->footer();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body