Merge branch 'MDL-70160-function-cache-310' of https://github.com/Peterburnett/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         // Make it impossible to save bad formulas anywhere.
46         $this->validate_question_data($question);
48         // Calculated options.
49         $update = true;
50         $options = $DB->get_record('question_calculated_options',
51                 array('question' => $question->id));
52         if (!$options) {
53             $options = new stdClass();
54             $options->question = $question->id;
55             $options->correctfeedback = '';
56             $options->partiallycorrectfeedback = '';
57             $options->incorrectfeedback = '';
58             $options->id = $DB->insert_record('question_calculated_options', $options);
59         }
60         $options->synchronize = $question->synchronize;
61         $options->single = $question->single;
62         $options->answernumbering = $question->answernumbering;
63         $options->shuffleanswers = $question->shuffleanswers;
64         $options = $this->save_combined_feedback_helper($options, $question, $context, true);
65         $DB->update_record('question_calculated_options', $options);
67         // Get old versions of the objects.
68         if (!$oldanswers = $DB->get_records('question_answers',
69                 array('question' => $question->id), 'id ASC')) {
70             $oldanswers = array();
71         }
72         if (!$oldoptions = $DB->get_records('question_calculated',
73                 array('question' => $question->id), 'answer ASC')) {
74             $oldoptions = array();
75         }
77         // Insert all the new answers.
78         foreach ($question->answer 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 validate_answer($answer) {
160         $error = qtype_calculated_find_formula_errors_in_text($answer);
161         if ($error) {
162             throw new coding_exception($error);
163         }
164     }
166     protected function validate_question_data($question) {
167         parent::validate_question_data($question);
168         $this->validate_text($question->correctfeedback['text']);
169         $this->validate_text($question->partiallycorrectfeedback['text']);
170         $this->validate_text($question->incorrectfeedback['text']);
171     }
173     protected function make_question_instance($questiondata) {
174         question_bank::load_question_definition_classes($this->name());
175         if ($questiondata->options->single) {
176             $class = 'qtype_calculatedmulti_single_question';
177         } else {
178             $class = 'qtype_calculatedmulti_multi_question';
179         }
180         return new $class();
181     }
183     protected function initialise_question_instance(question_definition $question, $questiondata) {
184         question_type::initialise_question_instance($question, $questiondata);
186         $question->shuffleanswers = $questiondata->options->shuffleanswers;
187         $question->answernumbering = $questiondata->options->answernumbering;
188         if (!empty($questiondata->options->layout)) {
189             $question->layout = $questiondata->options->layout;
190         } else {
191             $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
192         }
194         $question->synchronised = $questiondata->options->synchronize;
196         $this->initialise_combined_feedback($question, $questiondata, true);
197         $this->initialise_question_answers($question, $questiondata);
199         foreach ($questiondata->options->answers as $a) {
200             $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength;
201             $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat;
202         }
204         $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
205     }
207     /**
208      * Public override method, created so that make_answer will be available
209      * for use by classes which extend qtype_multichoice_base.
210      *
211      * @param stdClass $answer  Moodle DB question_answers object.
212      * @return question_answer
213      */
214     public function make_answer($answer) {
215         return parent::make_answer($answer);
216     }
218     protected function make_hint($hint) {
219         return question_hint_with_parts::load_from_record($hint);
220     }
222     public function comment_header($question) {
223         $strheader = '';
224         $delimiter = '';
226         $answers = $question->options->answers;
228         foreach ($answers as $key => $answer) {
229             $ans = shorten_text($answer->answer, 17, true);
230             $strheader .= $delimiter.$ans;
231             $delimiter = '<br/><br/>';
232         }
233         return $strheader;
234     }
236     public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
237             $answers, $data, $number) {
239         $comment = new stdClass();
240         $comment->stranswers = array();
241         $comment->outsidelimit = false;
242         $comment->answers = array();
244         $answers = fullclone($answers);
245         foreach ($answers as $key => $answer) {
246             // Evaluate the equations i.e {=5+4).
247             $anssubstituted = $this->substitute_variables($answer->answer, $data);
248             $formulas = $this->find_formulas($anssubstituted);
249             $replaces = [];
250             foreach ($formulas as $formula) {
251                 if ($formulaerrors = qtype_calculated_find_formula_errors($formula)) {
252                     $str = $formulaerrors;
253                 } else {
254                     eval('$str = ' . $formula . ';');
255                 }
256                 $replaces[$formula] = $str;
257             }
258             $anstext = str_replace(array_keys($replaces), array_values($replaces), $anssubstituted);
259             $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext;
260         }
261         return fullclone($comment);
262     }
264     public function get_virtual_qtype() {
265         return question_bank::get_qtype('multichoice');
266     }
268     public function get_possible_responses($questiondata) {
269         if ($questiondata->options->single) {
270             $responses = array();
272             foreach ($questiondata->options->answers as $aid => $answer) {
273                 $responses[$aid] = new question_possible_response($answer->answer,
274                         $answer->fraction);
275             }
277             $responses[null] = question_possible_response::no_response();
278             return array($questiondata->id => $responses);
279         } else {
280             $parts = array();
282             foreach ($questiondata->options->answers as $aid => $answer) {
283                 $parts[$aid] = array($aid =>
284                         new question_possible_response($answer->answer, $answer->fraction));
285             }
287             return $parts;
288         }
289     }
291     public function move_files($questionid, $oldcontextid, $newcontextid) {
292         $fs = get_file_storage();
294         parent::move_files($questionid, $oldcontextid, $newcontextid);
295         $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
296         $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
298         $fs->move_area_files_to_new_context($oldcontextid,
299                 $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid);
300         $fs->move_area_files_to_new_context($oldcontextid,
301                 $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid);
302         $fs->move_area_files_to_new_context($oldcontextid,
303                 $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid);
304     }
306     protected function delete_files($questionid, $contextid) {
307         $fs = get_file_storage();
309         parent::delete_files($questionid, $contextid);
310         $this->delete_files_in_answers($questionid, $contextid, true);
311         $this->delete_files_in_hints($questionid, $contextid);
313         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
314                 'correctfeedback', $questionid);
315         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
316                 'partiallycorrectfeedback', $questionid);
317         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
318                 'incorrectfeedback', $questionid);
319     }