weekly release 3.7dev
[moodle.git] / question / type / multianswer / questiontype.php
CommitLineData
aeb15530 1<?php
d3603157
TH
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
17/**
18 * Question type class for the multi-answer question type.
19 *
20 * @package qtype
21 * @subpackage multianswer
22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
97562c4d 29require_once($CFG->dirroot . '/question/type/questiontypebase.php');
dcedbb0e
TH
30require_once($CFG->dirroot . '/question/type/multichoice/question.php');
31
1976496e 32/**
d3603157
TH
33 * The multi-answer question type class.
34 *
35 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7375c542 37 */
ab50232b 38class qtype_multianswer extends question_type {
516cf3eb 39
ab50232b
TH
40 public function can_analyse_responses() {
41 return false;
42 }
43
59a3fcd3
TH
44 public function get_question_options($question) {
45 global $DB, $OUTPUT;
516cf3eb 46
3d9645ae 47 // Get relevant data indexed by positionkey from the multianswers table.
ab50232b 48 $sequence = $DB->get_field('question_multianswer', 'sequence',
3112c220 49 array('question' => $question->id), MUST_EXIST);
516cf3eb 50
ab50232b
TH
51 $wrappedquestions = $DB->get_records_list('question', 'id',
52 explode(',', $sequence), 'id ASC');
516cf3eb 53
3d9645ae 54 // We want an array with question ids as index and the positions as values.
516cf3eb 55 $sequence = array_flip(explode(',', $sequence));
484b43f4
MG
56 array_walk($sequence, function(&$val) {
57 $val++;
58 });
ab50232b
TH
59
60 // If a question is lost, the corresponding index is null
aeb15530 61 // so this null convention is used to test $question->options->questions
857caf3b 62 // before using the values.
3d9645ae 63 // First all possible questions from sequence are nulled
64 // then filled with the data if available in $wrappedquestions.
59a3fcd3
TH
65 foreach ($sequence as $seq) {
66 $question->options->questions[$seq] = '';
df79079f 67 }
ab50232b
TH
68
69 foreach ($wrappedquestions as $wrapped) {
70 question_bank::get_qtype($wrapped->qtype)->get_question_options($wrapped);
3d9645ae 71 // For wrapped questions the maxgrade is always equal to the defaultmark,
72 // there is no entry in the question_instances table for them.
ab50232b
TH
73 $wrapped->maxmark = $wrapped->defaultmark;
74 $question->options->questions[$sequence[$wrapped->id]] = $wrapped;
857caf3b 75 }
a4499532
TH
76 $question->hints = $DB->get_records('question_hints',
77 array('questionid' => $question->id), 'id ASC');
78
516cf3eb 79 return true;
80 }
81
59a3fcd3
TH
82 public function save_question_options($question) {
83 global $DB;
0ff4bd08 84 $result = new stdClass();
9fc3100f 85
516cf3eb 86 // This function needs to be able to handle the case where the existing set of wrapped
87 // questions does not match the new set of wrapped questions so that some need to be
3d9645ae 88 // created, some modified and some deleted.
516cf3eb 89 // Unfortunately the code currently simply overwrites existing ones in sequence. This
9fc3100f 90 // will make re-marking after a re-ordering of wrapped questions impossible and
516cf3eb 91 // will also create difficulties if questiontype specific tables reference the id.
9fc3100f 92
3d9645ae 93 // First we get all the existing wrapped questions.
ab50232b
TH
94 if (!$oldwrappedids = $DB->get_field('question_multianswer', 'sequence',
95 array('question' => $question->id))) {
857caf3b 96 $oldwrappedquestions = array();
0a5b58af 97 } else {
12c6e008
TH
98 $oldwrappedquestions = $DB->get_records_list('question', 'id',
99 explode(',', $oldwrappedids), 'id ASC');
516cf3eb 100 }
ab50232b 101
516cf3eb 102 $sequence = array();
59a3fcd3
TH
103 foreach ($question->options->questions as $wrapped) {
104 if (!empty($wrapped)) {
3d9645ae 105 // If we still have some old wrapped question ids, reuse the next of them.
f34488b2 106
12c6e008
TH
107 if (is_array($oldwrappedquestions) &&
108 $oldwrappedquestion = array_shift($oldwrappedquestions)) {
857caf3b 109 $wrapped->id = $oldwrappedquestion->id;
59a3fcd3 110 if ($oldwrappedquestion->qtype != $wrapped->qtype) {
857caf3b 111 switch ($oldwrappedquestion->qtype) {
59a3fcd3 112 case 'multichoice':
0aa04a7f
TH
113 $DB->delete_records('qtype_multichoice_options',
114 array('questionid' => $oldwrappedquestion->id));
59a3fcd3
TH
115 break;
116 case 'shortanswer':
b4cb0957
TH
117 $DB->delete_records('qtype_shortanswer_options',
118 array('questionid' => $oldwrappedquestion->id));
59a3fcd3
TH
119 break;
120 case 'numerical':
ab50232b
TH
121 $DB->delete_records('question_numerical',
122 array('question' => $oldwrappedquestion->id));
59a3fcd3
TH
123 break;
124 default:
ab50232b
TH
125 throw new moodle_exception('qtypenotrecognized',
126 'qtype_multianswer', '', $oldwrappedquestion->qtype);
59a3fcd3 127 $wrapped->id = 0;
df79079f 128 }
e9028ffc 129 }
59a3fcd3
TH
130 } else {
131 $wrapped->id = 0;
e9028ffc 132 }
516cf3eb 133 }
77fa3a0d 134 $wrapped->name = $question->name;
135 $wrapped->parent = $question->id;
59a3fcd3 136 $previousid = $wrapped->id;
3d9645ae 137 // Save_question strips this extra bit off the category again.
ab50232b
TH
138 $wrapped->category = $question->category . ',1';
139 $wrapped = question_bank::get_qtype($wrapped->qtype)->save_question(
140 $wrapped, clone($wrapped));
516cf3eb 141 $sequence[] = $wrapped->id;
59a3fcd3 142 if ($previousid != 0 && $previousid != $wrapped->id) {
3d9645ae 143 // For some reasons a new question has been created
144 // so delete the old one.
3908a523 145 question_delete_question($previousid);
26053641 146 }
516cf3eb 147 }
148
3d9645ae 149 // Delete redundant wrapped questions.
59a3fcd3 150 if (is_array($oldwrappedquestions) && count($oldwrappedquestions)) {
26053641 151 foreach ($oldwrappedquestions as $oldwrappedquestion) {
3908a523 152 question_delete_question($oldwrappedquestion->id);
e9028ffc 153 }
4bc4ca50 154 }
516cf3eb 155
156 if (!empty($sequence)) {
0ff4bd08 157 $multianswer = new stdClass();
516cf3eb 158 $multianswer->question = $question->id;
159 $multianswer->sequence = implode(',', $sequence);
12c6e008
TH
160 if ($oldid = $DB->get_field('question_multianswer', 'id',
161 array('question' => $question->id))) {
516cf3eb 162 $multianswer->id = $oldid;
ab50232b 163 $DB->update_record('question_multianswer', $multianswer);
516cf3eb 164 } else {
ab50232b 165 $DB->insert_record('question_multianswer', $multianswer);
516cf3eb 166 }
167 }
a4499532 168
e9af6091 169 $this->save_hints($question, true);
516cf3eb 170 }
171
59a3fcd3 172 public function save_question($authorizedquestion, $form) {
e51efd7e 173 $question = qtype_multianswer_extract_question($form->questiontext);
516cf3eb 174 if (isset($authorizedquestion->id)) {
175 $question->id = $authorizedquestion->id;
516cf3eb 176 }
177
516cf3eb 178 $question->category = $authorizedquestion->category;
ab50232b 179 $form->defaultmark = $question->defaultmark;
516cf3eb 180 $form->questiontext = $question->questiontext;
181 $form->questiontextformat = 0;
77fa3a0d 182 $form->options = clone($question->options);
516cf3eb 183 unset($question->options);
94dbfb3a 184 return parent::save_question($question, $form);
516cf3eb 185 }
186
e9af6091
JMV
187 protected function make_hint($hint) {
188 return question_hint_with_parts::load_from_record($hint);
189 }
190
59a3fcd3 191 public function delete_question($questionid, $contextid) {
f34488b2 192 global $DB;
ab50232b 193 $DB->delete_records('question_multianswer', array('question' => $questionid));
9203b705
TH
194
195 parent::delete_question($questionid, $contextid);
516cf3eb 196 }
197
072db71c 198 protected function initialise_question_instance(question_definition $question, $questiondata) {
ab50232b 199 parent::initialise_question_instance($question, $questiondata);
42a5b055
TH
200
201 $bits = preg_split('/\{#(\d+)\}/', $question->questiontext,
202 null, PREG_SPLIT_DELIM_CAPTURE);
203 $question->textfragments[0] = array_shift($bits);
204 $i = 1;
205 while (!empty($bits)) {
206 $question->places[$i] = array_shift($bits);
207 $question->textfragments[$i] = array_shift($bits);
208 $i += 1;
209 }
ab50232b
TH
210 foreach ($questiondata->options->questions as $key => $subqdata) {
211 $subqdata->contextid = $questiondata->contextid;
bd156853
PP
212 if ($subqdata->qtype == 'multichoice') {
213 $answerregs = array();
214 if ($subqdata->options->shuffleanswers == 1 && isset($questiondata->options->shuffleanswers)
215 && $questiondata->options->shuffleanswers == 0 ) {
216 $subqdata->options->shuffleanswers = 0;
217 }
218 }
ab50232b 219 $question->subquestions[$key] = question_bank::make_question($subqdata);
fa6c8620 220 $question->subquestions[$key]->maxmark = $subqdata->defaultmark;
7ac7977c
TH
221 if (isset($subqdata->options->layout)) {
222 $question->subquestions[$key]->layout = $subqdata->options->layout;
223 }
869309b8 224 }
869309b8 225 }
869309b8 226
ab50232b
TH
227 public function get_random_guess_score($questiondata) {
228 $fractionsum = 0;
229 $fractionmax = 0;
230 foreach ($questiondata->options->questions as $key => $subqdata) {
fa6c8620 231 $fractionmax += $subqdata->defaultmark;
ab50232b
TH
232 $fractionsum += question_bank::get_qtype(
233 $subqdata->qtype)->get_random_guess_score($subqdata);
516cf3eb 234 }
ab50232b 235 return $fractionsum / $fractionmax;
516cf3eb 236 }
d44480f6
TH
237
238 public function move_files($questionid, $oldcontextid, $newcontextid) {
239 parent::move_files($questionid, $oldcontextid, $newcontextid);
240 $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
241 }
242
243 protected function delete_files($questionid, $contextid) {
244 parent::delete_files($questionid, $contextid);
245 $this->delete_files_in_hints($questionid, $contextid);
246 }
516cf3eb 247}
516cf3eb 248
ab50232b 249
3d9645ae 250// ANSWER_ALTERNATIVE regexes.
ab50232b 251define('ANSWER_ALTERNATIVE_FRACTION_REGEX',
0b346164 252 '=|%(-?[0-9]+)%');
3d9645ae 253// For the syntax '(?<!' see http://www.perl.com/doc/manual/html/pod/perlre.html#item_C.
ab50232b 254define('ANSWER_ALTERNATIVE_ANSWER_REGEX',
e51efd7e 255 '.+?(?<!\\\\|&|&amp;)(?=[~#}]|$)');
ab50232b 256define('ANSWER_ALTERNATIVE_FEEDBACK_REGEX',
0b346164 257 '.*?(?<!\\\\)(?=[~}]|$)');
ab50232b 258define('ANSWER_ALTERNATIVE_REGEX',
e51efd7e 259 '(' . ANSWER_ALTERNATIVE_FRACTION_REGEX .')?' .
260 '(' . ANSWER_ALTERNATIVE_ANSWER_REGEX . ')' .
261 '(#(' . ANSWER_ALTERNATIVE_FEEDBACK_REGEX .'))?');
0b346164 262
3d9645ae 263// Parenthesis positions for ANSWER_ALTERNATIVE_REGEX.
ab50232b
TH
264define('ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION', 2);
265define('ANSWER_ALTERNATIVE_REGEX_FRACTION', 1);
266define('ANSWER_ALTERNATIVE_REGEX_ANSWER', 3);
267define('ANSWER_ALTERNATIVE_REGEX_FEEDBACK', 5);
0b346164 268
269// NUMBER_FORMATED_ALTERNATIVE_ANSWER_REGEX is used
3d9645ae 270// for identifying numerical answers in ANSWER_ALTERNATIVE_REGEX_ANSWER.
ab50232b 271define('NUMBER_REGEX',
e64e28d7 272 '-?(([0-9]+[.,]?[0-9]*|[.,][0-9]+)([eE][-+]?[0-9]+)?)');
ab50232b 273define('NUMERICAL_ALTERNATIVE_REGEX',
e64e28d7 274 '^(' . NUMBER_REGEX . ')(:' . NUMBER_REGEX . ')?$');
0b346164 275
3d9645ae 276// Parenthesis positions for NUMERICAL_FORMATED_ALTERNATIVE_ANSWER_REGEX.
ab50232b
TH
277define('NUMERICAL_CORRECT_ANSWER', 1);
278define('NUMERICAL_ABS_ERROR_MARGIN', 6);
0b346164 279
3d9645ae 280// Remaining ANSWER regexes.
ab50232b 281define('ANSWER_TYPE_DEF_REGEX',
12c6e008 282 '(NUMERICAL|NM)|(MULTICHOICE|MC)|(MULTICHOICE_V|MCV)|(MULTICHOICE_H|MCH)|' .
bd156853 283 '(SHORTANSWER|SA|MW)|(SHORTANSWER_C|SAC|MWC)|' .
946ab15c
DS
284 '(MULTICHOICE_S|MCS)|(MULTICHOICE_VS|MCVS)|(MULTICHOICE_HS|MCHS)|'.
285 '(MULTIRESPONSE|MR)|(MULTIRESPONSE_H|MRH)|(MULTIRESPONSE_S|MRS)|(MULTIRESPONSE_HS|MRHS)');
ab50232b 286define('ANSWER_START_REGEX',
0b346164 287 '\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX . '):');
288
ab50232b 289define('ANSWER_REGEX',
0b346164 290 ANSWER_START_REGEX
291 . '(' . ANSWER_ALTERNATIVE_REGEX
292 . '(~'
293 . ANSWER_ALTERNATIVE_REGEX
59a3fcd3 294 . ')*)\}');
0b346164 295
3d9645ae 296// Parenthesis positions for singulars in ANSWER_REGEX.
ab50232b
TH
297define('ANSWER_REGEX_NORM', 1);
298define('ANSWER_REGEX_ANSWER_TYPE_NUMERICAL', 3);
299define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE', 4);
300define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR', 5);
301define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL', 6);
302define('ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER', 7);
303define('ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER_C', 8);
bd156853
PP
304define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_SHUFFLED', 9);
305define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR_SHUFFLED', 10);
306define('ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL_SHUFFLED', 11);
946ab15c
DS
307define('ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE', 12);
308define('ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE_HORIZONTAL', 13);
309define('ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE_SHUFFLED', 14);
310define('ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE_HORIZONTAL_SHUFFLED', 15);
311define('ANSWER_REGEX_ALTERNATIVES', 16);
bd156853
PP
312
313/**
314 * Initialise subquestion fields that are constant across all MULTICHOICE
315 * types.
316 *
317 * @param objet $wrapped The subquestion to initialise
318 *
319 */
320function qtype_multianswer_initialise_multichoice_subquestion($wrapped) {
321 $wrapped->qtype = 'multichoice';
322 $wrapped->single = 1;
323 $wrapped->answernumbering = 0;
324 $wrapped->correctfeedback['text'] = '';
325 $wrapped->correctfeedback['format'] = FORMAT_HTML;
326 $wrapped->correctfeedback['itemid'] = '';
327 $wrapped->partiallycorrectfeedback['text'] = '';
328 $wrapped->partiallycorrectfeedback['format'] = FORMAT_HTML;
329 $wrapped->partiallycorrectfeedback['itemid'] = '';
330 $wrapped->incorrectfeedback['text'] = '';
331 $wrapped->incorrectfeedback['format'] = FORMAT_HTML;
332 $wrapped->incorrectfeedback['itemid'] = '';
333}
516cf3eb 334
7518b645 335function qtype_multianswer_extract_question($text) {
3d9645ae 336 // Variable $text is an array [text][format][itemid].
0ff4bd08 337 $question = new stdClass();
dfa47f96 338 $question->qtype = 'multianswer';
516cf3eb 339 $question->questiontext = $text;
61dfe97e 340 $question->generalfeedback['text'] = '';
c1f15d35 341 $question->generalfeedback['format'] = FORMAT_HTML;
61dfe97e 342 $question->generalfeedback['itemid'] = '';
59a3fcd3 343
caee6e6c 344 $question->options = new stdClass();
59a3fcd3 345 $question->options->questions = array();
3d9645ae 346 $question->defaultmark = 0; // Will be increased for each answer norm.
516cf3eb 347
ab50232b 348 for ($positionkey = 1;
aa9d6e43 349 preg_match('/'.ANSWER_REGEX.'/s', $question->questiontext['text'], $answerregs);
ab50232b 350 ++$positionkey) {
0ff4bd08 351 $wrapped = new stdClass();
61dfe97e 352 $wrapped->generalfeedback['text'] = '';
c1f15d35 353 $wrapped->generalfeedback['format'] = FORMAT_HTML;
61dfe97e 354 $wrapped->generalfeedback['itemid'] = '';
48b5b28f 355 if (isset($answerregs[ANSWER_REGEX_NORM]) && $answerregs[ANSWER_REGEX_NORM] !== '') {
ab50232b 356 $wrapped->defaultmark = $answerregs[ANSWER_REGEX_NORM];
8795a5ae 357 } else {
ab50232b 358 $wrapped->defaultmark = '1';
8795a5ae 359 }
516cf3eb 360 if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL])) {
dfa47f96 361 $wrapped->qtype = 'numerical';
516cf3eb 362 $wrapped->multiplier = array();
363 $wrapped->units = array();
61dfe97e 364 $wrapped->instructions['text'] = '';
c1f15d35 365 $wrapped->instructions['format'] = FORMAT_HTML;
61dfe97e 366 $wrapped->instructions['itemid'] = '';
59a3fcd3 367 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER])) {
dfa47f96 368 $wrapped->qtype = 'shortanswer';
516cf3eb 369 $wrapped->usecase = 0;
59a3fcd3 370 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER_C])) {
fd97082c 371 $wrapped->qtype = 'shortanswer';
372 $wrapped->usecase = 1;
59a3fcd3 373 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE])) {
bd156853
PP
374 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
375 $wrapped->shuffleanswers = 0;
376 $wrapped->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
377 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_SHUFFLED])) {
378 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
1cacb059 379 $wrapped->shuffleanswers = 1;
7ac7977c 380 $wrapped->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
59a3fcd3 381 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR])) {
bd156853 382 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
1cacb059 383 $wrapped->shuffleanswers = 0;
bd156853
PP
384 $wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
385 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR_SHUFFLED])) {
386 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
387 $wrapped->shuffleanswers = 1;
7ac7977c 388 $wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
59a3fcd3 389 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL])) {
bd156853 390 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
1cacb059 391 $wrapped->shuffleanswers = 0;
bd156853
PP
392 $wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL;
393 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL_SHUFFLED])) {
394 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
395 $wrapped->shuffleanswers = 1;
7ac7977c 396 $wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL;
946ab15c
DS
397 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE])) {
398 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
399 $wrapped->single = 0;
400 $wrapped->shuffleanswers = 0;
401 $wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
402 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE_HORIZONTAL])) {
403 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
404 $wrapped->single = 0;
405 $wrapped->shuffleanswers = 0;
406 $wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL;
407 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE_SHUFFLED])) {
408 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
409 $wrapped->single = 0;
410 $wrapped->shuffleanswers = 1;
411 $wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
412 } else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTIRESPONSE_HORIZONTAL_SHUFFLED])) {
413 qtype_multianswer_initialise_multichoice_subquestion($wrapped);
414 $wrapped->single = 0;
415 $wrapped->shuffleanswers = 1;
416 $wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL;
516cf3eb 417 } else {
2471ef86 418 print_error('unknownquestiontype', 'question', '', $answerregs[2]);
516cf3eb 419 return false;
420 }
421
422 // Each $wrapped simulates a $form that can be processed by the
423 // respective save_question and save_question_options methods of the
3d9645ae 424 // wrapped questiontypes.
516cf3eb 425 $wrapped->answer = array();
426 $wrapped->fraction = array();
427 $wrapped->feedback = array();
61dfe97e 428 $wrapped->questiontext['text'] = $answerregs[0];
c1f15d35 429 $wrapped->questiontext['format'] = FORMAT_HTML;
59a3fcd3
TH
430 $wrapped->questiontext['itemid'] = '';
431 $answerindex = 0;
516cf3eb 432
946ab15c 433 $hasspecificfraction = false;
516cf3eb 434 $remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES];
aa9d6e43 435 while (preg_match('/~?'.ANSWER_ALTERNATIVE_REGEX.'/s', $remainingalts, $altregs)) {
516cf3eb 436 if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION]) {
f4fe3968 437 $wrapped->fraction["{$answerindex}"] = '1';
59a3fcd3 438 } else if ($percentile = $altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION]) {
f4fe3968 439 $wrapped->fraction["{$answerindex}"] = .01 * $percentile;
946ab15c 440 $hasspecificfraction = true;
516cf3eb 441 } else {
f4fe3968 442 $wrapped->fraction["{$answerindex}"] = '0';
516cf3eb 443 }
e51efd7e 444 if (isset($altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK])) {
ab50232b
TH
445 $feedback = html_entity_decode(
446 $altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK], ENT_QUOTES, 'UTF-8');
095b599a 447 $feedback = str_replace('\}', '}', $feedback);
f4fe3968
TH
448 $wrapped->feedback["{$answerindex}"]['text'] = str_replace('\#', '#', $feedback);
449 $wrapped->feedback["{$answerindex}"]['format'] = FORMAT_HTML;
450 $wrapped->feedback["{$answerindex}"]['itemid'] = '';
e51efd7e 451 } else {
f4fe3968
TH
452 $wrapped->feedback["{$answerindex}"]['text'] = '';
453 $wrapped->feedback["{$answerindex}"]['format'] = FORMAT_HTML;
454 $wrapped->feedback["{$answerindex}"]['itemid'] = '';
61dfe97e 455
e51efd7e 456 }
516cf3eb 457 if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL])
aa9d6e43 458 && preg_match('~'.NUMERICAL_ALTERNATIVE_REGEX.'~s',
ab50232b 459 $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER], $numregs)) {
e51efd7e 460 $wrapped->answer[] = $numregs[NUMERICAL_CORRECT_ANSWER];
c83ed025 461 if (array_key_exists(NUMERICAL_ABS_ERROR_MARGIN, $numregs)) {
f4fe3968 462 $wrapped->tolerance["{$answerindex}"] =
e51efd7e 463 $numregs[NUMERICAL_ABS_ERROR_MARGIN];
516cf3eb 464 } else {
f4fe3968 465 $wrapped->tolerance["{$answerindex}"] = 0;
516cf3eb 466 }
3d9645ae 467 } else { // Tolerance can stay undefined for non numerical questions.
1f8db780 468 // Undo quoting done by the HTML editor.
ab50232b
TH
469 $answer = html_entity_decode(
470 $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER], ENT_QUOTES, 'UTF-8');
095b599a 471 $answer = str_replace('\}', '}', $answer);
f4fe3968 472 $wrapped->answer["{$answerindex}"] = str_replace('\#', '#', $answer);
c1f15d35 473 if ($wrapped->qtype == 'multichoice') {
f4fe3968
TH
474 $wrapped->answer["{$answerindex}"] = array(
475 'text' => $wrapped->answer["{$answerindex}"],
c1f15d35
TH
476 'format' => FORMAT_HTML,
477 'itemid' => '');
478 }
516cf3eb 479 }
480 $tmp = explode($altregs[0], $remainingalts, 2);
481 $remainingalts = $tmp[1];
59a3fcd3 482 $answerindex++;
516cf3eb 483 }
484
946ab15c
DS
485 // Fix the score for multichoice_multi questions (as positive scores should add up to 1, not have a maximum of 1).
486 if (isset($wrapped->single) && $wrapped->single == 0) {
487 $total = 0;
488 foreach ($wrapped->fraction as $idx => $fraction) {
489 if ($fraction > 0) {
490 $total += $fraction;
491 }
492 }
493 if ($total) {
494 foreach ($wrapped->fraction as $idx => $fraction) {
495 if ($fraction > 0) {
496 $wrapped->fraction[$idx] = $fraction / $total;
497 } else if (!$hasspecificfraction) {
498 // If no specific fractions are given, set incorrect answers to each cancel out one correct answer.
499 $wrapped->fraction[$idx] = -(1.0 / $total);
500 }
501 }
502 }
503 }
504
ab50232b 505 $question->defaultmark += $wrapped->defaultmark;
516cf3eb 506 $question->options->questions[$positionkey] = clone($wrapped);
61dfe97e
PP
507 $question->questiontext['text'] = implode("{#$positionkey}",
508 explode($answerregs[0], $question->questiontext['text'], 2));
516cf3eb 509 }
516cf3eb 510 return $question;
511}