MDL-30514 registration: table aliases don't use AS keyword
[moodle.git] / question / preview.php
blob6be4f9570a1676469b89318fea43bb99b6bc32d1
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.
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/>.
17 /**
18 * This page displays a preview of a question
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.
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
33 require_once(dirname(__FILE__) . '/../config.php');
34 require_once($CFG->libdir . '/questionlib.php');
35 require_once(dirname(__FILE__) . '/previewlib.php');
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
42 define('QUESTION_PREVIEW_MAX_VARIANTS', 100);
44 // Get and validate question id.
45 $id = required_param('id', PARAM_INT);
46 $question = question_bank::load_question($id);
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 = get_context_instance(CONTEXT_MODULE, $cmid);
55 } else if ($courseid = optional_param('courseid', 0, PARAM_INT)) {
56 require_login($courseid);
57 $context = get_context_instance(CONTEXT_COURSE, $courseid);
59 } else {
60 require_login();
61 $category = $DB->get_record('question_categories',
62 array('id' => $question->category), '*', MUST_EXIST);
63 $context = get_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.
67 question_require_capability_on($question, 'use');
68 $PAGE->set_pagelayout('popup');
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));
78 // Get and validate existing preview, or start a new one.
79 $previewid = optional_param('previewid', 0, PARAM_INT);
81 if ($previewid) {
82 try {
83 $quba = question_engine::load_questions_usage_by_activity($previewid);
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);
93 if ($quba->get_owning_context()->instanceid != $USER->id) {
94 print_error('notyourpreview', 'question');
97 $slot = $quba->get_first_question_number();
98 $usedquestion = $quba->get_question($slot);
99 if ($usedquestion->id != $question->id) {
100 print_error('questionidmismatch', 'question');
102 $question = $usedquestion;
103 $options->variant = $quba->get_variant($slot);
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);
111 if ($options->variant) {
112 $options->variant = min($maxvariant, max(1, $options->variant));
113 } else {
114 $options->variant = rand(1, $maxvariant);
117 $quba->start_question($slot, $options->variant);
119 $transaction = $DB->start_delegated_transaction();
120 question_engine::save_questions_usage_by_activity($quba);
121 $transaction->allow_commit();
123 $options->behaviour = $quba->get_preferred_behaviour();
124 $options->maxmark = $quba->get_question_max_mark($slot);
126 // Create the settings form, and initialise the fields.
127 $optionsform = new preview_options_form(question_preview_form_url($question->id, $context),
128 array('quba' => $quba, 'maxvariant' => $maxvariant));
129 $optionsform->set_data($options);
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;
138 restart_preview($previewid, $question->id, $newoptions, $context);
141 // Prepare a URL that is used in various places.
142 $actionurl = question_preview_action_url($question->id, $quba->get_id(), $options, $context);
144 // Process any actions from the buttons at the bottom of the form.
145 if (data_submitted() && confirm_sesskey()) {
147 try {
149 if (optional_param('restart', false, PARAM_BOOL)) {
150 restart_preview($previewid, $question->id, $options, $context);
152 } else if (optional_param('fill', null, PARAM_BOOL)) {
153 $correctresponse = $quba->get_correct_response($slot);
154 if (!is_null($correctresponse)) {
155 $quba->process_action($slot, $correctresponse);
157 $transaction = $DB->start_delegated_transaction();
158 question_engine::save_questions_usage_by_activity($quba);
159 $transaction->allow_commit();
161 redirect($actionurl);
163 } else if (optional_param('finish', null, PARAM_BOOL)) {
164 $quba->process_all_actions();
165 $quba->finish_all_questions();
167 $transaction = $DB->start_delegated_transaction();
168 question_engine::save_questions_usage_by_activity($quba);
169 $transaction->allow_commit();
170 redirect($actionurl);
172 } else {
173 $quba->process_all_actions();
175 $transaction = $DB->start_delegated_transaction();
176 question_engine::save_questions_usage_by_activity($quba);
177 $transaction->allow_commit();
179 $scrollpos = optional_param('scrollpos', '', PARAM_RAW);
180 if ($scrollpos !== '') {
181 $actionurl->param('scrollpos', (int) $scrollpos);
183 redirect($actionurl);
186 } catch (question_out_of_sequence_exception $e) {
187 print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
189 } catch (Exception $e) {
190 // This sucks, if we display our own custom error message, there is no way
191 // to display the original stack trace.
192 $debuginfo = '';
193 if (!empty($e->debuginfo)) {
194 $debuginfo = $e->debuginfo;
196 print_error('errorprocessingresponses', 'question', $actionurl,
197 $e->getMessage(), $debuginfo);
201 if ($question->length) {
202 $displaynumber = '1';
203 } else {
204 $displaynumber = 'i';
206 $restartdisabled = '';
207 $finishdisabled = '';
208 $filldisabled = '';
209 if ($quba->get_question_state($slot)->is_finished()) {
210 $finishdisabled = ' disabled="disabled"';
211 $filldisabled = ' disabled="disabled"';
213 // If question type cannot give us a correct response, disable this button.
214 if (is_null($quba->get_correct_response($slot))) {
215 $filldisabled = ' disabled="disabled"';
217 if (!$previewid) {
218 $restartdisabled = ' disabled="disabled"';
221 // Output
222 $title = get_string('previewquestion', 'question', format_string($question->name));
223 $headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot);
224 $PAGE->set_title($title);
225 $PAGE->set_heading($title);
226 echo $OUTPUT->header();
228 // Start the question form.
229 echo '<form method="post" action="' . $actionurl .
230 '" enctype="multipart/form-data" id="responseform">', "\n";
231 echo '<div>';
232 echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />', "\n";
233 echo '<input type="hidden" name="slots" value="' . $slot . '" />', "\n";
234 echo '</div>';
236 // Output the question.
237 echo $quba->render_question($slot, $options, $displaynumber);
239 echo '<p class="notifytiny">' . get_string('behaviourbeingused', 'question',
240 question_engine::get_behaviour_name(
241 $quba->get_question_attempt($slot)->get_behaviour_name())) . '</p>';
242 // Finish the question form.
243 echo '<div id="previewcontrols" class="controls">';
244 echo '<input type="submit" name="restart"' . $restartdisabled .
245 ' value="' . get_string('restart', 'question') . '" />', "\n";
246 echo '<input type="submit" name="fill"' . $filldisabled .
247 ' value="' . get_string('fillincorrect', 'question') . '" />', "\n";
248 echo '<input type="submit" name="finish"' . $finishdisabled .
249 ' value="' . get_string('submitandfinish', 'question') . '" />', "\n";
250 echo '<input type="hidden" name="scrollpos" id="scrollpos" value="" />';
251 echo '</div>';
252 echo '</form>';
254 // Display the settings form.
255 $optionsform->display();
257 $PAGE->requires->js_init_call('M.core_question_preview.init', null, false, array(
258 'name' => 'core_question_preview',
259 'fullpath' => '/question/preview.js',
260 'requires' => array('base', 'dom', 'event-delegate', 'event-key', 'core_question_engine'),
261 'strings' => array(
262 array('closepreview', 'question'),
263 )));
264 echo $OUTPUT->footer();