MDL-63459 qtype_calculatedmulti: unit test should test this qtype
[moodle.git] / question / type / calculatedmulti / questiontype.php
CommitLineData
2d279432 1<?php
fe6ce234
DC
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/>.
16
d3603157
TH
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 */
25
26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
cdece95e
TH
29require_once($CFG->dirroot . '/question/type/multichoice/questiontype.php');
30require_once($CFG->dirroot . '/question/type/calculated/questiontype.php');
31
a17b297d 32
d3603157
TH
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 */
29b68914 39class qtype_calculatedmulti extends qtype_calculated {
f5382dd2 40
29b68914
TH
41 public function save_question_options($question) {
42 global $CFG, $DB;
fe6ce234 43 $context = $question->context;
e6d76583 44
66de66fe
TH
45 // Make it impossible to save bad formulas anywhere.
46 $this->validate_question_data($question);
47
3d9645ae 48 // Calculated options.
29b68914
TH
49 $update = true;
50 $options = $DB->get_record('question_calculated_options',
51 array('question' => $question->id));
2d279432 52 if (!$options) {
0ff4bd08 53 $options = new stdClass();
2d279432 54 $options->question = $question->id;
e6d76583
TH
55 $options->correctfeedback = '';
56 $options->partiallycorrectfeedback = '';
57 $options->incorrectfeedback = '';
58 $options->id = $DB->insert_record('question_calculated_options', $options);
2d279432
PP
59 }
60 $options->synchronize = $question->synchronize;
2d279432
PP
61 $options->single = $question->single;
62 $options->answernumbering = $question->answernumbering;
63 $options->shuffleanswers = $question->shuffleanswers;
e6d76583
TH
64 $options = $this->save_combined_feedback_helper($options, $question, $context, true);
65 $DB->update_record('question_calculated_options', $options);
2d279432 66
3d9645ae 67 // Get old versions of the objects.
29b68914
TH
68 if (!$oldanswers = $DB->get_records('question_answers',
69 array('question' => $question->id), 'id ASC')) {
2d279432
PP
70 $oldanswers = array();
71 }
29b68914
TH
72 if (!$oldoptions = $DB->get_records('question_calculated',
73 array('question' => $question->id), 'answer ASC')) {
2d279432
PP
74 $oldoptions = array();
75 }
76
3d9645ae 77 // Insert all the new answers.
66de66fe 78 foreach ($question->answer as $key => $answerdata) {
e6d76583
TH
79 if (is_array($answerdata)) {
80 $answerdata = $answerdata['text'];
cde2709a 81 }
e6d76583
TH
82 if (trim($answerdata) == '') {
83 continue;
84 }
85
86 // Update an existing answer if possible.
87 $answer = array_shift($oldanswers);
88 if (!$answer) {
0ff4bd08 89 $answer = new stdClass();
2d279432 90 $answer->question = $question->id;
e6d76583
TH
91 $answer->answer = '';
92 $answer->feedback = '';
93 $answer->id = $DB->insert_record('question_answers', $answer);
94 }
95
96 if (is_array($answerdata)) {
3d9645ae 97 // Doing an import.
e6d76583
TH
98 $answer->answer = $this->import_or_save_files($answerdata,
99 $context, 'question', 'answer', $answer->id);
100 $answer->answerformat = $answerdata['format'];
101 } else {
3d9645ae 102 // Saving the form.
e6d76583
TH
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'];
110
111 $DB->update_record("question_answers", $answer);
112
3d9645ae 113 // Set up the options object.
e6d76583
TH
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]);
123
3d9645ae 124 // Save options.
e6d76583 125 if (isset($options->id)) {
3d9645ae 126 // Reusing existing record.
e6d76583
TH
127 $DB->update_record('question_calculated', $options);
128 } else {
3d9645ae 129 // New options.
e6d76583 130 $DB->insert_record('question_calculated', $options);
2d279432
PP
131 }
132 }
e6d76583 133
3d9645ae 134 // Delete old answer records.
2d279432 135 if (!empty($oldanswers)) {
29b68914 136 foreach ($oldanswers as $oa) {
2d279432
PP
137 $DB->delete_records('question_answers', array('id' => $oa->id));
138 }
139 }
2d279432 140 if (!empty($oldoptions)) {
29b68914 141 foreach ($oldoptions as $oo) {
2d279432
PP
142 $DB->delete_records('question_calculated', array('id' => $oo->id));
143 }
144 }
2d279432 145
e6d76583
TH
146 $this->save_hints($question, true);
147
29b68914 148 if (isset($question->import_process) && $question->import_process) {
2d279432 149 $this->import_datasets($question);
fe6ce234 150 }
2d279432
PP
151 // Report any problems.
152 if (!empty($result->notice)) {
153 return $result;
154 }
b130270d 155
2d279432
PP
156 return true;
157 }
158
88ec9f30
TH
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 }
165
66de66fe
TH
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 }
172
cdece95e
TH
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';
fe6ce234 177 } else {
cdece95e 178 $class = 'qtype_calculatedmulti_multi_question';
fe6ce234 179 }
cdece95e
TH
180 return new $class();
181 }
2d279432 182
cdece95e
TH
183 protected function initialise_question_instance(question_definition $question, $questiondata) {
184 question_type::initialise_question_instance($question, $questiondata);
fe6ce234 185
cdece95e
TH
186 $question->shuffleanswers = $questiondata->options->shuffleanswers;
187 $question->answernumbering = $questiondata->options->answernumbering;
188 if (!empty($questiondata->options->layout)) {
189 $question->layout = $questiondata->options->layout;
fe6ce234 190 } else {
cdece95e 191 $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
fe6ce234 192 }
fe6ce234 193
e35ba43c
TH
194 $question->synchronised = $questiondata->options->synchronize;
195
cdece95e
TH
196 $this->initialise_combined_feedback($question, $questiondata, true);
197 $this->initialise_question_answers($question, $questiondata);
29b68914 198
cdece95e
TH
199 foreach ($questiondata->options->answers as $a) {
200 $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength;
201 $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat;
202 }
2d279432 203
cdece95e 204 $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
2d279432
PP
205 }
206
faf01a4f
AR
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 }
217
29b68914 218 public function comment_header($question) {
2d279432
PP
219 $strheader = '';
220 $delimiter = '';
221
222 $answers = $question->options->answers;
223
224 foreach ($answers as $key => $answer) {
9ddb8a56 225 $ans = shorten_text($answer->answer, 17, true);
226 $strheader .= $delimiter.$ans;
227 $delimiter = '<br/><br/>';
2d279432
PP
228 }
229 return $strheader;
230 }
231
29b68914
TH
232 public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
233 $answers, $data, $number) {
75aa674b 234
0ff4bd08 235 $comment = new stdClass();
2d279432 236 $comment->stranswers = array();
29b68914 237 $comment->outsidelimit = false;
2d279432 238 $comment->answers = array();
2d279432
PP
239
240 $answers = fullclone($answers);
2d279432 241 foreach ($answers as $key => $answer) {
3d9645ae 242 // Evaluate the equations i.e {=5+4).
75aa674b
TH
243 $anssubstituted = $this->substitute_variables($answer->answer, $data);
244 $formulas = $this->find_formulas($anssubstituted);
245 $replaces = [];
246 foreach ($formulas as $formula) {
247 if ($formulaerrors = qtype_calculated_find_formula_errors($formula)) {
248 $str = $formulaerrors;
fe6ce234 249 } else {
75aa674b 250 eval('$str = ' . $formula . ';');
2d279432 251 }
75aa674b 252 $replaces[$formula] = $str;
fe6ce234 253 }
5aa93d1b 254 $anstext = str_replace(array_keys($replaces), array_values($replaces), $anssubstituted);
9ddb8a56 255 $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext;
2d279432
PP
256 }
257 return fullclone($comment);
258 }
259
29b68914 260 public function get_virtual_qtype() {
cdece95e 261 return question_bank::get_qtype('multichoice');
fe6ce234
DC
262 }
263
d1770e42
TH
264 public function get_possible_responses($questiondata) {
265 if ($questiondata->options->single) {
266 $responses = array();
267
268 foreach ($questiondata->options->answers as $aid => $answer) {
269 $responses[$aid] = new question_possible_response($answer->answer,
270 $answer->fraction);
271 }
272
273 $responses[null] = question_possible_response::no_response();
274 return array($questiondata->id => $responses);
275 } else {
276 $parts = array();
277
278 foreach ($questiondata->options->answers as $aid => $answer) {
279 $parts[$aid] = array($aid =>
280 new question_possible_response($answer->answer, $answer->fraction));
281 }
282
283 return $parts;
284 }
285 }
286
29b68914 287 public function move_files($questionid, $oldcontextid, $newcontextid) {
fe6ce234 288 $fs = get_file_storage();
fe6ce234 289
5d548d3e
TH
290 parent::move_files($questionid, $oldcontextid, $newcontextid);
291 $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
d44480f6 292 $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
5d548d3e
TH
293
294 $fs->move_area_files_to_new_context($oldcontextid,
295 $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid);
296 $fs->move_area_files_to_new_context($oldcontextid,
297 $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid);
298 $fs->move_area_files_to_new_context($oldcontextid,
299 $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid);
fe6ce234
DC
300 }
301
9203b705
TH
302 protected function delete_files($questionid, $contextid) {
303 $fs = get_file_storage();
304
305 parent::delete_files($questionid, $contextid);
306 $this->delete_files_in_answers($questionid, $contextid, true);
d44480f6 307 $this->delete_files_in_hints($questionid, $contextid);
cdece95e 308
29b68914
TH
309 $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
310 'correctfeedback', $questionid);
311 $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
312 'partiallycorrectfeedback', $questionid);
313 $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
314 'incorrectfeedback', $questionid);
9203b705 315 }
2d279432 316}