2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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.
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 // Get and validate question id.
38 $id = required_param('id', PARAM_INT
);
39 $question = question_bank
::load_question($id);
41 // Were we given a particular context to run the question in?
42 // This affects things like filter settings, or forced theme or language.
43 if ($cmid = optional_param('cmid', 0, PARAM_INT
)) {
44 $cm = get_coursemodule_from_id(false, $cmid);
45 require_login($cm->course
, false, $cm);
46 $context = get_context_instance(CONTEXT_MODULE
, $cmid);
48 } else if ($courseid = optional_param('courseid', 0, PARAM_INT
)) {
49 require_login($courseid);
50 $context = get_context_instance(CONTEXT_COURSE
, $courseid);
54 $category = $DB->get_record('question_categories',
55 array('id' => $question->category
), '*', MUST_EXIST
);
56 $context = get_context_instance_by_id($category->contextid
);
57 $PAGE->set_context($context);
58 // Note that in the other cases, require_login will set the correct page context.
60 question_require_capability_on($question, 'use');
61 $PAGE->set_pagelayout('popup');
63 // Get and validate display options.
64 $maxvariant = $question->get_num_variants();
65 $options = new question_preview_options($question);
66 $options->load_user_defaults();
67 $options->set_from_request();
68 $PAGE->set_url(question_preview_url($id, $options->behaviour
, $options->maxmark
,
69 $options, $options->variant
, $context));
71 // Get and validate exitsing preview, or start a new one.
72 $previewid = optional_param('previewid', 0, PARAM_INT
);
75 if (!isset($SESSION->question_previews
[$previewid])) {
76 print_error('notyourpreview', 'question');
79 $quba = question_engine
::load_questions_usage_by_activity($previewid);
80 } catch (Exception
$e) {
81 print_error('submissionoutofsequencefriendlymessage', 'question',
82 question_preview_url($question->id
, $options->behaviour
,
83 $options->maxmark
, $options, $options->variant
, $context), null, $e);
85 $slot = $quba->get_first_question_number();
86 $usedquestion = $quba->get_question($slot);
87 if ($usedquestion->id
!= $question->id
) {
88 print_error('questionidmismatch', 'question');
90 $question = $usedquestion;
91 $options->variant
= $quba->get_variant($slot);
94 $quba = question_engine
::make_questions_usage_by_activity(
95 'core_question_preview', $context);
96 $quba->set_preferred_behaviour($options->behaviour
);
97 $slot = $quba->add_question($question, $options->maxmark
);
99 if ($options->variant
) {
100 $options->variant
= min($maxvariant, max(1, $options->variant
));
102 $options->variant
= rand(1, $maxvariant);
105 $quba->start_question($slot, $options->variant
);
107 $transaction = $DB->start_delegated_transaction();
108 question_engine
::save_questions_usage_by_activity($quba);
109 $transaction->allow_commit();
111 $SESSION->question_previews
[$quba->get_id()] = true;
113 $options->behaviour
= $quba->get_preferred_behaviour();
114 $options->maxmark
= $quba->get_question_max_mark($slot);
116 // Create the settings form, and initialise the fields.
117 $optionsform = new preview_options_form(question_preview_form_url($question->id
, $context),
118 array('quba' => $quba, 'maxvariant' => $maxvariant));
119 $optionsform->set_data($options);
121 // Process change of settings, if that was requested.
122 if ($newoptions = $optionsform->get_submitted_data()) {
123 // Set user preferences
124 $options->save_user_preview_options($newoptions);
125 if (!isset($newoptions->variant
)) {
126 $newoptions->variant
= $options->variant
;
128 restart_preview($previewid, $question->id
, $newoptions, $context);
131 // Prepare a URL that is used in various places.
132 $actionurl = question_preview_action_url($question->id
, $quba->get_id(), $options, $context);
134 // Process any actions from the buttons at the bottom of the form.
135 if (data_submitted() && confirm_sesskey()) {
136 if (optional_param('restart', false, PARAM_BOOL
)) {
137 restart_preview($previewid, $question->id
, $options, $context);
139 } else if (optional_param('fill', null, PARAM_BOOL
)) {
140 $correctresponse = $quba->get_correct_response($slot);
141 $quba->process_action($slot, $correctresponse);
143 $transaction = $DB->start_delegated_transaction();
144 question_engine
::save_questions_usage_by_activity($quba);
145 $transaction->allow_commit();
147 redirect($actionurl);
149 } else if (optional_param('finish', null, PARAM_BOOL
)) {
151 $quba->process_all_actions();
152 } catch (question_out_of_sequence_exception
$e) {
153 print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
155 $quba->finish_all_questions();
157 $transaction = $DB->start_delegated_transaction();
158 question_engine
::save_questions_usage_by_activity($quba);
159 $transaction->allow_commit();
160 redirect($actionurl);
164 $quba->process_all_actions();
165 } catch (question_out_of_sequence_exception
$e) {
166 print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
169 $transaction = $DB->start_delegated_transaction();
170 question_engine
::save_questions_usage_by_activity($quba);
171 $transaction->allow_commit();
173 $scrollpos = optional_param('scrollpos', '', PARAM_RAW
);
174 if ($scrollpos !== '') {
175 $actionurl->param('scrollpos', (int) $scrollpos);
177 redirect($actionurl);
181 if ($question->length
) {
182 $displaynumber = '1';
184 $displaynumber = 'i';
186 $restartdisabled = '';
187 $finishdisabled = '';
189 if ($quba->get_question_state($slot)->is_finished()) {
190 $finishdisabled = ' disabled="disabled"';
191 $filldisabled = ' disabled="disabled"';
194 $restartdisabled = ' disabled="disabled"';
198 $title = get_string('previewquestion', 'question', format_string($question->name
));
199 $headtags = question_engine
::initialise_js() . $quba->render_question_head_html($slot);
200 $PAGE->set_title($title);
201 $PAGE->set_heading($title);
202 echo $OUTPUT->header();
204 // Start the question form.
205 echo '<form method="post" action="' . $actionurl .
206 '" enctype="multipart/form-data" id="responseform">', "\n";
207 echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />', "\n";
208 echo '<input type="hidden" name="slots" value="' . $slot . '" />', "\n";
210 // Output the question.
211 echo $quba->render_question($slot, $options, $displaynumber);
213 echo '<p class="notifytiny">' . get_string('behaviourbeingused', 'question',
214 question_engine
::get_behaviour_name(
215 $quba->get_question_attempt($slot)->get_behaviour_name())) . '</p>';
216 // Finish the question form.
217 echo '<div id="previewcontrols" class="controls">';
218 echo '<input type="submit" name="restart"' . $restartdisabled .
219 ' value="' . get_string('restart', 'question') . '" />', "\n";
220 echo '<input type="submit" name="fill"' . $filldisabled .
221 ' value="' . get_string('fillincorrect', 'question') . '" />', "\n";
222 echo '<input type="submit" name="finish"' . $finishdisabled .
223 ' value="' . get_string('submitandfinish', 'question') . '" />', "\n";
224 echo '<input type="hidden" name="scrollpos" id="scrollpos" value="" />';
228 // Display the settings form.
229 $optionsform->display();
231 $PAGE->requires
->js_init_call('M.core_question_preview.init', null, false, array(
232 'name' => 'core_question_preview',
233 'fullpath' => '/question/preview.js',
234 'requires' => array('base', 'dom', 'event-delegate', 'event-key', 'core_question_engine'),
236 array('closepreview', 'question'),
238 echo $OUTPUT->footer();