MDL-20636 Previewing a truefalse question in deferred feedback mode now works.
[moodle.git] / question / previewlib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
19 /**
20  * Library functions used by question/preview.php.
21  *
22  * @package    moodlecore
23  * @subpackage questionengine
24  * @copyright  2010 The Open University
25  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
29 /**
30  * Settings form for the preview options.
31  *
32  * @copyright 2009 The Open University
33  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class preview_options_form extends moodleform {
36     public function definition() {
37         $mform = $this->_form;
39         $hiddenofvisible = array(
40             question_display_options::HIDDEN => get_string('notshown', 'question'),
41             question_display_options::VISIBLE => get_string('shown', 'question'),
42         );
44         $mform->addElement('header', 'optionsheader', get_string('changeoptions', 'question'));
46         $behaviours = question_engine::get_behaviour_options($this->_customdata->get_preferred_behaviour());
47         $mform->addElement('select', 'behaviour', get_string('howquestionsbehave', 'question'), $behaviours);
48         $mform->addHelpButton('behaviour', 'howquestionsbehave', 'question');
50         $mform->addElement('text', 'maxmark', get_string('markedoutof', 'question'), array('size' => '5'));
51         $mform->setType('maxmark', PARAM_NUMBER);
53         $mform->addElement('select', 'correctness', get_string('whethercorrect', 'question'), $hiddenofvisible);
55         $marksoptions = array(
56             question_display_options::HIDDEN => get_string('notshown', 'question'),
57             question_display_options::MAX_ONLY => get_string('showmaxmarkonly', 'question'),
58             question_display_options::MARK_AND_MAX => get_string('showmarkandmax', 'question'),
59         );
60         $mform->addElement('select', 'marks', get_string('marks', 'question'), $marksoptions);
62         $mform->addElement('select', 'markdp', get_string('decimalplacesingrades', 'question'),
63                 question_engine::get_dp_options());
65         $mform->addElement('select', 'feedback', get_string('specificfeedback', 'question'), $hiddenofvisible);
67         $mform->addElement('select', 'generalfeedback', get_string('generalfeedback', 'question'), $hiddenofvisible);
69         $mform->addElement('select', 'rightanswer', get_string('rightanswer', 'question'), $hiddenofvisible);
71         $mform->addElement('select', 'history', get_string('responsehistory', 'question'), $hiddenofvisible);
73         $mform->addElement('submit', 'submit', get_string('restartwiththeseoptions', 'question'), $hiddenofvisible);
74     }
75 }
78 /**
79  * Displays question preview options as default and set the options
80  * Setting default, getting and setting user preferences in question preview options.
81  *
82  * @copyright 2010 The Open University
83  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
84  */
85 class question_preview_options extends question_display_options {
86     /** @var string the behaviour to use for this preview. */
87     public $behaviour;
89     /** @var number the maximum mark to use for this preview. */
90     public $maxmark;
92     /** @var string prefix to append to field names to get user_preference names. */
93     const OPTIONPREFIX = 'question_preview_options_';
95     /**
96      * Constructor.
97      */
98     public function __construct($question) {
99         global $CFG;
100         $this->behaviour = 'deferredfeedback';
101         $this->maxmark = $question->defaultmark;
102         $this->correctness = self::VISIBLE;
103         $this->marks = self::MARK_AND_MAX;
104         $this->markdp = get_config('quiz', 'decimalpoints');
105         $this->feedback = self::VISIBLE;
106         $this->numpartscorrect = $this->feedback;
107         $this->generalfeedback = self::VISIBLE;
108         $this->rightanswer = self::VISIBLE;
109         $this->history = self::HIDDEN;
110         $this->flags = self::HIDDEN;
111         $this->manualcomment = self::HIDDEN;
112     }
114     /**
115      * @return array names of the options we store in the user preferences table.
116      */
117     protected function get_user_pref_fields() {
118         return array('behaviour', 'correctness', 'marks', 'markdp', 'feedback',
119                 'generalfeedback', 'rightanswer', 'history');
120     }
122     /**
123      * @return array names and param types of the options we read from the request.
124      */
125     protected function get_field_types() {
126         return array(
127             'behaviour' => PARAM_ALPHA,
128             'maxmark' => PARAM_NUMBER,
129             'correctness' => PARAM_BOOL,
130             'marks' => PARAM_INT,
131             'markdp' => PARAM_INT,
132             'feedback' => PARAM_BOOL,
133             'generalfeedback' => PARAM_BOOL,
134             'rightanswer' => PARAM_BOOL,
135             'history' => PARAM_BOOL,
136         );
137     }
139     /**
140      * Load the value of the options from the user_preferences table.
141      */
142     public function load_user_defaults() {
143         foreach ($this->get_user_pref_fields() as $field) {
144             $this->$field = get_user_preferences(
145                     self::OPTIONPREFIX . $field, $this->$field);
146         }
147         $this->numpartscorrect = $this->feedback;
148     }
150     /**
151      * Save a change to the user's preview options to the database.
152      * @param object $newoptions
153      */
154     public function save_user_preview_options($newoptions) {
155         foreach ($this->get_user_pref_fields() as $field) {
156             if (isset($newoptions->$field)) {
157                 set_user_preference(self::OPTIONPREFIX . $field, $newoptions->$field);
158             }
159         }
160     }
162     /**
163      * Set the value of any fields included in the request.
164      */
165     public function set_from_request() {
166         foreach ($this->get_field_types() as $field => $type) {
167             $this->$field = optional_param($field, $this->$field, $type);
168         }
169         $this->numpartscorrect = $this->feedback;
170     }
172     /**
173      * @return string URL fragment. Parameters needed in the URL when continuing
174      * this preview.
175      */
176     public function get_query_string() {
177         $querystring = array();
178         foreach ($this->get_field_types() as $field => $notused) {
179             if ($field == 'behaviour' || $field == 'maxmark') {
180                 continue;
181             }
182             $querystring[] = $field . '=' . $this->$field;
183         }
184         return implode('&', $querystring);
185     }
189 /**
190  * Called via pluginfile.php -> question_pluginfile to serve files belonging to
191  * a question in a question_attempt when that attempt is a preview.
192  *
193  * @param object $course course settings object
194  * @param object $context context object
195  * @param string $component the name of the component we are serving files for.
196  * @param string $filearea the name of the file area.
197  * @param array $args the remaining bits of the file path.
198  * @param bool $forcedownload whether the user must be forced to download the file.
199  * @return bool false if file not found, does not return if found - justsend the file
200  */
201 function question_preview_question_pluginfile($course, $context, $component,
202         $filearea, $attemptid, $questionid, $args, $forcedownload) {
203     global $USER, $SESSION, $DB, $CFG;
204     require_once($CFG->dirroot . '/mod/quiz/locallib.php');
206     if (!$question = $DB->get_record('question', array('id' => $questionid))) {
207         return send_file_not_found();
208     }
210     if (!question_has_capability_on($question, 'use', $question->category)) {
211         send_file_not_found();
212     }
214     if (!isset($SESSION->quizpreview->states) || $SESSION->quizpreview->questionid != $questionid) {
215         send_file_not_found();
216     }
218     $states = end($SESSION->quizpreview->states);
219     if (!array_key_exists($question->id, $states)) {
220         send_file_not_found();
221     }
222     $state = $states[$question->id];
224     // Build fake cmoptions
225     $quiz = new cmoptions;
226     $quiz->id = 0;
227     $quiz->review = get_config('quiz', 'review');
228     if (empty($course->id)) {
229         $quiz->course = SITEID;
230     } else {
231         $quiz->course = $course->id;
232     }
233     $quiz->decimalpoints = get_config('quiz', 'decimalpoints');
235     $questions[$question->id] = $question;
236     get_question_options($questions);
238     // Build fake attempt
239     $timenow = time();
240     $attempt = new stdClass();
241     $attempt->quiz = $quiz->id;
242     $attempt->userid = $USER->id;
243     $attempt->attempt = 0;
244     $attempt->sumgrades = 0;
245     $attempt->timestart = $timenow;
246     $attempt->timefinish = 0;
247     $attempt->timemodified = $timenow;
248     $attempt->uniqueid = 0;
249     $attempt->id = 0;
250     $attempt->layout = $question->id;
252     $options = quiz_get_renderoptions($quiz, $attempt, $context, $state);
253     $options->noeditlink = true;
254     // TODO: mulitichoice type needs quiz id to get maxgrade
255     $options->quizid = 0;
257     if (!question_check_file_access($question, $state, $options, $context->id, $component,
258             $filearea, $args, $forcedownload)) {
259         send_file_not_found();
260     }
262     $fs = get_file_storage();
263     $relativepath = implode('/', $args);
264     $fullpath = "/$context->id/$component/$filearea/$relativepath";
265     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
266         send_file_not_found();
267     }
269     send_stored_file($file, 0, 0, $forcedownload);
272 /**
273  * The the URL to use for actions relating to this preview.
274  * @param integer $questionid the question being previewed.
275  * @param integer $qubaid the id of the question usage for this preview.
276  * @param question_preview_options $options the options in use.
277  */
278 function question_preview_action_url($questionid, $qubaid,
279         question_preview_options $options) {
280     global $CFG;
281     $url = $CFG->wwwroot . '/question/preview.php?id=' . $questionid . '&previewid=' . $qubaid;
282     return $url . '&' . $options->get_query_string();
285 /**
286  * Delete the current preview, if any, and redirect to start a new preview.
287  * @param integer $previewid
288  * @param integer $questionid
289  * @param object $displayoptions
290  */
291 function restart_preview($previewid, $questionid, $displayoptions) {
292     global $DB;
294     if ($previewid) {
295         $transaction = $DB->start_delegated_transaction();
296         question_engine::delete_questions_usage_by_activity($previewid);
297         $transaction->allow_commit();
298     }
299     redirect(question_preview_url($questionid, $displayoptions->behaviour, $displayoptions->maxmark, $displayoptions));