4 /// MULTIANSWER /// (Embedded - cloze)
8 /// The multianswer question type is special in that it
9 /// depends on a few other question types, i.e.
10 /// 'multichoice', 'shortanswer' and 'numerical'.
11 /// These question types have got a few special features that
12 /// makes them useable by the 'multianswer' question type
15 /// QUESTION TYPE CLASS //////////////////
17 * @package questionbank
18 * @subpackage questiontypes
20 class embedded_cloze_qtype
extends default_questiontype
{
26 function get_question_options(&$question) {
29 // Get relevant data indexed by positionkey from the multianswers table
30 if (!$sequence = get_field('question_multianswer', 'sequence', 'question', $question->id
)) {
31 notify('Error: Cloze question '.$question->id
.' is missing question options!');
35 $wrappedquestions = get_records_list('question', 'id', $sequence, 'id ASC');
37 // We want an array with question ids as index and the positions as values
38 $sequence = array_flip(explode(',', $sequence));
39 array_walk($sequence, create_function('&$val', '$val++;'));
41 foreach ($wrappedquestions as $wrapped) {
42 if (!$QTYPES[$wrapped->qtype
]->get_question_options($wrapped)) {
43 notify("Unable to get options for questiontype {$wrapped->qtype} (id={$wrapped->id})");
45 // for wrapped questions the maxgrade is always equal to the defaultgrade,
46 // there is no entry in the question_instances table for them
47 $wrapped->maxgrade
= $wrapped->defaultgrade
;
49 $question->options
->questions
[$sequence[$wrapped->id
]] = clone($wrapped); // ??? Why do we need a clone here?
55 function save_question_options($question) {
57 $result = new stdClass
;
59 // This function needs to be able to handle the case where the existing set of wrapped
60 // questions does not match the new set of wrapped questions so that some need to be
61 // created, some modified and some deleted
62 // Unfortunately the code currently simply overwrites existing ones in sequence. This
63 // will make re-marking after a re-ordering of wrapped questions impossible and
64 // will also create difficulties if questiontype specific tables reference the id.
66 // First we get all the existing wrapped questions
67 if (!$oldwrappedids = get_field('question_multianswer', 'sequence', 'question', $question->id
)) {
68 $oldwrappedids = array();
70 $oldwrappedids = explode(',', $oldwrappedids);
73 foreach($question->options
->questions
as $wrapped) {
74 // if we still have some old wrapped question ids, reuse the next of them
75 if ($oldwrappedid = array_shift($oldwrappedids)) {
76 $wrapped->id
= $oldwrappedid;
78 $wrapped->name
= $question->name
;
79 $wrapped->parent
= $question->id
;
80 $wrapped->category
= $question->category
;
81 $wrapped = $QTYPES[$wrapped->qtype
]->save_question($wrapped,
82 $wrapped, $question->course
);
83 $sequence[] = $wrapped->id
;
86 // Delete redundant wrapped questions
87 $oldwrappedids = implode(',', $oldwrappedids);
88 delete_records_select('question', "id IN ($oldwrappedids)");
90 if (!empty($sequence)) {
91 $multianswer = new stdClass
;
92 $multianswer->question
= $question->id
;
93 $multianswer->sequence
= implode(',', $sequence);
94 if ($oldid = get_field('question_multianswer', 'id', 'question', $question->id
)) {
95 $multianswer->id
= $oldid;
96 if (!update_record("question_multianswer", $multianswer)) {
97 $result->error
= "Could not update cloze question options! " .
98 "(id=$multianswer->id)";
102 if (!insert_record("question_multianswer", $multianswer)) {
103 $result->error
= "Could not insert cloze question options!";
110 function save_question($authorizedquestion, $form, $course) {
111 $question = qtype_multianswer_extract_question($form->questiontext
);
112 if (isset($authorizedquestion->id
)) {
113 $question->id
= $authorizedquestion->id
;
117 $question->category
= $authorizedquestion->category
;
118 $form->course
= $course; // To pass the course object to
119 // save_question_options, where it is
120 // needed to call type specific
121 // save_question methods.
122 $form->defaultgrade
= $question->defaultgrade
;
123 $form->questiontext
= $question->questiontext
;
124 $form->questiontextformat
= 0;
125 $form->options
= clone($question->options
);
126 unset($question->options
);
127 return parent
::save_question($question, $form, $course);
130 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
131 $state->responses
= array();
132 foreach ($question->options
->questions
as $key => $wrapped) {
133 $state->responses
[$key] = '';
138 function restore_session_and_responses(&$question, &$state) {
139 $responses = explode(',', $state->responses
['']);
140 $state->responses
= array();
141 foreach ($responses as $response) {
142 $tmp = explode("-", $response);
143 // restore encoded characters
144 $state->responses
[$tmp[0]] = str_replace(array(",", "-"),
145 array(",", "-"), $tmp[1]);
150 function save_session_and_responses(&$question, &$state) {
151 $responses = $state->responses
;
152 // encode - (hyphen) and , (comma) to - because they are used as
154 array_walk($responses, create_function('&$val, $key',
155 '$val = str_replace(array(",", "-"), array(",", "-"), $val);
156 $val = "$key-$val";'));
157 $responses = implode(',', $responses);
159 // Set the legacy answer field
160 if (!set_field('question_states', 'answer', $responses, 'id', $state->id
)) {
167 * Deletes question from the question-type specific tables
169 * @return boolean Success/Failure
170 * @param object $question The question being deleted
172 function delete_question($questionid) {
173 delete_records("question_multianswer", "question", $questionid);
177 function get_correct_responses(&$question, &$state) {
179 $responses = array();
180 foreach($question->options
->questions
as $key => $wrapped) {
181 if ($correct = $QTYPES[$wrapped->qtype
]->get_correct_responses($wrapped, $state)) {
182 $responses[$key] = $correct[''];
184 // if there is no correct answer to this subquestion then there
185 // can not be a correct answer to the whole question either, so
186 // we have to return null.
193 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
195 global $QTYPES, $CFG, $USER;
196 $readonly = empty($options->readonly
) ?
'' : 'readonly="readonly"';
197 $disabled = empty($options->readonly
) ?
'' : 'disabled="disabled"';
198 $formatoptions = new stdClass
;
199 $formatoptions->noclean
= true;
200 $formatoptions->para
= false;
201 $nameprefix = $question->name_prefix
;
203 // adding an icon with alt to warn user this is a fill in the gap question
205 if (!empty($USER->screenreader
)) {
206 echo "<img src=\"$CFG->wwwroot/question/type/$question->qtype/icon.gif\" ".
207 "class=\"icon\" alt=\"".get_string('clozeaid','qtype_multichoice')."\" /> ";
209 // For this question type, we better print the image on top:
210 if ($image = get_question_image($question, $cmoptions->course
)) {
211 echo('<img class="qimage" src="' . $image . '" alt="" /><br />');
214 $qtextremaining = format_text($question->questiontext
,
215 $question->questiontextformat
, $formatoptions, $cmoptions->course
);
217 $strfeedback = get_string('feedback', 'quiz');
219 // The regex will recognize text snippets of type {#X}
220 // where the X can be any text not containg } or white-space characters.
222 while (ereg('\{#([^[:space:]}]*)}', $qtextremaining, $regs)) {
223 $qtextsplits = explode($regs[0], $qtextremaining, 2);
224 echo "<label>"; // MDL-7497
225 echo $qtextsplits[0];
226 $qtextremaining = $qtextsplits[1];
228 $positionkey = $regs[1];
229 $wrapped = &$question->options
->questions
[$positionkey];
230 $answers = &$wrapped->options
->answers
;
231 $correctanswers = $QTYPES[$wrapped->qtype
]->get_correct_responses($wrapped, $state);
233 $inputname = $nameprefix.$positionkey;
234 if (isset($state->responses
[$positionkey])) {
235 $response = $state->responses
[$positionkey];
240 // Determine feedback popup if any
244 if ($options->feedback
) {
245 $chosenanswer = null;
246 switch ($wrapped->qtype
) {
249 $testedstate = clone($state);
250 $testedstate->responses
[''] = $response;
251 foreach ($answers as $answer) {
252 if($QTYPES[$wrapped->qtype
]
253 ->test_response($wrapped, $testedstate, $answer)) {
254 $chosenanswer = clone($answer);
260 if (isset($answers[$response])) {
261 $chosenanswer = clone($answers[$response]);
268 // Set up a default chosenanswer so that all non-empty wrong
269 // answers are highlighted red
270 if (empty($chosenanswer) && !empty($response)) {
271 $chosenanswer = new stdClass
;
272 $chosenanswer->fraction
= 0.0;
275 if (!empty($chosenanswer->feedback
)) {
276 $feedback = s(str_replace(array("\\", "'"), array("\\\\", "\\'"), $chosenanswer->feedback
));
277 $popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedback', FGCOLOR, '#FFFFFF');\" ".
278 " onmouseout=\"return nd();\" ";
282 if ($options->feedback
&& !empty($response)) {
283 $style = 'class = "'.question_get_feedback_class($chosenanswer->fraction
).'"';
284 $feedbackimg = question_get_feedback_image($chosenanswer->fraction
);
291 // Print the input control
292 switch ($wrapped->qtype
) {
295 echo " <input $style $readonly $popup name=\"$inputname\"
296 type=\"text\" value=\"".s($response, true)."\" size=\"12\" /> ";
297 if (!empty($feedback) && !empty($USER->screenreader
)) {
298 echo "<img src=\"$CFG->pixpath/i/feedback.gif\" alt=\"$feedback\" />";
303 $outputoptions = '<option></option>'; // Default empty option
304 foreach ($answers as $mcanswer) {
306 if ($response == $mcanswer->id
) {
307 $selected = ' selected="selected"';
309 $outputoptions .= "<option value=\"$mcanswer->id\"$selected>" .
310 s($mcanswer->answer
, true) . '</option>';
312 // In the next line, $readonly is invalid HTML, but it works in
313 // all browsers. $disabled would be valid, but then the JS for
314 // displaying the feedback does not work. Of course, we should
315 // not be relying on JS (for accessibility reasons), but that is
318 // The span is used for safari, which does not allow styling of
320 echo "<span $style><select $popup $readonly $style name=\"$inputname\">";
322 echo '</select></span>';
323 if (!empty($feedback) && !empty($USER->screenreader
)) {
324 echo "<img src=\"$CFG->pixpath/i/feedback.gif\" alt=\"$feedback\" />";
329 error("Unable to recognize questiontype ($wrapped->qtype) of
330 question part $positionkey.");
333 echo "</label>"; // MDL-7497
336 // Print the final piece of question text:
337 echo $qtextremaining;
338 $this->print_question_submit_buttons($question, $state, $cmoptions, $options);
341 function grade_responses(&$question, &$state, $cmoptions) {
343 $teststate = clone($state);
344 $state->raw_grade
= 0;
345 foreach($question->options
->questions
as $key => $wrapped) {
346 $state->responses
[$key] = $state->responses
[$key];
347 $teststate->responses
= array('' => $state->responses
[$key]);
348 $teststate->raw_grade
= 0;
349 if (false === $QTYPES[$wrapped->qtype
]
350 ->grade_responses($wrapped, $teststate, $cmoptions)) {
353 $state->raw_grade +
= $teststate->raw_grade
;
355 $state->raw_grade
/= $question->defaultgrade
;
356 $state->raw_grade
= min(max((float) $state->raw_grade
, 0.0), 1.0)
357 * $question->maxgrade
;
359 if (empty($state->raw_grade
)) {
360 $state->raw_grade
= 0.0;
362 $state->penalty
= $question->penalty
* $question->maxgrade
;
364 // mark the state as graded
365 $state->event
= ($state->event
== QUESTION_EVENTCLOSE
) ? QUESTION_EVENTCLOSEANDGRADE
: QUESTION_EVENTGRADE
;
370 function get_actual_response($question, $state) {
372 $teststate = clone($state);
373 foreach($question->options
->questions
as $key => $wrapped) {
374 $state->responses
[$key] = html_entity_decode($state->responses
[$key]);
375 $teststate->responses
= array('' => $state->responses
[$key]);
376 $correct = $QTYPES[$wrapped->qtype
]
377 ->get_actual_response($wrapped, $teststate);
378 // change separator here if you want
379 $responsesseparator = ',';
380 $responses[$key] = implode($responsesseparator, $correct);
385 /// BACKUP FUNCTIONS ////////////////////////////
388 * Backup the data in the question
390 * This is used in question/backuplib.php
392 function backup($bf,$preferences,$question,$level=6) {
396 $multianswers = get_records("question_multianswer","question",$question,"id");
397 //If there are multianswers
399 //Print multianswers header
400 $status = fwrite ($bf,start_tag("MULTIANSWERS",$level,true));
401 //Iterate over each multianswer
402 foreach ($multianswers as $multianswer) {
403 $status = fwrite ($bf,start_tag("MULTIANSWER",$level+
1,true));
404 //Print multianswer contents
405 fwrite ($bf,full_tag("ID",$level+
2,false,$multianswer->id
));
406 fwrite ($bf,full_tag("QUESTION",$level+
2,false,$multianswer->question
));
407 fwrite ($bf,full_tag("SEQUENCE",$level+
2,false,$multianswer->sequence
));
408 $status = fwrite ($bf,end_tag("MULTIANSWER",$level+
1,true));
410 //Print multianswers footer
411 $status = fwrite ($bf,end_tag("MULTIANSWERS",$level,true));
412 //Now print question_answers
413 $status = question_backup_answers($bf,$preferences,$question);
418 /// RESTORE FUNCTIONS /////////////////
421 * Restores the data in the question
423 * This is used in question/restorelib.php
425 function restore($old_question_id,$new_question_id,$info,$restore) {
429 //Get the multianswers array
430 $multianswers = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
431 //Iterate over multianswers
432 for($i = 0; $i < sizeof($multianswers); $i++
) {
433 $mul_info = $multianswers[$i];
436 $oldid = backup_todb($mul_info['#']['ID']['0']['#']);
438 //Now, build the question_multianswer record structure
439 $multianswer = new stdClass
;
440 $multianswer->question
= $new_question_id;
441 $multianswer->sequence
= backup_todb($mul_info['#']['SEQUENCE']['0']['#']);
443 //We have to recode the sequence field (a list of question ids)
444 //Extracts question id from sequence
445 $sequence_field = "";
447 $tok = strtok($multianswer->sequence
,",");
449 //Get the answer from backup_ids
450 $question = backup_getid($restore->backup_unique_code
,"question",$tok);
453 $sequence_field .= $question->new_id
;
456 $sequence_field .= ",".$question->new_id
;
462 //We have the answers field recoded to its new ids
463 $multianswer->sequence
= $sequence_field;
464 //The structure is equal to the db, so insert the question_multianswer
465 $newid = insert_record("question_multianswer", $multianswer);
467 //Save ids in backup_ids
469 backup_putid($restore->backup_unique_code
,"question_multianswer",
474 if (($i+
1) %
50 == 0) {
475 if (!defined('RESTORE_SILENTLY')) {
477 if (($i+
1) %
1000 == 0) {
488 function restore_map($old_question_id,$new_question_id,$info,$restore) {
492 //Get the multianswers array
493 $multianswers = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
494 //Iterate over multianswers
495 for($i = 0; $i < sizeof($multianswers); $i++
) {
496 $mul_info = $multianswers[$i];
499 $oldid = backup_todb($mul_info['#']['ID']['0']['#']);
501 //Now, build the question_multianswer record structure
502 $multianswer->question
= $new_question_id;
503 $multianswer->answers
= backup_todb($mul_info['#']['ANSWERS']['0']['#']);
504 $multianswer->positionkey
= backup_todb($mul_info['#']['POSITIONKEY']['0']['#']);
505 $multianswer->answertype
= backup_todb($mul_info['#']['ANSWERTYPE']['0']['#']);
506 $multianswer->norm
= backup_todb($mul_info['#']['NORM']['0']['#']);
508 //If we are in this method is because the question exists in DB, so its
509 //multianswer must exist too.
510 //Now, we are going to look for that multianswer in DB and to create the
511 //mappings in backup_ids to use them later where restoring states (user level).
513 //Get the multianswer from DB (by question and positionkey)
514 $db_multianswer = get_record ("question_multianswer","question",$new_question_id,
515 "positionkey",$multianswer->positionkey
);
517 if (($i+
1) %
50 == 0) {
518 if (!defined('RESTORE_SILENTLY')) {
520 if (($i+
1) %
1000 == 0) {
527 //We have the database multianswer, so update backup_ids
528 if ($db_multianswer) {
529 //We have the newid, update backup_ids
530 backup_putid($restore->backup_unique_code
,"question_multianswer",$oldid,
531 $db_multianswer->id
);
540 function restore_recode_answer($state, $restore) {
541 //The answer is a comma separated list of hypen separated sequence number and answers. We may have to recode the answers
544 $tok = strtok($state->answer
,",");
546 //Extract the multianswer_id and the answer
547 $exploded = explode("-",$tok);
548 $seqnum = $exploded[0];
549 $answer = $exploded[1];
550 // $sequence is an ordered array of the question ids.
551 if (!$sequence = get_field('question_multianswer', 'sequence', 'question', $state->question
)) {
552 error("The cloze question $state->question is missing its options");
554 $sequence = explode(',', $sequence);
555 // The id of the current question.
556 $wrappedquestionid = $sequence[$seqnum-1];
557 // now we can find the question
558 if (!$wrappedquestion = get_record('question', 'id', $wrappedquestionid)) {
559 notify("Can't find the subquestion $wrappedquestionid that is used as part $seqnum in cloze question $state->question");
561 // For multichoice question we need to recode the answer
562 if ($answer and $wrappedquestion->qtype
== 'multichoice') {
563 //The answer is an answer_id, look for it in backup_ids
564 if (!$ans = backup_getid($restore->backup_unique_code
,"question_answers",$answer)) {
565 echo 'Could not recode cloze multichoice answer '.$answer.'<br />';
567 $answer = $ans->new_id
;
569 //build the new answer field for each pair
571 $answer_field .= $seqnum."-".$answer;
574 $answer_field .= ",".$seqnum."-".$answer;
579 return $answer_field;
584 //// END OF CLASS ////
587 //////////////////////////////////////////////////////////////////////////
588 //// INITIATION - Without this line the question type is not in use... ///
589 //////////////////////////////////////////////////////////////////////////
590 question_register_questiontype(new embedded_cloze_qtype());
592 /////////////////////////////////////////////////////////////
593 //// ADDITIONAL FUNCTIONS
594 //// The functions below deal exclusivly with editing
595 //// of questions with question type 'multianswer'.
596 //// Therefore they are kept in this file.
597 //// They are not in the class as they are not
598 //// likely to be subject for overriding.
599 /////////////////////////////////////////////////////////////
601 // ANSWER_ALTERNATIVE regexes
602 define("ANSWER_ALTERNATIVE_FRACTION_REGEX",
604 // for the syntax '(?<!' see http://www.perl.com/doc/manual/html/pod/perlre.html#item_C
605 define("ANSWER_ALTERNATIVE_ANSWER_REGEX",
606 '.+?(?<!\\\\|&|&)(?=[~#}]|$)');
607 define("ANSWER_ALTERNATIVE_FEEDBACK_REGEX",
608 '.*?(?<!\\\\)(?=[~}]|$)');
609 define("ANSWER_ALTERNATIVE_REGEX",
610 '(' . ANSWER_ALTERNATIVE_FRACTION_REGEX
.')?' .
611 '(' . ANSWER_ALTERNATIVE_ANSWER_REGEX
. ')' .
612 '(#(' . ANSWER_ALTERNATIVE_FEEDBACK_REGEX
.'))?');
614 // Parenthesis positions for ANSWER_ALTERNATIVE_REGEX
615 define("ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION", 2);
616 define("ANSWER_ALTERNATIVE_REGEX_FRACTION", 1);
617 define("ANSWER_ALTERNATIVE_REGEX_ANSWER", 3);
618 define("ANSWER_ALTERNATIVE_REGEX_FEEDBACK", 5);
620 // NUMBER_FORMATED_ALTERNATIVE_ANSWER_REGEX is used
621 // for identifying numerical answers in ANSWER_ALTERNATIVE_REGEX_ANSWER
622 define("NUMBER_REGEX",
623 '-?(([0-9]+[.,]?[0-9]*|[.,][0-9]+)([eE][-+]?[0-9]+)?)');
624 define("NUMERICAL_ALTERNATIVE_REGEX",
625 '^(' . NUMBER_REGEX
. ')(:' . NUMBER_REGEX
. ')?$');
627 // Parenthesis positions for NUMERICAL_FORMATED_ALTERNATIVE_ANSWER_REGEX
628 define("NUMERICAL_CORRECT_ANSWER", 1);
629 define("NUMERICAL_ABS_ERROR_MARGIN", 6);
631 // Remaining ANSWER regexes
632 define("ANSWER_TYPE_DEF_REGEX",
633 '(NUMERICAL|NM)|(MULTICHOICE|MC)|(SHORTANSWER|SA|MW)');
634 define("ANSWER_START_REGEX",
635 '\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX
. '):');
637 define("ANSWER_REGEX",
639 . '(' . ANSWER_ALTERNATIVE_REGEX
641 . ANSWER_ALTERNATIVE_REGEX
644 // Parenthesis positions for singulars in ANSWER_REGEX
645 define("ANSWER_REGEX_NORM", 1);
646 define("ANSWER_REGEX_ANSWER_TYPE_NUMERICAL", 3);
647 define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE", 4);
648 define("ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER", 5);
649 define("ANSWER_REGEX_ALTERNATIVES", 6);
651 function qtype_multianswer_extract_question($text) {
652 $question = new stdClass
;
653 $question->qtype
= 'multianswer';
654 $question->questiontext
= $text;
655 $question->options
->questions
= array();
656 $question->defaultgrade
= 0; // Will be increased for each answer norm
659 ; preg_match('/'.ANSWER_REGEX
.'/', $question->questiontext
, $answerregs)
661 $wrapped = new stdClass
;
662 $wrapped->defaultgrade
= $answerregs[ANSWER_REGEX_NORM
]
663 or $wrapped->defaultgrade
= '1';
664 if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL
])) {
665 $wrapped->qtype
= 'numerical';
666 $wrapped->multiplier
= array();
667 $wrapped->units
= array();
668 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER
])) {
669 $wrapped->qtype
= 'shortanswer';
670 $wrapped->usecase
= 0;
671 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE
])) {
672 $wrapped->qtype
= 'multichoice';
673 $wrapped->single
= 1;
674 $wrapped->correctfeedback
= '';
675 $wrapped->partiallycorrectfeedback
= '';
676 $wrapped->incorrectfeedback
= '';
678 error("Cannot identify qtype $answerregs[2]");
682 // Each $wrapped simulates a $form that can be processed by the
683 // respective save_question and save_question_options methods of the
684 // wrapped questiontypes
685 $wrapped->answer
= array();
686 $wrapped->fraction
= array();
687 $wrapped->feedback
= array();
688 $wrapped->shuffleanswers
= 1;
689 $wrapped->questiontext
= $answerregs[0];
690 $wrapped->questiontextformat
= 0;
692 $remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES
];
693 while (preg_match('/~?'.ANSWER_ALTERNATIVE_REGEX
.'/', $remainingalts, $altregs)) {
694 if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION
]) {
695 $wrapped->fraction
[] = '1';
696 } else if ($percentile = $altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION
]){
697 $wrapped->fraction
[] = .01 * $percentile;
699 $wrapped->fraction
[] = '0';
701 if (isset($altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK
])) {
702 $wrapped->feedback
[] = html_entity_decode($altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK
], ENT_QUOTES
, 'UTF-8');
704 $wrapped->feedback
[] = '';
706 if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL
])
707 && ereg(NUMERICAL_ALTERNATIVE_REGEX
, $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER
], $numregs)) {
708 $wrapped->answer
[] = $numregs[NUMERICAL_CORRECT_ANSWER
];
709 if ($numregs[NUMERICAL_ABS_ERROR_MARGIN
]) {
710 $wrapped->tolerance
[] =
711 $numregs[NUMERICAL_ABS_ERROR_MARGIN
];
713 $wrapped->tolerance
[] = 0;
715 } else { // Tolerance can stay undefined for non numerical questions
716 // Undo quoting done by the HTML editor.
717 $wrapped->answer
[] = html_entity_decode($altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER
], ENT_QUOTES
, 'UTF-8');
719 $tmp = explode($altregs[0], $remainingalts, 2);
720 $remainingalts = $tmp[1];
723 $question->defaultgrade +
= $wrapped->defaultgrade
;
724 $question->options
->questions
[$positionkey] = clone($wrapped);
725 $question->questiontext
= implode("{#$positionkey}",
726 explode($answerregs[0], $question->questiontext
, 2));
728 $question->questiontext
= $question->questiontext
;