Merge branch 'MDL-31680' of https://github.com/ppichet/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             $ans = shorten_text($answer->answer, 17, true);
201             $strheader .= $delimiter.$ans;
202             $delimiter = '<br/><br/>';
203         }
204         return $strheader;
205     }
207     public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
208             $answers, $data, $number) {
209         global $DB;
210         $comment = new stdClass();
211         $comment->stranswers = array();
212         $comment->outsidelimit = false;
213         $comment->answers = array();
215         $answers = fullclone($answers);
216         $errors = '';
217         $delimiter = ': ';
218         foreach ($answers as $key => $answer) {
219             $anssubstituted = $this->substitute_variables($answer->answer, $data);
220             // Evaluate the equations i.e {=5+4).
221             $anstext = '';
222             $anstextremaining = $anssubstituted;
223             while (preg_match('~\{=([^[:space:]}]*)}~', $anstextremaining, $regs1)) {
224                 $anstextsplits = explode($regs1[0], $anstextremaining, 2);
225                 $anstext =$anstext.$anstextsplits[0];
226                 $anstextremaining = $anstextsplits[1];
227                 if (empty($regs1[1])) {
228                     $str = '';
229                 } else {
230                     if ($formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
231                         $str=$formulaerrors;
232                     } else {
233                         eval('$str = '.$regs1[1].';');
234                     }
235                 }
236                 $anstext = $anstext.$str;
237             }
238             $anstext .= $anstextremaining;
239             $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext;
240         }
241         return fullclone($comment);
242     }
244     public function get_virtual_qtype() {
245         return question_bank::get_qtype('multichoice');
246     }
248     public function get_possible_responses($questiondata) {
249         if ($questiondata->options->single) {
250             $responses = array();
252             foreach ($questiondata->options->answers as $aid => $answer) {
253                 $responses[$aid] = new question_possible_response($answer->answer,
254                         $answer->fraction);
255             }
257             $responses[null] = question_possible_response::no_response();
258             return array($questiondata->id => $responses);
259         } else {
260             $parts = array();
262             foreach ($questiondata->options->answers as $aid => $answer) {
263                 $parts[$aid] = array($aid =>
264                         new question_possible_response($answer->answer, $answer->fraction));
265             }
267             return $parts;
268         }
269     }
271     public function move_files($questionid, $oldcontextid, $newcontextid) {
272         $fs = get_file_storage();
274         parent::move_files($questionid, $oldcontextid, $newcontextid);
275         $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
276         $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
278         $fs->move_area_files_to_new_context($oldcontextid,
279                 $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid);
280         $fs->move_area_files_to_new_context($oldcontextid,
281                 $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid);
282         $fs->move_area_files_to_new_context($oldcontextid,
283                 $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid);
284     }
286     protected function delete_files($questionid, $contextid) {
287         $fs = get_file_storage();
289         parent::delete_files($questionid, $contextid);
290         $this->delete_files_in_answers($questionid, $contextid, true);
291         $this->delete_files_in_hints($questionid, $contextid);
293         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
294                 'correctfeedback', $questionid);
295         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
296                 'partiallycorrectfeedback', $questionid);
297         $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
298                 'incorrectfeedback', $questionid);
299     }