Merge branch 'MDL-27820' of git://github.com/stronk7/moodle
[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 requires_qtypes() {
42 return array('calculated', 'multichoice');
2d279432 43 }
2d279432 44
29b68914
TH
45 public function save_question_options($question) {
46 global $CFG, $DB;
fe6ce234 47 $context = $question->context;
2d279432
PP
48 if (isset($question->answer) && !isset($question->answers)) {
49 $question->answers = $question->answer;
50 }
51 // calculated options
29b68914
TH
52 $update = true;
53 $options = $DB->get_record('question_calculated_options',
54 array('question' => $question->id));
2d279432
PP
55 if (!$options) {
56 $update = false;
0ff4bd08 57 $options = new stdClass();
2d279432
PP
58 $options->question = $question->id;
59 }
60 $options->synchronize = $question->synchronize;
2d279432
PP
61 $options->single = $question->single;
62 $options->answernumbering = $question->answernumbering;
63 $options->shuffleanswers = $question->shuffleanswers;
fe6ce234
DC
64
65 // save question feedback files
66 foreach (array('correct', 'partiallycorrect', 'incorrect') as $feedbacktype) {
67 $feedbackname = $feedbacktype . 'feedback';
68 $feedbackformat = $feedbackname . 'format';
69 $feedback = $question->$feedbackname;
70 $options->$feedbackformat = $feedback['format'];
cde2709a
DC
71 if (isset($feedback['files'])) {
72 $options->$feedbackname = trim($feedback['text']);
73 $files = $feedback['files'];
74 foreach ($files as $file) {
29b68914
TH
75 $this->import_file($question->context, 'qtype_calculatedmulti',
76 $feedbackname, $question->id, $file);
cde2709a
DC
77 }
78 } else {
29b68914
TH
79 $options->$feedbackname = file_save_draft_area_files($feedback['itemid'],
80 $context->id, 'qtype_calculatedmulti', $feedbackname,
81 $question->id, $this->fileoptionsa, trim($feedback['text']));
cde2709a 82 }
fe6ce234
DC
83 }
84
2d279432 85 if ($update) {
29b68914 86 $DB->update_record('question_calculated_options', $options);
2d279432 87 } else {
29b68914 88 $DB->insert_record('question_calculated_options', $options);
2d279432
PP
89 }
90
91 // Get old versions of the objects
29b68914
TH
92 if (!$oldanswers = $DB->get_records('question_answers',
93 array('question' => $question->id), 'id ASC')) {
2d279432
PP
94 $oldanswers = array();
95 }
96
29b68914
TH
97 if (!$oldoptions = $DB->get_records('question_calculated',
98 array('question' => $question->id), 'answer ASC')) {
2d279432
PP
99 $oldoptions = array();
100 }
101
2d279432
PP
102 // Insert all the new answers
103 if (isset($question->answer) && !isset($question->answers)) {
fe6ce234 104 $question->answers = $question->answer;
2d279432
PP
105 }
106 foreach ($question->answers as $key => $dataanswer) {
cde2709a
DC
107 if (is_array($dataanswer)) {
108 $dataanswer = $dataanswer['text'];
109 }
29b68914 110 if (trim($dataanswer) != '') {
0ff4bd08 111 $answer = new stdClass();
2d279432
PP
112 $answer->question = $question->id;
113 $answer->answer = trim($dataanswer);
114 $answer->fraction = $question->fraction[$key];
fe6ce234
DC
115 $answer->feedback = trim($question->feedback[$key]['text']);
116 $answer->feedbackformat = $question->feedback[$key]['format'];
cde2709a
DC
117 if (isset($question->feedback[$key]['files'])) {
118 $files = $question->feedback[$key]['files'];
119 }
2d279432 120
29b68914
TH
121 if ($oldanswer = array_shift($oldanswers)) {
122 // Existing answer, so reuse it
2d279432 123 $answer->id = $oldanswer->id;
29b68914
TH
124 $answer->feedback = file_save_draft_area_files(
125 $question->feedback[$key]['itemid'], $context->id, 'question',
126 'answerfeedback', $answer->id, $this->fileoptionsa, $answer->feedback);
127 $DB->update_record('question_answers', $answer);
128 } else {
129 // This is a completely new answer
130 $answer->id = $DB->insert_record('question_answers', $answer);
cde2709a
DC
131 if (isset($files)) {
132 $feedbacktext = $answer->feedback;
133 foreach ($files as $file) {
29b68914
TH
134 $this->import_file($context, 'question', 'answerfeedback',
135 $answer->id, $file);
cde2709a
DC
136 }
137 } else {
29b68914
TH
138 $feedbacktext = file_save_draft_area_files(
139 $question->feedback[$key]['itemid'], $context->id,
140 'question', 'answerfeedback', $answer->id,
141 $this->fileoptionsa, $answer->feedback);
cde2709a 142 }
29b68914
TH
143 $DB->set_field('question_answers', 'feedback', $feedbacktext,
144 array('id'=>$answer->id));
2d279432
PP
145 }
146
147 // Set up the options object
148 if (!$options = array_shift($oldoptions)) {
0ff4bd08 149 $options = new stdClass();
2d279432
PP
150 }
151 $options->question = $question->id;
152 $options->answer = $answer->id;
153 $options->tolerance = trim($question->tolerance[$key]);
154 $options->tolerancetype = trim($question->tolerancetype[$key]);
155 $options->correctanswerlength = trim($question->correctanswerlength[$key]);
156 $options->correctanswerformat = trim($question->correctanswerformat[$key]);
157
158 // Save options
159 if (isset($options->id)) { // reusing existing record
160 $DB->update_record('question_calculated', $options);
161 } else { // new options
162 $DB->insert_record('question_calculated', $options);
163 }
164 }
165 }
166 // delete old answer records
167 if (!empty($oldanswers)) {
29b68914 168 foreach ($oldanswers as $oa) {
2d279432
PP
169 $DB->delete_records('question_answers', array('id' => $oa->id));
170 }
171 }
172
173 // delete old answer records
174 if (!empty($oldoptions)) {
29b68914 175 foreach ($oldoptions as $oo) {
2d279432
PP
176 $DB->delete_records('question_calculated', array('id' => $oo->id));
177 }
178 }
2d279432 179
29b68914 180 if (isset($question->import_process) && $question->import_process) {
2d279432 181 $this->import_datasets($question);
fe6ce234 182 }
2d279432
PP
183 // Report any problems.
184 if (!empty($result->notice)) {
185 return $result;
186 }
b130270d
TH
187
188 $this->save_hints($question, true);
189
2d279432
PP
190 return true;
191 }
192
cdece95e
TH
193 protected function make_question_instance($questiondata) {
194 question_bank::load_question_definition_classes($this->name());
195 if ($questiondata->options->single) {
196 $class = 'qtype_calculatedmulti_single_question';
fe6ce234 197 } else {
cdece95e 198 $class = 'qtype_calculatedmulti_multi_question';
fe6ce234 199 }
cdece95e
TH
200 return new $class();
201 }
2d279432 202
cdece95e
TH
203 protected function initialise_question_instance(question_definition $question, $questiondata) {
204 question_type::initialise_question_instance($question, $questiondata);
fe6ce234 205
cdece95e
TH
206 $question->shuffleanswers = $questiondata->options->shuffleanswers;
207 $question->answernumbering = $questiondata->options->answernumbering;
208 if (!empty($questiondata->options->layout)) {
209 $question->layout = $questiondata->options->layout;
fe6ce234 210 } else {
cdece95e 211 $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
fe6ce234 212 }
fe6ce234 213
e35ba43c
TH
214 $question->synchronised = $questiondata->options->synchronize;
215
cdece95e
TH
216 $this->initialise_combined_feedback($question, $questiondata, true);
217 $this->initialise_question_answers($question, $questiondata);
29b68914 218
cdece95e
TH
219 foreach ($questiondata->options->answers as $a) {
220 $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength;
221 $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat;
222 }
2d279432 223
cdece95e 224 $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
2d279432
PP
225 }
226
29b68914 227 public function comment_header($question) {
2d279432
PP
228 $strheader = '';
229 $delimiter = '';
230
231 $answers = $question->options->answers;
232
233 foreach ($answers as $key => $answer) {
234 if (is_string($answer)) {
235 $strheader .= $delimiter.$answer;
236 } else {
237 $strheader .= $delimiter.$answer->answer;
238 }
fe6ce234 239 $delimiter = '<br/>';
2d279432
PP
240 }
241 return $strheader;
242 }
243
29b68914
TH
244 public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
245 $answers, $data, $number) {
2d279432 246 global $DB;
0ff4bd08 247 $comment = new stdClass();
2d279432 248 $comment->stranswers = array();
29b68914 249 $comment->outsidelimit = false;
2d279432 250 $comment->answers = array();
2d279432
PP
251
252 $answers = fullclone($answers);
2d279432
PP
253 $errors = '';
254 $delimiter = ': ';
255 foreach ($answers as $key => $answer) {
fe6ce234
DC
256 $answer->answer = $this->substitute_variables($answer->answer, $data);
257 //evaluate the equations i.e {=5+4)
29b68914
TH
258 $qtext = '';
259 $qtextremaining = $answer->answer;
260 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
fe6ce234
DC
261 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
262 $qtext =$qtext.$qtextsplits[0];
263 $qtextremaining = $qtextsplits[1];
264 if (empty($regs1[1])) {
265 $str = '';
266 } else {
29b68914
TH
267 if ($formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
268 $str=$formulaerrors;
269 } else {
fe6ce234
DC
270 eval('$str = '.$regs1[1].';');
271 }
2d279432 272 }
29b68914 273 $qtext = $qtext.$str;
fe6ce234
DC
274 }
275 $answer->answer = $qtext.$qtextremaining;
276 $comment->stranswers[$key] = $answer->answer;
2d279432
PP
277 }
278 return fullclone($comment);
279 }
280
29b68914 281 public function get_virtual_qtype() {
cdece95e 282 return question_bank::get_qtype('multichoice');
fe6ce234
DC
283 }
284
d1770e42
TH
285 public function get_possible_responses($questiondata) {
286 if ($questiondata->options->single) {
287 $responses = array();
288
289 foreach ($questiondata->options->answers as $aid => $answer) {
290 $responses[$aid] = new question_possible_response($answer->answer,
291 $answer->fraction);
292 }
293
294 $responses[null] = question_possible_response::no_response();
295 return array($questiondata->id => $responses);
296 } else {
297 $parts = array();
298
299 foreach ($questiondata->options->answers as $aid => $answer) {
300 $parts[$aid] = array($aid =>
301 new question_possible_response($answer->answer, $answer->fraction));
302 }
303
304 return $parts;
305 }
306 }
307
29b68914 308 public function move_files($questionid, $oldcontextid, $newcontextid) {
fe6ce234 309 $fs = get_file_storage();
fe6ce234 310
5d548d3e
TH
311 parent::move_files($questionid, $oldcontextid, $newcontextid);
312 $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
313
314 $fs->move_area_files_to_new_context($oldcontextid,
315 $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid);
316 $fs->move_area_files_to_new_context($oldcontextid,
317 $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid);
318 $fs->move_area_files_to_new_context($oldcontextid,
319 $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid);
fe6ce234
DC
320 }
321
9203b705
TH
322 protected function delete_files($questionid, $contextid) {
323 $fs = get_file_storage();
324
325 parent::delete_files($questionid, $contextid);
326 $this->delete_files_in_answers($questionid, $contextid, true);
cdece95e 327
29b68914
TH
328 $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
329 'correctfeedback', $questionid);
330 $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
331 'partiallycorrectfeedback', $questionid);
332 $fs->delete_area_files($contextid, 'qtype_calculatedmulti',
333 'incorrectfeedback', $questionid);
9203b705 334 }
2d279432 335}