Merge branch 'MDL-67514-310' of https://github.com/paulholden/moodle into MOODLE_310_...
[moodle.git] / question / preview.php
CommitLineData
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 33require_once(__DIR__ . '/../config.php');
c76145d3 34require_once($CFG->libdir . '/questionlib.php');
1fcf0ca8 35require_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 */
42define('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.
50if ($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}
67question_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 81if ($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.
132if ($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.
147if (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
203if ($question->length) {
204 $displaynumber = '1';
205} else {
206 $displaynumber = 'i';
207}
05a7d155
TH
208$restartdisabled = array();
209$finishdisabled = array();
210$filldisabled = array();
c76145d3 211if ($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.
216if (is_null($quba->get_correct_response($slot))) {
05a7d155 217 $filldisabled = array('disabled' => 'disabled');
c76145d3
TH
218}
219if (!$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);
241echo $OUTPUT->header();
242
243// Start the question form.
05a7d155
TH
244echo html_writer::start_tag('form', array('method' => 'post', 'action' => $actionurl,
245 'enctype' => 'multipart/form-data', 'id' => 'responseform'));
246echo html_writer::start_tag('div');
247echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
248echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'slots', 'value' => $slot));
249echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'scrollpos', 'value' => '', 'id' => 'scrollpos'));
250echo html_writer::end_tag('div');
c76145d3
TH
251
252// Output the question.
253echo $quba->render_question($slot, $options, $displaynumber);
254
c76145d3 255// Finish the question form.
05a7d155
TH
256echo html_writer::start_tag('div', array('id' => 'previewcontrols', 'class' => 'controls'));
257echo html_writer::empty_tag('input', $restartdisabled + array('type' => 'submit',
009c57d7 258 'name' => 'restart', 'value' => get_string('restart', 'question'), 'class' => 'btn btn-secondary'));
05a7d155 259echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit',
009c57d7 260 'name' => 'save', 'value' => get_string('save', 'question'), 'class' => 'btn btn-secondary'));
05a7d155 261echo html_writer::empty_tag('input', $filldisabled + array('type' => 'submit',
009c57d7 262 'name' => 'fill', 'value' => get_string('fillincorrect', 'question'), 'class' => 'btn btn-secondary'));
05a7d155 263echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit',
009c57d7 264 'name' => 'finish', 'value' => get_string('submitandfinish', 'question'), 'class' => 'btn btn-secondary'));
05a7d155
TH
265echo html_writer::end_tag('div');
266echo html_writer::end_tag('form');
c76145d3 267
802f8d2a 268// Output the technical info.
0ab0a293 269print_collapsible_region_start('', 'techinfo', get_string('technicalinfo', 'question'),
270 'core_question_preview_techinfo_collapsed', true, false, $OUTPUT->help_icon('technicalinfo', 'question'));
802f8d2a 271foreach ($technical as $info) {
05a7d155 272 echo html_writer::tag('p', $info, array('class' => 'notifytiny'));
802f8d2a
TH
273}
274print_collapsible_region_end();
275
616442a2
TH
276// Output a link to export this single question.
277if (question_has_capability_on($question, 'view')) {
278 echo html_writer::link(question_get_export_single_question_url($question),
279 get_string('exportonequestion', 'question'));
280}
281
93e435b9 282// Log the preview of this question.
4ca60a56 283$event = \core\event\question_viewed::create_from_question_instance($question, $context);
93e435b9
SB
284$event->trigger();
285
c76145d3
TH
286// Display the settings form.
287$optionsform->display();
288
fd2e73ab
AN
289$PAGE->requires->js_module('core_question_engine');
290$PAGE->requires->strings_for_js(array(
291 'closepreview',
292), 'question');
72584702 293$PAGE->requires->yui_module('moodle-question-preview', 'M.question.preview.init');
c76145d3 294echo $OUTPUT->footer();
aeb15530 295