Merge branch 'MDL-70248-310' of https://github.com/HuongNV13/moodle into MOODLE_310_S...
[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
61407786
TH
218 protected function make_hint($hint) {
219 return question_hint_with_parts::load_from_record($hint);
220 }
221
29b68914 222 public function comment_header($question) {
2d279432
PP
223 $strheader = '';
224 $delimiter = '';
225
226 $answers = $question->options->answers;
227
228 foreach ($answers as $key => $answer) {
9ddb8a56 229 $ans = shorten_text($answer->answer, 17, true);
230 $strheader .= $delimiter.$ans;
231 $delimiter = '<br/><br/>';
2d279432
PP
232 }
233 return $strheader;
234 }
235
29b68914
TH
236 public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
237 $answers, $data, $number) {
75aa674b 238
0ff4bd08 239 $comment = new stdClass();
2d279432 240 $comment->stranswers = array();
29b68914 241 $comment->outsidelimit = false;
2d279432 242 $comment->answers = array();
2d279432
PP
243
244 $answers = fullclone($answers);
2d279432 245 foreach ($answers as $key => $answer) {
3d9645ae 246 // Evaluate the equations i.e {=5+4).
75aa674b
TH
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;
fe6ce234 253 } else {
75aa674b 254 eval('$str = ' . $formula . ';');
2d279432 255 }
75aa674b 256 $replaces[$formula] = $str;
fe6ce234 257 }
5aa93d1b 258 $anstext = str_replace(array_keys($replaces), array_values($replaces), $anssubstituted);
9ddb8a56 259 $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext;
2d279432
PP
260 }
261 return fullclone($comment);
262 }
263
29b68914 264 public function get_virtual_qtype() {
cdece95e 265 return question_bank::get_qtype('multichoice');
fe6ce234
DC
266 }
267
d1770e42
TH
268 public function get_possible_responses($questiondata) {
269 if ($questiondata->options->single) {
270 $responses = array();
271
272 foreach ($questiondata->options->answers as $aid => $answer) {
273 $responses[$aid] = new question_possible_response($answer->answer,
274 $answer->fraction);
275 }
276
277 $responses[null] = question_possible_response::no_response();
278 return array($questiondata->id => $responses);
279 } else {
280 $parts = array();
281
282 foreach ($questiondata->options->answers as $aid => $answer) {
283 $parts[$aid] = array($aid =>
284 new question_possible_response($answer->answer, $answer->fraction));
285 }
286
287 return $parts;
288 }
289 }
290
29b68914 291 public function move_files($questionid, $oldcontextid, $newcontextid) {
fe6ce234 292 $fs = get_file_storage();
fe6ce234 293
5d548d3e
TH
294 parent::move_files($questionid, $oldcontextid, $newcontextid);
295 $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
d44480f6 296 $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
5d548d3e
TH
297
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);
fe6ce234
DC
304 }
305
9203b705
TH
306 protected function delete_files($questionid, $contextid) {
307 $fs = get_file_storage();
308
309 parent::delete_files($questionid, $contextid);
310 $this->delete_files_in_answers($questionid, $contextid, true);
d44480f6 311 $this->delete_files_in_hints($questionid, $contextid);
cdece95e 312
29b68914
TH
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);
9203b705 319 }
2d279432 320}