Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 $options->versioninfo = false; 87 $PAGE->set_url(helper::question_preview_url($id, $options->behaviour, $options->maxmark, 88 $options, $options->variant, $context, null, $restartversion)); 89 90 // Get and validate existing preview, or start a new one. 91 $previewid = optional_param('previewid', 0, PARAM_INT); 92 93 if ($previewid) { 94 try { 95 $quba = question_engine::load_questions_usage_by_activity($previewid); 96 97 } catch (Exception $e) { 98 // This may not seem like the right error message to display, but 99 // actually from the user point of view, it makes sense. 100 throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', 101 helper::question_preview_url($question->id, $options->behaviour, 102 $options->maxmark, $options, $options->variant, $context, null, $restartversion), null, $e); 103 } 104 105 if ($quba->get_owning_context()->instanceid != $USER->id) { 106 throw new moodle_exception('notyourpreview', 'question'); 107 } 108 109 $slot = $quba->get_first_question_number(); 110 $usedquestion = $quba->get_question($slot, false); 111 if ($usedquestion->id != $question->id) { 112 throw new moodle_exception('questionidmismatch', 'question'); 113 } 114 $question = $usedquestion; 115 $options->variant = $quba->get_variant($slot); 116 117 } else { 118 $quba = question_engine::make_questions_usage_by_activity( 119 'core_question_preview', context_user::instance($USER->id)); 120 $quba->set_preferred_behaviour($options->behaviour); 121 $slot = $quba->add_question($question, $options->maxmark); 122 123 if ($options->variant) { 124 $options->variant = min($maxvariant, max(1, $options->variant)); 125 } else { 126 $options->variant = rand(1, $maxvariant); 127 } 128 129 $quba->start_question($slot, $options->variant); 130 131 $transaction = $DB->start_delegated_transaction(); 132 question_engine::save_questions_usage_by_activity($quba); 133 $transaction->allow_commit(); 134 } 135 $options->behaviour = $quba->get_preferred_behaviour(); 136 $options->maxmark = $quba->get_question_max_mark($slot); 137 138 $versionids = helper::load_versions($question->questionbankentryid); 139 // Create the settings form, and initialise the fields. 140 $optionsform = new preview_options_form(helper::question_preview_form_url($question->id, $context, $previewid, $returnurl), 141 [ 142 'quba' => $quba, 143 'maxvariant' => $maxvariant, 144 'versions' => array_combine(array_values($versionids), array_values($versionids)), 145 'restartversion' => $restartversion, 146 ]); 147 $optionsform->set_data($options); 148 149 // Process change of settings, if that was requested. 150 if ($newoptions = $optionsform->get_submitted_data()) { 151 // Set user preferences. 152 $options->save_user_preview_options($newoptions); 153 if (!isset($newoptions->variant)) { 154 $newoptions->variant = $options->variant; 155 } 156 $questionid = helper::get_restart_id($versionids, $restartversion); 157 if (isset($newoptions->saverestart)) { 158 helper::restart_preview($previewid, $questionid, $newoptions, $context, $returnurl, $newoptions->restartversion); 159 } 160 } 161 162 // Prepare a URL that is used in various places. 163 $actionurl = helper::question_preview_action_url($question->id, $quba->get_id(), $options, $context, $returnurl, $restartversion); 164 165 // Process any actions from the buttons at the bottom of the form. 166 if (data_submitted() && confirm_sesskey()) { 167 168 try { 169 170 if (optional_param('restart', false, PARAM_BOOL)) { 171 $questionid = helper::get_restart_id($versionids, $restartversion); 172 helper::restart_preview($previewid, $questionid, $options, $context, $returnurl, $restartversion); 173 174 } else if (optional_param('fill', null, PARAM_BOOL)) { 175 $correctresponse = $quba->get_correct_response($slot); 176 if (!is_null($correctresponse)) { 177 $quba->process_action($slot, $correctresponse); 178 179 $transaction = $DB->start_delegated_transaction(); 180 question_engine::save_questions_usage_by_activity($quba); 181 $transaction->allow_commit(); 182 } 183 redirect($actionurl); 184 185 } else if (optional_param('finish', null, PARAM_BOOL)) { 186 $quba->process_all_actions(); 187 $quba->finish_all_questions(); 188 189 $transaction = $DB->start_delegated_transaction(); 190 question_engine::save_questions_usage_by_activity($quba); 191 $transaction->allow_commit(); 192 redirect($actionurl); 193 194 } else { 195 $quba->process_all_actions(); 196 197 $transaction = $DB->start_delegated_transaction(); 198 question_engine::save_questions_usage_by_activity($quba); 199 $transaction->allow_commit(); 200 201 $mdlscrollto = optional_param('mdlscrollto', '', PARAM_RAW); 202 if ($mdlscrollto !== '') { 203 $actionurl->param('mdlscrollto', (int) $mdlscrollto); 204 } 205 redirect($actionurl); 206 } 207 208 } catch (question_out_of_sequence_exception $e) { 209 throw new moodle_exception('submissionoutofsequencefriendlymessage', 'question', $actionurl); 210 211 } catch (Exception $e) { 212 // This sucks, if we display our own custom error message, there is no way 213 // to display the original stack trace. 214 $debuginfo = ''; 215 if (!empty($e->debuginfo)) { 216 $debuginfo = $e->debuginfo; 217 } 218 throw new moodle_exception('errorprocessingresponses', 'question', $actionurl, 219 $e->getMessage(), $debuginfo); 220 } 221 } 222 223 if ($question->length) { 224 $displaynumber = '1'; 225 } else { 226 $displaynumber = 'i'; 227 } 228 $restartdisabled = []; 229 $finishdisabled = []; 230 $filldisabled = []; 231 if ($quba->get_question_state($slot)->is_finished()) { 232 $finishdisabled = ['disabled' => 'disabled']; 233 $filldisabled = ['disabled' => 'disabled']; 234 } 235 // If question type cannot give us a correct response, disable this button. 236 if (is_null($quba->get_correct_response($slot))) { 237 $filldisabled = ['disabled' => 'disabled']; 238 } 239 if (!$previewid) { 240 $restartdisabled = ['disabled' => 'disabled']; 241 } 242 243 // Prepare technical info to be output. 244 $qa = $quba->get_question_attempt($slot); 245 $technical = []; 246 $technical[] = get_string('behaviourbeingused', 'question', 247 question_engine::get_behaviour_name($qa->get_behaviour_name())); 248 $technical[] = get_string('technicalinfominfraction', 'question', $qa->get_min_fraction()); 249 $technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction()); 250 $technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant()); 251 $technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary())); 252 $technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary())); 253 $technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary())); 254 $technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state()); 255 256 // Start output. 257 $title = get_string('previewquestion', 'question', format_string($question->name)); 258 $headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot); 259 $PAGE->set_title($title); 260 $PAGE->set_heading($title); 261 echo $OUTPUT->header(); 262 263 $previewdata = []; 264 265 $previewdata['questionicon'] = print_question_icon($question); 266 $previewdata['questionidumber'] = $question->idnumber; 267 $previewdata['questiontitle'] = $question->name; 268 $versioninfo = new \core_question\output\question_version_info($question); 269 $previewdata['versiontitle'] = $versioninfo->export_for_template($OUTPUT); 270 if ($versioninfo->version !== $versioninfo->latestversion) { 271 if ($restartversion == question_preview_options::ALWAYS_LATEST) { 272 $newerversionparams = (object) [ 273 'currentversion' => $question->version, 274 'latestversion' => max($versionids), 275 'restartbutton' => $OUTPUT->render_from_template('qbank_previewquestion/restartbutton', []), 276 ]; 277 $newversionurl = clone $actionurl; 278 $newversionurl->param('restart', 1); 279 $previewdata['newerversionurl'] = $newversionurl; 280 $previewdata['newerversion'] = get_string('newerversion', 'qbank_previewquestion', $newerversionparams); 281 } 282 } 283 284 $previewdata['actionurl'] = $actionurl; 285 $previewdata['session'] = sesskey(); 286 $previewdata['slot'] = $slot; 287 // Output of the question. 288 $previewdata['question'] = $quba->render_question($slot, $options, $displaynumber); 289 $previewdata['restartdisabled'] = html_writer::attributes($restartdisabled); 290 $previewdata['finishdisabled'] = html_writer::attributes($finishdisabled); 291 $previewdata['filldisabled'] = html_writer::attributes($filldisabled); 292 // Output the technical info. 293 $previewdata['techinfo'] = print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'), 294 'core_question_preview_techinfo_collapsed', true, true, $OUTPUT->help_icon('technicalinfo', 'question')); 295 foreach ($technical as $info) { 296 $previewdata['techinfo'] .= html_writer::tag('p', $info, ['class' => 'notifytiny']); 297 } 298 $previewdata['techinfo'] .= print_collapsible_region_end(true); 299 300 // Display the settings form. 301 $previewdata['options'] = $optionsform->render(); 302 303 list($comment, $extraelements) = helper::get_preview_extra_elements($question, $COURSE->id); 304 305 if (!empty($comment)) { 306 $previewdata['comments'] = $comment; 307 } 308 309 if (!empty($extraelements)) { 310 $elements = []; 311 foreach ($extraelements as $extraelement) { 312 $element = new stdClass(); 313 $element->extrapreviewelements = $extraelement; 314 $elements[] = $element; 315 } 316 $previewdata['extrapreviewelements'] = $elements; 317 } 318 319 $previewdata['redirect'] = false; 320 if (!is_null($returnurl)) { 321 $previewdata['redirect'] = true; 322 $previewdata['redirecturl'] = $returnurl; 323 } 324 $closeurl = new moodle_url('/question/edit.php', ['courseid' => $COURSE->id]); 325 echo $PAGE->get_renderer('qbank_previewquestion')->render_preview_page($previewdata); 326 327 // Log the preview of this question. 328 $event = \core\event\question_viewed::create_from_question_instance($question, $context); 329 $event->trigger(); 330 331 $PAGE->requires->js_call_amd('qbank_previewquestion/preview', 'init', [$previewdata['redirect'], $closeurl->__toString()]); 332 echo $OUTPUT->footer();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body