Commit | Line | Data |
---|---|---|
aeb15530 | 1 | <?php |
c76145d3 TH |
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 | ||
516cf3eb | 17 | /** |
4323d029 | 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 | * | |
017bc1d9 | 25 | * @package moodlecore |
7764183a | 26 | * @subpackage questionengine |
017bc1d9 | 27 | * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} and |
c76145d3 | 28 | * numerous contributors. |
7764183a | 29 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
4323d029 | 30 | */ |
516cf3eb | 31 | |
017bc1d9 | 32 | |
1fcf0ca8 | 33 | require_once(__DIR__ . '/../config.php'); |
c76145d3 | 34 | require_once($CFG->libdir . '/questionlib.php'); |
1fcf0ca8 | 35 | require_once(__DIR__ . '/previewlib.php'); |
c76145d3 | 36 | |
76e6cf5d JP |
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 | ||
c76145d3 TH |
44 | // Get and validate question id. |
45 | $id = required_param('id', PARAM_INT); | |
46 | $question = question_bank::load_question($id); | |
56a4ae46 TH |
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); | |
21c08c63 | 53 | $context = context_module::instance($cmid); |
56a4ae46 TH |
54 | |
55 | } else if ($courseid = optional_param('courseid', 0, PARAM_INT)) { | |
56 | require_login($courseid); | |
21c08c63 | 57 | $context = context_course::instance($courseid); |
56a4ae46 TH |
58 | |
59 | } else { | |
60 | require_login(); | |
61 | $category = $DB->get_record('question_categories', | |
62 | array('id' => $question->category), '*', MUST_EXIST); | |
d197ea43 | 63 | $context = context::instance_by_id($category->contextid); |
56a4ae46 TH |
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'); | |
56920c36 | 68 | $PAGE->set_pagelayout('popup'); |
c76145d3 TH |
69 | |
70 | // Get and validate display options. | |
76e6cf5d | 71 | $maxvariant = min($question->get_num_variants(), QUESTION_PREVIEW_MAX_VARIANTS); |
c76145d3 TH |
72 | $options = new question_preview_options($question); |
73 | $options->load_user_defaults(); | |
74 | $options->set_from_request(); | |
56a4ae46 TH |
75 | $PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, |
76 | $options, $options->variant, $context)); | |
c76145d3 | 77 | |
4c16e191 | 78 | // Get and validate existing preview, or start a new one. |
612106b3 TH |
79 | $previewid = optional_param('previewid', 0, PARAM_INT); |
80 | ||
c76145d3 | 81 | if ($previewid) { |
c76145d3 TH |
82 | try { |
83 | $quba = question_engine::load_questions_usage_by_activity($previewid); | |
bed49557 | 84 | |
c014b989 | 85 | } catch (Exception $e) { |
bed49557 TH |
86 | // This may not seem like the right error message to display, but |
87 | // actually from the user point of view, it makes sense. | |
c76145d3 TH |
88 | print_error('submissionoutofsequencefriendlymessage', 'question', |
89 | question_preview_url($question->id, $options->behaviour, | |
56a4ae46 | 90 | $options->maxmark, $options, $options->variant, $context), null, $e); |
c76145d3 | 91 | } |
bed49557 | 92 | |
4c16e191 TH |
93 | if ($quba->get_owning_context()->instanceid != $USER->id) { |
94 | print_error('notyourpreview', 'question'); | |
95 | } | |
96 | ||
c76145d3 | 97 | $slot = $quba->get_first_question_number(); |
64207dab | 98 | $usedquestion = $quba->get_question($slot, false); |
c76145d3 TH |
99 | if ($usedquestion->id != $question->id) { |
100 | print_error('questionidmismatch', 'question'); | |
101 | } | |
102 | $question = $usedquestion; | |
c014b989 | 103 | $options->variant = $quba->get_variant($slot); |
c76145d3 TH |
104 | |
105 | } else { | |
56a4ae46 | 106 | $quba = question_engine::make_questions_usage_by_activity( |
4c16e191 | 107 | 'core_question_preview', context_user::instance($USER->id)); |
c76145d3 TH |
108 | $quba->set_preferred_behaviour($options->behaviour); |
109 | $slot = $quba->add_question($question, $options->maxmark); | |
c014b989 TH |
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); | |
c76145d3 TH |
118 | |
119 | $transaction = $DB->start_delegated_transaction(); | |
120 | question_engine::save_questions_usage_by_activity($quba); | |
121 | $transaction->allow_commit(); | |
c76145d3 TH |
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. | |
599cc731 | 127 | $optionsform = new preview_options_form(question_preview_form_url($question->id, $context, $previewid), |
56a4ae46 | 128 | array('quba' => $quba, 'maxvariant' => $maxvariant)); |
c76145d3 TH |
129 | $optionsform->set_data($options); |
130 | ||
131 | // Process change of settings, if that was requested. | |
132 | if ($newoptions = $optionsform->get_submitted_data()) { | |
05a7d155 | 133 | // Set user preferences. |
c76145d3 | 134 | $options->save_user_preview_options($newoptions); |
612106b3 TH |
135 | if (!isset($newoptions->variant)) { |
136 | $newoptions->variant = $options->variant; | |
137 | } | |
599cc731 TL |
138 | if (isset($newoptions->saverestart)) { |
139 | restart_preview($previewid, $question->id, $newoptions, $context); | |
140 | } | |
c76145d3 TH |
141 | } |
142 | ||
143 | // Prepare a URL that is used in various places. | |
56a4ae46 | 144 | $actionurl = question_preview_action_url($question->id, $quba->get_id(), $options, $context); |
c76145d3 TH |
145 | |
146 | // Process any actions from the buttons at the bottom of the form. | |
147 | if (data_submitted() && confirm_sesskey()) { | |
c76145d3 | 148 | |
bed49557 TH |
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); | |
8f725ade CF |
156 | if (!is_null($correctresponse)) { |
157 | $quba->process_action($slot, $correctresponse); | |
c76145d3 | 158 | |
8f725ade CF |
159 | $transaction = $DB->start_delegated_transaction(); |
160 | question_engine::save_questions_usage_by_activity($quba); | |
161 | $transaction->allow_commit(); | |
162 | } | |
bed49557 | 163 | redirect($actionurl); |
c76145d3 | 164 | |
bed49557 | 165 | } else if (optional_param('finish', null, PARAM_BOOL)) { |
c76145d3 | 166 | $quba->process_all_actions(); |
bed49557 | 167 | $quba->finish_all_questions(); |
516cf3eb | 168 | |
bed49557 TH |
169 | $transaction = $DB->start_delegated_transaction(); |
170 | question_engine::save_questions_usage_by_activity($quba); | |
171 | $transaction->allow_commit(); | |
172 | redirect($actionurl); | |
516cf3eb | 173 | |
bed49557 | 174 | } else { |
c76145d3 | 175 | $quba->process_all_actions(); |
bed49557 TH |
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); | |
516cf3eb | 186 | } |
516cf3eb | 187 | |
bed49557 TH |
188 | } catch (question_out_of_sequence_exception $e) { |
189 | print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl); | |
516cf3eb | 190 | |
bed49557 TH |
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; | |
c76145d3 | 197 | } |
bed49557 TH |
198 | print_error('errorprocessingresponses', 'question', $actionurl, |
199 | $e->getMessage(), $debuginfo); | |
c76145d3 TH |
200 | } |
201 | } | |
202 | ||
203 | if ($question->length) { | |
204 | $displaynumber = '1'; | |
205 | } else { | |
206 | $displaynumber = 'i'; | |
207 | } | |
05a7d155 TH |
208 | $restartdisabled = array(); |
209 | $finishdisabled = array(); | |
210 | $filldisabled = array(); | |
c76145d3 | 211 | if ($quba->get_question_state($slot)->is_finished()) { |
05a7d155 TH |
212 | $finishdisabled = array('disabled' => 'disabled'); |
213 | $filldisabled = array('disabled' => 'disabled'); | |
da22c012 K |
214 | } |
215 | // If question type cannot give us a correct response, disable this button. | |
216 | if (is_null($quba->get_correct_response($slot))) { | |
05a7d155 | 217 | $filldisabled = array('disabled' => 'disabled'); |
c76145d3 TH |
218 | } |
219 | if (!$previewid) { | |
05a7d155 | 220 | $restartdisabled = array('disabled' => 'disabled'); |
c76145d3 TH |
221 | } |
222 | ||
802f8d2a TH |
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()); | |
4e3d8293 | 229 | $technical[] = get_string('technicalinfomaxfraction', 'question', $qa->get_max_fraction()); |
40459677 | 230 | $technical[] = get_string('technicalinfovariant', 'question', $qa->get_variant()); |
802f8d2a TH |
231 | $technical[] = get_string('technicalinfoquestionsummary', 'question', s($qa->get_question_summary())); |
232 | $technical[] = get_string('technicalinforightsummary', 'question', s($qa->get_right_answer_summary())); | |
40459677 | 233 | $technical[] = get_string('technicalinforesponsesummary', 'question', s($qa->get_response_summary())); |
802f8d2a TH |
234 | $technical[] = get_string('technicalinfostate', 'question', '' . $qa->get_state()); |
235 | ||
05a7d155 | 236 | // Start output. |
c76145d3 TH |
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. | |
05a7d155 TH |
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'); | |
c76145d3 TH |
251 | |
252 | // Output the question. | |
253 | echo $quba->render_question($slot, $options, $displaynumber); | |
254 | ||
c76145d3 | 255 | // Finish the question form. |
05a7d155 TH |
256 | echo html_writer::start_tag('div', array('id' => 'previewcontrols', 'class' => 'controls')); |
257 | echo html_writer::empty_tag('input', $restartdisabled + array('type' => 'submit', | |
009c57d7 | 258 | 'name' => 'restart', 'value' => get_string('restart', 'question'), 'class' => 'btn btn-secondary')); |
05a7d155 | 259 | echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit', |
009c57d7 | 260 | 'name' => 'save', 'value' => get_string('save', 'question'), 'class' => 'btn btn-secondary')); |
05a7d155 | 261 | echo html_writer::empty_tag('input', $filldisabled + array('type' => 'submit', |
009c57d7 | 262 | 'name' => 'fill', 'value' => get_string('fillincorrect', 'question'), 'class' => 'btn btn-secondary')); |
05a7d155 | 263 | echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit', |
009c57d7 | 264 | 'name' => 'finish', 'value' => get_string('submitandfinish', 'question'), 'class' => 'btn btn-secondary')); |
05a7d155 TH |
265 | echo html_writer::end_tag('div'); |
266 | echo html_writer::end_tag('form'); | |
c76145d3 | 267 | |
802f8d2a | 268 | // Output the technical info. |
18056cd8 TH |
269 | print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question') . |
270 | $OUTPUT->help_icon('technicalinfo', 'question'), | |
802f8d2a TH |
271 | 'core_question_preview_techinfo_collapsed', true); |
272 | foreach ($technical as $info) { | |
05a7d155 | 273 | echo html_writer::tag('p', $info, array('class' => 'notifytiny')); |
802f8d2a TH |
274 | } |
275 | print_collapsible_region_end(); | |
276 | ||
616442a2 TH |
277 | // Output a link to export this single question. |
278 | if (question_has_capability_on($question, 'view')) { | |
279 | echo html_writer::link(question_get_export_single_question_url($question), | |
280 | get_string('exportonequestion', 'question')); | |
281 | } | |
282 | ||
93e435b9 | 283 | // Log the preview of this question. |
4ca60a56 | 284 | $event = \core\event\question_viewed::create_from_question_instance($question, $context); |
93e435b9 SB |
285 | $event->trigger(); |
286 | ||
c76145d3 TH |
287 | // Display the settings form. |
288 | $optionsform->display(); | |
289 | ||
fd2e73ab AN |
290 | $PAGE->requires->js_module('core_question_engine'); |
291 | $PAGE->requires->strings_for_js(array( | |
292 | 'closepreview', | |
293 | ), 'question'); | |
72584702 | 294 | $PAGE->requires->yui_module('moodle-question-preview', 'M.question.preview.init'); |
c76145d3 | 295 | echo $OUTPUT->footer(); |
aeb15530 | 296 |