Merge branch 'wip-MDL-40136-m26' of git://github.com/samhemelryk/moodle
[moodle.git] / question / type / calculatedmulti / questiontype.php
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.
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/>.
17 /**
18  * Question type class for the calculated multiple-choice question type.
19  *
20  * @package    qtype
21  * @subpackage calculatedmulti
22  * @copyright  2009 Pierre Pichet
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->dirroot . '/question/type/multichoice/questiontype.php');
30 require_once($CFG->dirroot . '/question/type/calculated/questiontype.php');
33 /**
34  * The calculated multiple-choice question type.
35  *
36  * @copyright  2009 Pierre Pichet
37  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class qtype_calculatedmulti extends qtype_calculated {
41     public function save_question_options($question) {
42         global $CFG, $DB;
43         $context = $question->context;
45         // Calculated options.
46         $update = true;
47         $options = $DB->get_record('question_calculated_options',
48                 array('question' => $question->id));
49         if (!$options) {
50             $options = new stdClass();
51             $options->question = $question->id;
52             $options->correctfeedback = '';
53             $options->partiallycorrectfeedback = '';
54             $options->incorrectfeedback = '';
55             $options->id = $DB->insert_record('question_calculated_options', $options);
56         }
57         $options->synchronize = $question->synchronize;
58         $options->single = $question->single;
59         $options->answernumbering = $question->answernumbering;
60         $options->shuffleanswers = $question->shuffleanswers;
61         $options = $this->save_combined_feedback_helper($options, $question, $context, true);
62         $DB->update_record('question_calculated_options', $options);
64         // Get old versions of the objects.
65         if (!$oldanswers = $DB->get_records('question_answers',
66                 array('question' => $question->id), 'id ASC')) {
67             $oldanswers = array();
68         }
69         if (!$oldoptions = $DB->get_records('question_calculated',
70                 array('question' => $question->id), 'answer ASC')) {
71             $oldoptions = array();
72         }
74         // Insert all the new answers.
75         if (isset($question->answer) && !isset($question->answers)) {
76             $question->answers = $question->answer;
77         }
78         foreach ($question->answers as $key => $answerdata) {
79             if (is_array($answerdata)) {
80                 $answerdata = $answerdata['text'];
81             }
82             if (trim($answerdata) == '') {
83                 continue;
84             }
86             // Update an existing answer if possible.
87             $answer = array_shift($oldanswers);
88             if (!$answer) {
89                 $answer = new stdClass();
90                 $answer->question = $question->id;
91                 $answer->answer   = '';
92                 $answer->feedback = '';
93                 $answer->id       = $DB->insert_record('question_answers', $answer);
94             }
96             if (is_array($answerdata)) {
97                 // Doing an import.
98                 $answer->answer = $this->import_or_save_files($answerdata,
99                         $context, 'question', 'answer', $answer->id);
100                 $answer->answerformat = $answerdata['format'];
101             } else {
102                 // Saving the form.
103                 $answer->answer = $answerdata;
104                 $answer->answerformat = FORMAT_HTML;
105             }
106             $answer->fraction = $question->fraction[$key];
107             $answer->feedback = $this->import_or_save_files($question->feedback[$key],
108                     $context, 'question', 'answerfeedback', $answer->id);
109             $answer->feedbackformat = $question->feedback[$key]['format'];
111             $DB->update_record("question_answers", $answer);
113             // Set up the options object.
114             if (!$options = array_shift($oldoptions)) {
115                 $options = new stdClass();
116             }
117             $options->question            = $question->id;
118             $options->answer              = $answer->id;
119             $options->tolerance           = trim($question->tolerance[$key]);
120             $options->tolerancetype       = trim($question->tolerancetype[$key]);
121             $options->correctanswerlength = trim($question->correctanswerlength[$key]);
122             $options->correctanswerformat = trim($question->correctanswerformat[$key]);
124             // Save options.
125             if (isset($options->id)) {
126                 // Reusing existing record.
127                 $DB->update_record('question_calculated', $options);
128             } else {
129                 // New options.
130                 $DB->insert_record('question_calculated', $options);
131             }
132         }
134         // Delete old answer records.
135         if (!empty($oldanswers)) {
136             foreach ($oldanswers as $oa) {
137                 $DB->delete_records('question_answers', array('id' => $oa->id));
138             }
139         }
140         if (!empty($oldoptions)) {
141             foreach ($oldoptions as $oo) {
142                 $DB->delete_records('question_calculated', array('id' => $oo->id));
143             }
144         }
146         $this->save_hints($question, true);
148         if (isset($question->import_process) && $question->import_process) {
149             $this->import_datasets($question);
150         }
151         // Report any problems.
152         if (!empty($result->notice)) {
153             return $result;
154         }
156         return true;
157     }
159     protected function make_question_instance($questiondata) {
160         question_bank::load_question_definition_classes($this->name());
161         if ($questiondata->options->single) {
162             $class = 'qtype_calculatedmulti_single_question';
163         } else {
164             $class = 'qtype_calculatedmulti_multi_question';
165         }
166         return new $class();
167     }
169     protected function initialise_question_instance(question_definition $question, $questiondata) {
170         question_type::initialise_question_instance($question, $questiondata);
172         $question->shuffleanswers = $questiondata->options->shuffleanswers;
173         $question->answernumbering = $questiondata->options->answernumbering;
174         if (!empty($questiondata->options->layout)) {
175             $question->layout = $questiondata->options->layout;
176         } else {
177             $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
178         }
180         $question->synchronised = $questiondata->options->synchronize;
182         $this->initialise_combined_feedback($question, $questiondata, true);
183         $this->initialise_question_answers($question, $questiondata);
185         foreach ($questiondata->options->answers as $a) {
186             $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength;
187             $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat;
188         }
190         $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
191     }
193     public function comment_header($question) {
194         $strheader = '';
195         $delimiter = '';
197         $answers = $question->options->answers;
199         foreach ($answers as $key => $answer) {
200             if (is_string($answer)) {
201                 $strheader .= $delimiter.$answer;
202             } else {
203                 $strheader .= $delimiter.$answer->answer;
204             }
205             $delimiter = '<br/>';
206         }
207         return $strheader;
208     }
210     public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
211             $answers, $data, $number) {
212         global $DB;
213         $comment = new stdClass();
214         $comment->stranswers = array();
215         $comment->outsidelimit = false;
216         $comment->answers = array();
218         $answers = fullclone($answers);
219         $errors = '';
220         $delimiter = ': ';
221         foreach ($answers as $key => $answer) {
222             $answer->answer = $this->substitute_variables($answer->answer, $data);
223             // Evaluate the equations i.e {=5+4).
224             $qtext = '';
225             $qtextremaining = $answer->answer;
226             while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
227                 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
228                 $qtext =$qtext.$qtextsplits[0];
229                 $qtextremaining = $qtextsplits[1];
230                 if (empty($regs1[1])) {
231                     $str = '';
232                 } else {
233                     if ($formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
234                         $str=$formulaerrors;
235                     } else {
236                         eval('$str = '.$regs1[1].';');
237                     }
238                 }
239                 $qtext = $qtext.$str;
240             }
241             $answer->answer = $qtext.$qtextremaining;
242             $comment->stranswers[$key] = $answer->answer;
243         }
244         return fullclone($comment);
245     }
247     public function get_virtual_qtype() {
248         return question_bank::get_qtype('multichoice');
249     }
251     public function get_possible_responses($questiondata) {
252         if ($questiondata->options->single) {
253             $responses = array();
255             foreach ($questiondata->options->answers as $aid => $answer) {
256                 $responses[$aid] = new question_possible_response($answer->answer,
257                         $answer->fraction);
258             }
260             $responses[null] = question_possible_response::no_response();
261             return array($questiondata->id => $responses);
262         } else {
263             $parts = array();
265             foreach ($questiondata->options->answers as $aid => $answer) {
266                 $parts[$aid] = array($aid =>
267                         new question_possible_response($answer->answer, $answer->fraction));
268             }
270             return $parts;
271         }
272     }
274     public function move_files($questionid, $oldcontextid, $newcontextid) {
275         $fs = get_file_storage();
277         parent::move_files($questionid, $oldcontextid, $newcontextid);
278         $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
279         $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
281         $fs->move_area_files_to_new_context($oldcontextid,
282                 $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid);
283         $fs->move_area_files_to_new_context($oldcontextid,
284                 $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid);
285         $fs->move_area_files_to_new_context($oldcontextid,
286                 $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid);
287     }
289     protected function delete_files($questionid, $contextid) {
290         $fs = get_file_storage();
292         parent::delete_files($questionid, $contextid);
293         $this->delete_files_in_answers($questionid, $contextid, true);
294         $this->delete_files_in_hints($questionid, $contextid);
296         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
297                 'correctfeedback', $questionid);
298         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
299                 'partiallycorrectfeedback', $questionid);
300         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
301                 'incorrectfeedback', $questionid);
302     }