MDL-24168 questions can be created and edited
[moodle.git] / question / type / multianswer / questiontype.php
CommitLineData
aeb15530 1<?php
516cf3eb 2
3///////////////////
4/// MULTIANSWER /// (Embedded - cloze)
5///////////////////
6
7///
8/// The multianswer question type is special in that it
9/// depends on a few other question types, i.e.
dfa47f96 10/// 'multichoice', 'shortanswer' and 'numerical'.
516cf3eb 11/// These question types have got a few special features that
dfa47f96 12/// makes them useable by the 'multianswer' question type
516cf3eb 13///
14
15/// QUESTION TYPE CLASS //////////////////
1976496e 16/**
17 * @package questionbank
18 * @subpackage questiontypes
7375c542 19 */
7518b645 20class embedded_cloze_qtype extends default_questiontype {
516cf3eb 21
22 function name() {
23 return 'multianswer';
24 }
aeb15530 25
869309b8 26 function has_wildcards_in_responses($question, $subqid) {
fef8f84e 27 global $QTYPES, $OUTPUT;
869309b8 28 foreach ($question->options->questions as $subq){
29 if ($subq->id == $subqid){
30 return $QTYPES[$subq->qtype]->has_wildcards_in_responses($subq, $subqid);
31 }
32 }
fef8f84e 33 echo $OUTPUT->notification('Could not find sub question!');
869309b8 34 return true;
35 }
36
37 function requires_qtypes() {
38 return array('shortanswer', 'numerical', 'multichoice');
39 }
516cf3eb 40
41 function get_question_options(&$question) {
fef8f84e 42 global $QTYPES, $DB, $OUTPUT;
516cf3eb 43
44 // Get relevant data indexed by positionkey from the multianswers table
f34488b2 45 if (!$sequence = $DB->get_field('question_multianswer', 'sequence', array('question' => $question->id))) {
fef8f84e 46 echo $OUTPUT->notification(get_string('noquestions','qtype_multianswer',$question->name));
857caf3b 47 $question->options->questions['1']= '';
48 return true ;
516cf3eb 49 }
50
44e1b7d7 51 $wrappedquestions = $DB->get_records_list('question', 'id', explode(',', $sequence), 'id ASC');
516cf3eb 52
53 // We want an array with question ids as index and the positions as values
54 $sequence = array_flip(explode(',', $sequence));
55 array_walk($sequence, create_function('&$val', '$val++;'));
857caf3b 56 //If a question is lost, the corresponding index is null
aeb15530 57 // so this null convention is used to test $question->options->questions
857caf3b 58 // before using the values.
aeb15530 59 // first all possible questions from sequence are nulled
857caf3b 60 // then filled with the data if available in $wrappedquestions
61 $nbvaliquestion = 0 ;
df79079f 62 foreach($sequence as $seq){
63 $question->options->questions[$seq]= '';
64 }
65 if (isset($wrappedquestions) && is_array($wrappedquestions)){
66 foreach ($wrappedquestions as $wrapped) {
67 if (!$QTYPES[$wrapped->qtype]->get_question_options($wrapped)) {
fef8f84e 68 echo $OUTPUT->notification("Unable to get options for questiontype {$wrapped->qtype} (id={$wrapped->id})");
857caf3b 69 }else {
df79079f 70 // for wrapped questions the maxgrade is always equal to the defaultgrade,
71 // there is no entry in the question_instances table for them
72 $wrapped->maxgrade = $wrapped->defaultgrade;
857caf3b 73 $nbvaliquestion++ ;
df79079f 74 $question->options->questions[$sequence[$wrapped->id]] = clone($wrapped); // ??? Why do we need a clone here?
516cf3eb 75 }
516cf3eb 76 }
857caf3b 77 }
78 if ($nbvaliquestion == 0 ) {
fef8f84e 79 echo $OUTPUT->notification(get_string('noquestions','qtype_multianswer',$question->name));
857caf3b 80 }
516cf3eb 81
82 return true;
83 }
84
85 function save_question_options($question) {
44e1b7d7 86 global $QTYPES, $DB;
da298d82 87 $result = new stdClass;
9fc3100f 88
516cf3eb 89 // This function needs to be able to handle the case where the existing set of wrapped
90 // questions does not match the new set of wrapped questions so that some need to be
91 // created, some modified and some deleted
92 // Unfortunately the code currently simply overwrites existing ones in sequence. This
9fc3100f 93 // will make re-marking after a re-ordering of wrapped questions impossible and
516cf3eb 94 // will also create difficulties if questiontype specific tables reference the id.
9fc3100f 95
516cf3eb 96 // First we get all the existing wrapped questions
f34488b2 97 if (!$oldwrappedids = $DB->get_field('question_multianswer', 'sequence', array('question' => $question->id))) {
857caf3b 98 $oldwrappedquestions = array();
0a5b58af 99 } else {
857caf3b 100 $oldwrappedquestions = $DB->get_records_list('question', 'id', explode(',', $oldwrappedids), 'id ASC');
516cf3eb 101 }
516cf3eb 102 $sequence = array();
103 foreach($question->options->questions as $wrapped) {
103a800d 104 if (!empty($wrapped)){
df79079f 105 // if we still have some old wrapped question ids, reuse the next of them
f34488b2 106
857caf3b 107 if (is_array($oldwrappedquestions) && $oldwrappedquestion = array_shift($oldwrappedquestions)) {
108 $wrapped->id = $oldwrappedquestion->id;
109 if($oldwrappedquestion->qtype != $wrapped->qtype ) {
110 switch ($oldwrappedquestion->qtype) {
df79079f 111 case 'multichoice':
857caf3b 112 $DB->delete_records('question_multichoice', array('question' => $oldwrappedquestion->id));
df79079f 113 break;
114 case 'shortanswer':
857caf3b 115 $DB->delete_records('question_shortanswer', array('question' => $oldwrappedquestion->id));
df79079f 116 break;
117 case 'numerical':
857caf3b 118 $DB->delete_records('question_numerical', array('question' => $oldwrappedquestion->id));
df79079f 119 break;
120 default:
857caf3b 121 print_error('qtypenotrecognized', 'qtype_multianswer','',$oldwrappedquestion->qtype);
df79079f 122 $wrapped->id = 0 ;
df79079f 123 }
e9028ffc 124 }
df79079f 125 }else {
126 $wrapped->id = 0 ;
e9028ffc 127 }
516cf3eb 128 }
77fa3a0d 129 $wrapped->name = $question->name;
130 $wrapped->parent = $question->id;
26053641 131 $previousid = $wrapped->id ;
80fdc53e 132 $wrapped->category = $question->category . ',1'; // save_question strips this extra bit off again.
f02c6f01 133 $wrapped = $QTYPES[$wrapped->qtype]->save_question($wrapped,
77fa3a0d 134 $wrapped, $question->course);
516cf3eb 135 $sequence[] = $wrapped->id;
aeb15530 136 if ($previousid != 0 && $previousid != $wrapped->id ) {
26053641 137 // for some reasons a new question has been created
138 // so delete the old one
139 delete_question($previousid) ;
140 }
516cf3eb 141 }
142
143 // Delete redundant wrapped questions
26053641 144 if(is_array($oldwrappedquestions) && count($oldwrappedquestions)){
145 foreach ($oldwrappedquestions as $oldwrappedquestion) {
146 delete_question($oldwrappedquestion->id) ;
e9028ffc 147 }
4bc4ca50 148 }
516cf3eb 149
150 if (!empty($sequence)) {
151 $multianswer = new stdClass;
152 $multianswer->question = $question->id;
153 $multianswer->sequence = implode(',', $sequence);
f34488b2 154 if ($oldid = $DB->get_field('question_multianswer', 'id', array('question' => $question->id))) {
516cf3eb 155 $multianswer->id = $oldid;
bb4b6010 156 $DB->update_record("question_multianswer", $multianswer);
516cf3eb 157 } else {
bb4b6010 158 $DB->insert_record("question_multianswer", $multianswer);
516cf3eb 159 }
160 }
161 }
162
163 function save_question($authorizedquestion, $form, $course) {
e51efd7e 164 $question = qtype_multianswer_extract_question($form->questiontext);
516cf3eb 165 if (isset($authorizedquestion->id)) {
166 $question->id = $authorizedquestion->id;
516cf3eb 167 }
168
516cf3eb 169 $question->category = $authorizedquestion->category;
170 $form->course = $course; // To pass the course object to
171 // save_question_options, where it is
172 // needed to call type specific
173 // save_question methods.
174 $form->defaultgrade = $question->defaultgrade;
175 $form->questiontext = $question->questiontext;
176 $form->questiontextformat = 0;
77fa3a0d 177 $form->options = clone($question->options);
516cf3eb 178 unset($question->options);
179 return parent::save_question($question, $form, $course);
180 }
181
182 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
183 $state->responses = array();
184 foreach ($question->options->questions as $key => $wrapped) {
185 $state->responses[$key] = '';
186 }
187 return true;
188 }
189
190 function restore_session_and_responses(&$question, &$state) {
191 $responses = explode(',', $state->responses['']);
192 $state->responses = array();
193 foreach ($responses as $response) {
194 $tmp = explode("-", $response);
195 // restore encoded characters
77fa3a0d 196 $state->responses[$tmp[0]] = str_replace(array("&#0044;", "&#0045;"),
197 array(",", "-"), $tmp[1]);
516cf3eb 198 }
199 return true;
200 }
201
202 function save_session_and_responses(&$question, &$state) {
f34488b2 203 global $DB;
516cf3eb 204 $responses = $state->responses;
77fa3a0d 205 // encode - (hyphen) and , (comma) to &#0045; because they are used as
206 // delimiters
516cf3eb 207 array_walk($responses, create_function('&$val, $key',
77fa3a0d 208 '$val = str_replace(array(",", "-"), array("&#0044;", "&#0045;"), $val);
209 $val = "$key-$val";'));
516cf3eb 210 $responses = implode(',', $responses);
211
212 // Set the legacy answer field
f685e830 213 $DB->set_field('question_states', 'answer', $responses, array('id' => $state->id));
516cf3eb 214 return true;
215 }
216
217 /**
218 * Deletes question from the question-type specific tables
219 *
220 * @return boolean Success/Failure
221 * @param object $question The question being deleted
222 */
90c3f310 223 function delete_question($questionid) {
f34488b2 224 global $DB;
225 $DB->delete_records("question_multianswer", array("question" => $questionid));
516cf3eb 226 return true;
227 }
228
229 function get_correct_responses(&$question, &$state) {
f02c6f01 230 global $QTYPES;
516cf3eb 231 $responses = array();
232 foreach($question->options->questions as $key => $wrapped) {
103a800d 233 if (!empty($wrapped)){
8795a5ae 234 if ($correct = $QTYPES[$wrapped->qtype]->get_correct_responses($wrapped, $state)) {
235 $responses[$key] = $correct[''];
236 } else {
237 // if there is no correct answer to this subquestion then there
238 // can not be a correct answer to the whole question either, so
239 // we have to return null.
240 return null;
241 }
516cf3eb 242 }
243 }
244 return $responses;
245 }
246
869309b8 247 function get_possible_responses(&$question) {
248 global $QTYPES;
249 $responses = array();
250 foreach($question->options->questions as $key => $wrapped) {
103a800d 251 if (!empty($wrapped)){
869309b8 252 if ($correct = $QTYPES[$wrapped->qtype]->get_possible_responses($wrapped)) {
253 $responses += $correct;
254 } else {
255 // if there is no correct answer to this subquestion then there
256 // can not be a correct answer to the whole question either, so
257 // we have to return null.
258 return null;
259 }
260 }
261 }
262 return $responses;
263 }
264 function get_actual_response_details($question, $state){
265 global $QTYPES;
266 $details = array();
267 foreach($question->options->questions as $key => $wrapped) {
103a800d 268 if (!empty($wrapped)){
869309b8 269 $stateforquestion = clone($state);
270 $stateforquestion->responses[''] = $state->responses[$key];
271 $details = array_merge($details, $QTYPES[$wrapped->qtype]->get_actual_response_details($wrapped, $stateforquestion));
272 }
273 }
274 return $details;
275 }
276
45c4a5c7
TH
277 function get_html_head_contributions(&$question, &$state) {
278 global $PAGE;
279 parent::get_html_head_contributions($question, $state);
280 $PAGE->requires->js('/lib/overlib/overlib.js', true);
281 $PAGE->requires->js('/lib/overlib/overlib_cssstyle.js', true);
282 }
283
516cf3eb 284 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
458eb0d1 285 global $QTYPES, $CFG, $USER, $OUTPUT, $PAGE;
9fc3100f 286
45c4a5c7
TH
287 static $overlibdivoutput = false;
288 if (!$overlibdivoutput) {
289 echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
290 $overlibdivoutput = true;
291 }
292
516cf3eb 293 $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
6463e8a6 294 $disabled = empty($options->readonly) ? '' : 'disabled="disabled"';
da298d82 295 $formatoptions = new stdClass;
7347c60b 296 $formatoptions->noclean = true;
297 $formatoptions->para = false;
516cf3eb 298 $nameprefix = $question->name_prefix;
9fc3100f 299
73ca1421 300 // adding an icon with alt to warn user this is a fill in the gap question
301 // MDL-7497
ccffd412 302 if (!empty($USER->screenreader)) {
0c3c5493 303 echo "<img src=\"".$OUTPUT->pix_url('icon', 'qtype_'.$question->qtype)."\" ".
0d905d9f 304 "class=\"icon\" alt=\"".get_string('clozeaid','qtype_multichoice')."\" /> ";
73ca1421 305 }
06e2b0de 306
307 echo '<div class="ablock clearfix">';
9fc3100f 308
516cf3eb 309 $qtextremaining = format_text($question->questiontext,
77fa3a0d 310 $question->questiontextformat, $formatoptions, $cmoptions->course);
516cf3eb 311
312 $strfeedback = get_string('feedback', 'quiz');
313
314 // The regex will recognize text snippets of type {#X}
315 // where the X can be any text not containg } or white-space characters.
6dbcacee 316 while (preg_match('~\{#([^[:space:]}]*)}~', $qtextremaining, $regs)) {
516cf3eb 317 $qtextsplits = explode($regs[0], $qtextremaining, 2);
318 echo $qtextsplits[0];
0bddf4b6 319 echo "<label>"; // MDL-7497
516cf3eb 320 $qtextremaining = $qtextsplits[1];
321
322 $positionkey = $regs[1];
df79079f 323 if (isset($question->options->questions[$positionkey]) && $question->options->questions[$positionkey] != ''){
516cf3eb 324 $wrapped = &$question->options->questions[$positionkey];
325 $answers = &$wrapped->options->answers;
907a3759 326 // $correctanswers = $QTYPES[$wrapped->qtype]->get_correct_responses($wrapped, $state);
516cf3eb 327
328 $inputname = $nameprefix.$positionkey;
e51efd7e 329 if (isset($state->responses[$positionkey])) {
1f8db780 330 $response = $state->responses[$positionkey];
e51efd7e 331 } else {
332 $response = null;
333 }
f26c5297 334 // echo "<p> multianswer positionkey $positionkey response $response state <pre>";print_r($state);echo "</pre></p>";
516cf3eb 335
336 // Determine feedback popup if any
337 $popup = '';
338 $style = '';
2b087056 339 $feedbackimg = '';
907a3759 340 $feedback = '' ;
341 $correctanswer = '';
f34488b2 342 $strfeedbackwrapped = $strfeedback;
f34488b2 343 $testedstate = clone($state);
f26c5297 344 if ($correctanswers = $QTYPES[$wrapped->qtype]->get_correct_responses($wrapped, $state)) {
907a3759 345 if ($options->readonly && $options->correct_responses) {
346 $delimiter = '';
347 if ($correctanswers) {
348 foreach ($correctanswers as $ca) {
349 switch($wrapped->qtype){
350 case 'numerical':
f34488b2 351 case 'shortanswer':
907a3759 352 $correctanswer .= $delimiter.$ca;
353 break ;
354 case 'multichoice':
355 if (isset($answers[$ca])){
356 $correctanswer .= $delimiter.$answers[$ca]->answer;
357 }
358 break ;
f34488b2 359 }
907a3759 360 $delimiter = ', ';
361 }
362 }
363 }
ba954145 364 if ($correctanswer != '' ) {
907a3759 365 $feedback = '<div class="correctness">';
cdfaa838 366 $feedback .= get_string('correctansweris', 'quiz', s($correctanswer));
907a3759 367 $feedback .= '</div>';
907a3759 368 }
369 }
458eb0d1 370
516cf3eb 371 if ($options->feedback) {
372 $chosenanswer = null;
373 switch ($wrapped->qtype) {
dfa47f96 374 case 'numerical':
dfa47f96 375 case 'shortanswer':
516cf3eb 376 $testedstate = clone($state);
377 $testedstate->responses[''] = $response;
516cf3eb 378 foreach ($answers as $answer) {
f02c6f01 379 if($QTYPES[$wrapped->qtype]
bc2defd5 380 ->test_response($wrapped, $testedstate, $answer)) {
381 $chosenanswer = clone($answer);
382 break;
516cf3eb 383 }
384 }
385 break;
dfa47f96 386 case 'multichoice':
516cf3eb 387 if (isset($answers[$response])) {
388 $chosenanswer = clone($answers[$response]);
389 }
390 break;
391 default:
392 break;
393 }
394
395 // Set up a default chosenanswer so that all non-empty wrong
396 // answers are highlighted red
b7c81cf0 397 if (empty($chosenanswer) && $response != '') {
516cf3eb 398 $chosenanswer = new stdClass;
399 $chosenanswer->fraction = 0.0;
400 }
401
402 if (!empty($chosenanswer->feedback)) {
907a3759 403 $feedback = s(str_replace(array("\\", "'"), array("\\\\", "\\'"), $feedback.$chosenanswer->feedback));
404 if ($options->readonly && $options->correct_responses) {
405 $strfeedbackwrapped = get_string('correctanswerandfeedback', 'qtype_multianswer');
406 }else {
407 $strfeedbackwrapped = get_string('feedback', 'quiz');
408 }
409 $popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
516cf3eb 410 " onmouseout=\"return nd();\" ";
411 }
412
413 /// Determine style
d9935f29 414 if ($options->feedback && $response != '') {
2b087056 415 $style = 'class = "'.question_get_feedback_class($chosenanswer->fraction).'"';
416 $feedbackimg = question_get_feedback_image($chosenanswer->fraction);
516cf3eb 417 } else {
418 $style = '';
2b087056 419 $feedbackimg = '';
516cf3eb 420 }
421 }
907a3759 422 if ($feedback !='' && $popup == ''){
f34488b2 423 $strfeedbackwrapped = get_string('correctanswer', 'qtype_multianswer');
907a3759 424 $feedback = s(str_replace(array("\\", "'"), array("\\\\", "\\'"), $feedback));
425 $popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
426 " onmouseout=\"return nd();\" ";
427 }
516cf3eb 428
429 // Print the input control
430 switch ($wrapped->qtype) {
dfa47f96 431 case 'shortanswer':
432 case 'numerical':
eba53585 433 $size = 1 ;
434 foreach ($answers as $answer) {
435 if (strlen(trim($answer->answer)) > $size ){
436 $size = strlen(trim($answer->answer));
437 }
f34488b2 438 }
eba53585 439 if (strlen(trim($response))> $size ){
440 $size = strlen(trim($response))+1;
441 }
442 $size = $size + rand(0,$size*0.15);
f34488b2 443 $size > 60 ? $size = 60 : $size = $size;
b119a40f 444 $styleinfo = "size=\"$size\"";
eba53585 445 /**
446 * Uncomment the following lines if you want to limit for small sizes.
f34488b2 447 * Results may vary with browsers see MDL-3274
eba53585 448 */
f34488b2 449 /*
eba53585 450 if ($size < 2) {
451 $styleinfo = 'style="width: 1.1em;"';
452 }
453 if ($size == 2) {
454 $styleinfo = 'style="width: 1.9em;"';
455 }
456 if ($size == 3) {
457 $styleinfo = 'style="width: 2.3em;"';
458 }
459 if ($size == 4) {
460 $styleinfo = 'style="width: 2.8em;"';
461 }
462 */
f34488b2 463
814a96fa 464 echo "<input $style $readonly $popup name=\"$inputname\"";
cdfaa838 465 echo " type=\"text\" value=\"".s($response)."\" ".$styleinfo." /> ";
ccffd412 466 if (!empty($feedback) && !empty($USER->screenreader)) {
b5d0cafc 467 echo "<img src=\"" . $OUTPUT->pix_url('i/feedback') . "\" alt=\"$feedback\" />";
04d6ac46 468 }
2b087056 469 echo $feedbackimg;
516cf3eb 470 break;
dfa47f96 471 case 'multichoice':
e5ebbd53 472 if ($wrapped->options->layout == 0 ){
473 $outputoptions = '<option></option>'; // Default empty option
474 foreach ($answers as $mcanswer) {
77fa3a0d 475 $selected = '';
476 if ($response == $mcanswer->id) {
477 $selected = ' selected="selected"';
478 }
479 $outputoptions .= "<option value=\"$mcanswer->id\"$selected>" .
cdfaa838 480 s($mcanswer->answer) . '</option>';
e5ebbd53 481 }
482 // In the next line, $readonly is invalid HTML, but it works in
483 // all browsers. $disabled would be valid, but then the JS for
484 // displaying the feedback does not work. Of course, we should
485 // not be relying on JS (for accessibility reasons), but that is
486 // a bigger problem.
487 //
488 // The span is used for safari, which does not allow styling of
489 // selects.
490 echo "<span $style><select $popup $readonly $style name=\"$inputname\">";
491 echo $outputoptions;
492 echo '</select></span>';
493 if (!empty($feedback) && !empty($USER->screenreader)) {
b5d0cafc 494 echo "<img src=\"" . $OUTPUT->pix_url('i/feedback') . "\" alt=\"$feedback\" />";
e5ebbd53 495 }
496 echo $feedbackimg;
497 }else if ($wrapped->options->layout == 1 || $wrapped->options->layout == 2){
e5ebbd53 498 $ordernumber=0;
499 $anss = Array();
500 foreach ($answers as $mcanswer) {
501 $ordernumber++;
502 $checked = '';
503 $chosen = false;
504 $type = 'type="radio"';
505 $name = "name=\"{$inputname}\"";
506 if ($response == $mcanswer->id) {
507 $checked = 'checked="checked"';
508 $chosen = true;
509 }
510 $a = new stdClass;
511 $a->id = $question->name_prefix . $mcanswer->id;
e5ebbd53 512 $a->class = '';
513 $a->feedbackimg = '';
aeb15530 514
e5ebbd53 515 // Print the control
516 $a->control = "<input $readonly id=\"$a->id\" $name $checked $type value=\"$mcanswer->id\" />";
517 if ($options->correct_responses && $mcanswer->fraction > 0) {
518 $a->class = question_get_feedback_class(1);
519 }
520 if (($options->feedback && $chosen) || $options->correct_responses) {
521 if ($type == ' type="checkbox" ') {
522 $a->feedbackimg = question_get_feedback_image($mcanswer->fraction > 0 ? 1 : 0, $chosen && $options->feedback);
523 } else {
524 $a->feedbackimg = question_get_feedback_image($mcanswer->fraction, $chosen && $options->feedback);
516cf3eb 525 }
e5ebbd53 526 }
aeb15530 527
0bddf4b6 528 // Print the answer text: no automatic numbering
529
e95d204d 530 $a->text =format_text($mcanswer->answer, FORMAT_MOODLE, $formatoptions, $cmoptions->course);
aeb15530 531
e5ebbd53 532 // Print feedback if feedback is on
533 if (($options->feedback || $options->correct_responses) && ($checked )) { //|| $options->readonly
534 $a->feedback = format_text($mcanswer->feedback, true, $formatoptions, $cmoptions->course);
535 } else {
536 $a->feedback = '';
537 }
aeb15530 538
e5ebbd53 539 $anss[] = clone($a);
540 }
541 ?>
542 <?php if ($wrapped->options->layout == 1 ){
543 ?>
544 <table class="answer">
545 <?php $row = 1; foreach ($anss as $answer) { ?>
546 <tr class="<?php echo 'r'.$row = $row ? 0 : 1; ?>">
547 <td class="c0 control">
548 <?php echo $answer->control; ?>
549 </td>
550 <td class="c1 text <?php echo $answer->class ?>">
551 <label for="<?php echo $answer->id ?>">
552 <?php echo $answer->text; ?>
553 <?php echo $answer->feedbackimg; ?>
554 </label>
555 </td>
556 <td class="c0 feedback">
557 <?php echo $answer->feedback; ?>
558 </td>
559 </tr>
560 <?php } ?>
561 </table>
562 <?php }else if ($wrapped->options->layout == 2 ){
563 ?>
aeb15530 564
e5ebbd53 565 <table class="answer">
566 <tr class="<?php echo 'r'.$row = $row ? 0 : 1; ?>">
567 <?php $row = 1; foreach ($anss as $answer) { ?>
568 <td class="c0 control">
569 <?php echo $answer->control; ?>
570 </td>
571 <td class="c1 text <?php echo $answer->class ?>">
572 <label for="<?php echo $answer->id ?>">
573 <?php echo $answer->text; ?>
574 <?php echo $answer->feedbackimg; ?>
575 </label>
576 </td>
577 <td class="c0 feedback">
578 <?php echo $answer->feedback; ?>
579 </td>
580 <?php } ?>
581 </tr>
582 </table>
aeb15530
PS
583 <?php }
584
e5ebbd53 585 }else {
586 echo "no valid layout";
e0c25647 587 }
aeb15530 588
e0c25647 589 break;
590 default:
857caf3b 591 $a = new stdClass;
aeb15530 592 $a->type = $wrapped->qtype ;
857caf3b 593 $a->sub = $positionkey;
594 print_error('unknownquestiontypeofsubquestion', 'qtype_multianswer','',$a);
e0c25647 595 break;
516cf3eb 596 }
73ca1421 597 echo "</label>"; // MDL-7497
516cf3eb 598 }
df79079f 599 else {
600 if(! isset($question->options->questions[$positionkey])){
ea632e88 601 echo $regs[0]."</label>";
df79079f 602 }else {
ea632e88 603 echo '</label><div class="error" >'.get_string('questionnotfound','qtype_multianswer',$positionkey).'</div>';
df79079f 604 }
605 }
606 }
516cf3eb 607
608 // Print the final piece of question text:
609 echo $qtextremaining;
f4b72cdb 610 $this->print_question_submit_buttons($question, $state, $cmoptions, $options);
f34488b2 611 echo '</div>';
516cf3eb 612 }
613
614 function grade_responses(&$question, &$state, $cmoptions) {
f02c6f01 615 global $QTYPES;
516cf3eb 616 $teststate = clone($state);
617 $state->raw_grade = 0;
618 foreach($question->options->questions as $key => $wrapped) {
103a800d 619 if (!empty($wrapped)){
0bddf4b6 620 if(isset($state->responses[$key])){
621 $state->responses[$key] = $state->responses[$key];
622 }else {
623 $state->responses[$key] = '' ;
624 }
625 $teststate->responses = array('' => $state->responses[$key]);
626 $teststate->raw_grade = 0;
627 if (false === $QTYPES[$wrapped->qtype]
628 ->grade_responses($wrapped, $teststate, $cmoptions)) {
629 return false;
630 }
631 $state->raw_grade += $teststate->raw_grade;
516cf3eb 632 }
df79079f 633 }
516cf3eb 634 $state->raw_grade /= $question->defaultgrade;
635 $state->raw_grade = min(max((float) $state->raw_grade, 0.0), 1.0)
636 * $question->maxgrade;
637
638 if (empty($state->raw_grade)) {
639 $state->raw_grade = 0.0;
640 }
641 $state->penalty = $question->penalty * $question->maxgrade;
642
f30bbcaf 643 // mark the state as graded
644 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
645
516cf3eb 646 return true;
647 }
648
649 function get_actual_response($question, $state) {
f02c6f01 650 global $QTYPES;
516cf3eb 651 $teststate = clone($state);
652 foreach($question->options->questions as $key => $wrapped) {
653 $state->responses[$key] = html_entity_decode($state->responses[$key]);
654 $teststate->responses = array('' => $state->responses[$key]);
f02c6f01 655 $correct = $QTYPES[$wrapped->qtype]
516cf3eb 656 ->get_actual_response($wrapped, $teststate);
2280e147 657 $responses[$key] = implode(';', $correct);
516cf3eb 658 }
659 return $responses;
660 }
aeb15530 661
6f51ed72 662 /**
663 * @param object $question
455c3efa 664 * @return mixed either a integer score out of 1 that the average random
665 * guess by a student might give or an empty string which means will not
666 * calculate.
6f51ed72 667 */
668 function get_random_guess_score($question) {
669 $totalfraction = 0;
670 foreach (array_keys($question->options->questions) as $key){
455c3efa 671 $totalfraction += question_get_random_guess_score($question->options->questions[$key]);
6f51ed72 672 }
673 return $totalfraction / count($question->options->questions);
674 }
9fc3100f 675
c5d94c41 676/// BACKUP FUNCTIONS ////////////////////////////
677
678 /*
679 * Backup the data in the question
680 *
681 * This is used in question/backuplib.php
682 */
683 function backup($bf,$preferences,$question,$level=6) {
f34488b2 684 global $DB;
c5d94c41 685 $status = true;
686
f34488b2 687 $multianswers = $DB->get_records("question_multianswer",array("question" => $question),"id");
c5d94c41 688 //If there are multianswers
689 if ($multianswers) {
690 //Print multianswers header
691 $status = fwrite ($bf,start_tag("MULTIANSWERS",$level,true));
692 //Iterate over each multianswer
693 foreach ($multianswers as $multianswer) {
694 $status = fwrite ($bf,start_tag("MULTIANSWER",$level+1,true));
695 //Print multianswer contents
696 fwrite ($bf,full_tag("ID",$level+2,false,$multianswer->id));
697 fwrite ($bf,full_tag("QUESTION",$level+2,false,$multianswer->question));
698 fwrite ($bf,full_tag("SEQUENCE",$level+2,false,$multianswer->sequence));
699 $status = fwrite ($bf,end_tag("MULTIANSWER",$level+1,true));
700 }
701 //Print multianswers footer
702 $status = fwrite ($bf,end_tag("MULTIANSWERS",$level,true));
703 //Now print question_answers
704 $status = question_backup_answers($bf,$preferences,$question);
705 }
706 return $status;
707 }
708
315559d3 709/// RESTORE FUNCTIONS /////////////////
710
711 /*
712 * Restores the data in the question
713 *
714 * This is used in question/restorelib.php
715 */
716 function restore($old_question_id,$new_question_id,$info,$restore) {
9db7dab2 717 global $DB;
315559d3 718
719 $status = true;
720
721 //Get the multianswers array
722 $multianswers = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
723 //Iterate over multianswers
724 for($i = 0; $i < sizeof($multianswers); $i++) {
725 $mul_info = $multianswers[$i];
726
727 //We need this later
728 $oldid = backup_todb($mul_info['#']['ID']['0']['#']);
729
730 //Now, build the question_multianswer record structure
da298d82 731 $multianswer = new stdClass;
315559d3 732 $multianswer->question = $new_question_id;
733 $multianswer->sequence = backup_todb($mul_info['#']['SEQUENCE']['0']['#']);
734
735 //We have to recode the sequence field (a list of question ids)
736 //Extracts question id from sequence
737 $sequence_field = "";
738 $in_first = true;
739 $tok = strtok($multianswer->sequence,",");
740 while ($tok) {
741 //Get the answer from backup_ids
742 $question = backup_getid($restore->backup_unique_code,"question",$tok);
743 if ($question) {
744 if ($in_first) {
745 $sequence_field .= $question->new_id;
746 $in_first = false;
747 } else {
748 $sequence_field .= ",".$question->new_id;
749 }
750 }
751 //check for next
752 $tok = strtok(",");
753 }
754 //We have the answers field recoded to its new ids
755 $multianswer->sequence = $sequence_field;
756 //The structure is equal to the db, so insert the question_multianswer
9db7dab2 757 $newid = $DB->insert_record("question_multianswer", $multianswer);
315559d3 758
759 //Save ids in backup_ids
760 if ($newid) {
761 backup_putid($restore->backup_unique_code,"question_multianswer",
762 $oldid, $newid);
763 }
764
765 //Do some output
766 if (($i+1) % 50 == 0) {
767 if (!defined('RESTORE_SILENTLY')) {
768 echo ".";
769 if (($i+1) % 1000 == 0) {
770 echo "<br />";
771 }
772 }
773 backup_flush(300);
774 }
315559d3 775 }
776
777 return $status;
778 }
779
780 function restore_map($old_question_id,$new_question_id,$info,$restore) {
9db7dab2 781 global $DB;
315559d3 782
783 $status = true;
784
785 //Get the multianswers array
786 $multianswers = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
787 //Iterate over multianswers
788 for($i = 0; $i < sizeof($multianswers); $i++) {
789 $mul_info = $multianswers[$i];
790
791 //We need this later
792 $oldid = backup_todb($mul_info['#']['ID']['0']['#']);
793
794 //Now, build the question_multianswer record structure
795 $multianswer->question = $new_question_id;
796 $multianswer->answers = backup_todb($mul_info['#']['ANSWERS']['0']['#']);
797 $multianswer->positionkey = backup_todb($mul_info['#']['POSITIONKEY']['0']['#']);
798 $multianswer->answertype = backup_todb($mul_info['#']['ANSWERTYPE']['0']['#']);
799 $multianswer->norm = backup_todb($mul_info['#']['NORM']['0']['#']);
800
801 //If we are in this method is because the question exists in DB, so its
802 //multianswer must exist too.
803 //Now, we are going to look for that multianswer in DB and to create the
804 //mappings in backup_ids to use them later where restoring states (user level).
805
806 //Get the multianswer from DB (by question and positionkey)
9db7dab2 807 $db_multianswer = $DB->get_record ("question_multianswer",array("question"=>$new_question_id,
808 "positionkey"=>$multianswer->positionkey));
315559d3 809 //Do some output
810 if (($i+1) % 50 == 0) {
811 if (!defined('RESTORE_SILENTLY')) {
812 echo ".";
813 if (($i+1) % 1000 == 0) {
814 echo "<br />";
815 }
816 }
817 backup_flush(300);
818 }
819
820 //We have the database multianswer, so update backup_ids
821 if ($db_multianswer) {
822 //We have the newid, update backup_ids
823 backup_putid($restore->backup_unique_code,"question_multianswer",$oldid,
824 $db_multianswer->id);
825 } else {
826 $status = false;
827 }
828 }
829
830 return $status;
831 }
832
833 function restore_recode_answer($state, $restore) {
fef8f84e 834 global $DB, $OUTPUT;
315559d3 835 //The answer is a comma separated list of hypen separated sequence number and answers. We may have to recode the answers
836 $answer_field = "";
837 $in_first = true;
838 $tok = strtok($state->answer,",");
839 while ($tok) {
840 //Extract the multianswer_id and the answer
841 $exploded = explode("-",$tok);
842 $seqnum = $exploded[0];
843 $answer = $exploded[1];
844 // $sequence is an ordered array of the question ids.
f34488b2 845 if (!$sequence = $DB->get_field('question_multianswer', 'sequence', array('question' => $state->question))) {
2471ef86 846 print_error('missingoption', 'question', '', $state->question);
315559d3 847 }
848 $sequence = explode(',', $sequence);
849 // The id of the current question.
850 $wrappedquestionid = $sequence[$seqnum-1];
851 // now we can find the question
f34488b2 852 if (!$wrappedquestion = $DB->get_record('question', array('id' => $wrappedquestionid))) {
fef8f84e 853 echo $OUTPUT->notification("Can't find the subquestion $wrappedquestionid that is used as part $seqnum in cloze question $state->question");
315559d3 854 }
855 // For multichoice question we need to recode the answer
dfa47f96 856 if ($answer and $wrappedquestion->qtype == 'multichoice') {
315559d3 857 //The answer is an answer_id, look for it in backup_ids
858 if (!$ans = backup_getid($restore->backup_unique_code,"question_answers",$answer)) {
859 echo 'Could not recode cloze multichoice answer '.$answer.'<br />';
860 }
861 $answer = $ans->new_id;
862 }
863 //build the new answer field for each pair
864 if ($in_first) {
865 $answer_field .= $seqnum."-".$answer;
866 $in_first = false;
867 } else {
868 $answer_field .= ",".$seqnum."-".$answer;
869 }
870 //check for next
871 $tok = strtok(",");
872 }
873 return $answer_field;
874 }
875
b9bd6da4 876 /**
877 * Runs all the code required to set up and save an essay question for testing purposes.
878 * Alternate DB table prefix may be used to facilitate data deletion.
879 */
880 function generate_test($name, $courseid = null) {
881 global $DB;
882 list($form, $question) = parent::generate_test($name, $courseid);
883 $question->category = $form->category;
884 $form->questiontext = "This question consists of some text with an answer embedded right here {1:MULTICHOICE:Wrong answer#Feedback for this wrong answer~Another wrong answer#Feedback for the other wrong answer~=Correct answer#Feedback for correct answer~%50%Answer that gives half the credit#Feedback for half credit answer} and right after that you will have to deal with this short answer {1:SHORTANSWER:Wrong answer#Feedback for this wrong answer~=Correct answer#Feedback for correct answer~%50%Answer that gives half the credit#Feedback for half credit answer} and finally we have a floating point number {2:NUMERICAL:=23.8:0.1#Feedback for correct answer 23.8~%50%23.8:2#Feedback for half credit answer in the nearby region of the correct answer}.
885
886Note that addresses like www.moodle.org and smileys :-) all work as normal:
887 a) How good is this? {:MULTICHOICE:=Yes#Correct~No#We have a different opinion}
888 b) What grade would you give it? {3:NUMERICAL:=3:2}
889
890Good luck!
891";
892 $form->feedback = "feedback";
893 $form->generalfeedback = "General feedback";
894 $form->fraction = 0;
895 $form->penalty = 0.1;
896 $form->versioning = 0;
897
898 if ($courseid) {
899 $course = $DB->get_record('course', array('id' => $courseid));
900 }
901
902 return $this->save_question($question, $form, $course);
903 }
315559d3 904
516cf3eb 905}
906//// END OF CLASS ////
907
908
909//////////////////////////////////////////////////////////////////////////
910//// INITIATION - Without this line the question type is not in use... ///
911//////////////////////////////////////////////////////////////////////////
a2156789 912question_register_questiontype(new embedded_cloze_qtype());
516cf3eb 913
914/////////////////////////////////////////////////////////////
915//// ADDITIONAL FUNCTIONS
916//// The functions below deal exclusivly with editing
dfa47f96 917//// of questions with question type 'multianswer'.
516cf3eb 918//// Therefore they are kept in this file.
919//// They are not in the class as they are not
920//// likely to be subject for overriding.
921/////////////////////////////////////////////////////////////
922
0b346164 923// ANSWER_ALTERNATIVE regexes
924define("ANSWER_ALTERNATIVE_FRACTION_REGEX",
925 '=|%(-?[0-9]+)%');
926// for the syntax '(?<!' see http://www.perl.com/doc/manual/html/pod/perlre.html#item_C
927define("ANSWER_ALTERNATIVE_ANSWER_REGEX",
e51efd7e 928 '.+?(?<!\\\\|&|&amp;)(?=[~#}]|$)');
0b346164 929define("ANSWER_ALTERNATIVE_FEEDBACK_REGEX",
930 '.*?(?<!\\\\)(?=[~}]|$)');
0b346164 931define("ANSWER_ALTERNATIVE_REGEX",
e51efd7e 932 '(' . ANSWER_ALTERNATIVE_FRACTION_REGEX .')?' .
933 '(' . ANSWER_ALTERNATIVE_ANSWER_REGEX . ')' .
934 '(#(' . ANSWER_ALTERNATIVE_FEEDBACK_REGEX .'))?');
0b346164 935
936// Parenthesis positions for ANSWER_ALTERNATIVE_REGEX
937define("ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION", 2);
938define("ANSWER_ALTERNATIVE_REGEX_FRACTION", 1);
939define("ANSWER_ALTERNATIVE_REGEX_ANSWER", 3);
940define("ANSWER_ALTERNATIVE_REGEX_FEEDBACK", 5);
941
942// NUMBER_FORMATED_ALTERNATIVE_ANSWER_REGEX is used
943// for identifying numerical answers in ANSWER_ALTERNATIVE_REGEX_ANSWER
944define("NUMBER_REGEX",
945 '-?(([0-9]+[.,]?[0-9]*|[.,][0-9]+)([eE][-+]?[0-9]+)?)');
946define("NUMERICAL_ALTERNATIVE_REGEX",
947 '^(' . NUMBER_REGEX . ')(:' . NUMBER_REGEX . ')?$');
948
949// Parenthesis positions for NUMERICAL_FORMATED_ALTERNATIVE_ANSWER_REGEX
950define("NUMERICAL_CORRECT_ANSWER", 1);
951define("NUMERICAL_ABS_ERROR_MARGIN", 6);
952
953// Remaining ANSWER regexes
954define("ANSWER_TYPE_DEF_REGEX",
fd97082c 955 '(NUMERICAL|NM)|(MULTICHOICE|MC)|(MULTICHOICE_V|MCV)|(MULTICHOICE_H|MCH)|(SHORTANSWER|SA|MW)|(SHORTANSWER_C|SAC|MWC)');
0b346164 956define("ANSWER_START_REGEX",
957 '\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX . '):');
958
959define("ANSWER_REGEX",
960 ANSWER_START_REGEX
961 . '(' . ANSWER_ALTERNATIVE_REGEX
962 . '(~'
963 . ANSWER_ALTERNATIVE_REGEX
964 . ')*)\}' );
965
966// Parenthesis positions for singulars in ANSWER_REGEX
967define("ANSWER_REGEX_NORM", 1);
968define("ANSWER_REGEX_ANSWER_TYPE_NUMERICAL", 3);
969define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE", 4);
e5ebbd53 970define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR", 5);
971define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL", 6);
972define("ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER", 7);
fd97082c 973define("ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER_C", 8);
974define("ANSWER_REGEX_ALTERNATIVES", 9);
516cf3eb 975
7518b645 976function qtype_multianswer_extract_question($text) {
516cf3eb 977 $question = new stdClass;
dfa47f96 978 $question->qtype = 'multianswer';
516cf3eb 979 $question->questiontext = $text;
980 $question->options->questions = array();
981 $question->defaultgrade = 0; // Will be increased for each answer norm
982
fe6ce234 983 for ($positionkey=1; preg_match('/'.ANSWER_REGEX.'/', $question->questiontext['text'], $answerregs); ++$positionkey ) {
516cf3eb 984 $wrapped = new stdClass;
8795a5ae 985 if (isset($answerregs[ANSWER_REGEX_NORM])&& $answerregs[ANSWER_REGEX_NORM]!== ''){
986 $wrapped->defaultgrade = $answerregs[ANSWER_REGEX_NORM];
987 } else {
988 $wrapped->defaultgrade = '1';
989 }
516cf3eb 990 if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL])) {
dfa47f96 991 $wrapped->qtype = 'numerical';
516cf3eb 992 $wrapped->multiplier = array();
993 $wrapped->units = array();
994 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER])) {
dfa47f96 995 $wrapped->qtype = 'shortanswer';
516cf3eb 996 $wrapped->usecase = 0;
fd97082c 997 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER_C])) {
998 $wrapped->qtype = 'shortanswer';
999 $wrapped->usecase = 1;
516cf3eb 1000 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE])) {
dfa47f96 1001 $wrapped->qtype = 'multichoice';
516cf3eb 1002 $wrapped->single = 1;
271e6dec 1003 $wrapped->answernumbering = 0;
e51efd7e 1004 $wrapped->correctfeedback = '';
1005 $wrapped->partiallycorrectfeedback = '';
1006 $wrapped->incorrectfeedback = '';
e5ebbd53 1007 $wrapped->layout = 0;
1008 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR])) {
1009 $wrapped->qtype = 'multichoice';
1010 $wrapped->single = 1;
1011 $wrapped->answernumbering = 0;
1012 $wrapped->correctfeedback = '';
1013 $wrapped->partiallycorrectfeedback = '';
1014 $wrapped->incorrectfeedback = '';
1015 $wrapped->layout = 1;
1016 } else if(!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL])) {
1017 $wrapped->qtype = 'multichoice';
1018 $wrapped->single = 1;
1019 $wrapped->answernumbering = 0;
1020 $wrapped->correctfeedback = '';
1021 $wrapped->partiallycorrectfeedback = '';
1022 $wrapped->incorrectfeedback = '';
1023 $wrapped->layout = 2;
516cf3eb 1024 } else {
2471ef86 1025 print_error('unknownquestiontype', 'question', '', $answerregs[2]);
516cf3eb 1026 return false;
1027 }
1028
1029 // Each $wrapped simulates a $form that can be processed by the
1030 // respective save_question and save_question_options methods of the
1031 // wrapped questiontypes
1032 $wrapped->answer = array();
1033 $wrapped->fraction = array();
1034 $wrapped->feedback = array();
1035 $wrapped->shuffleanswers = 1;
e51efd7e 1036 $wrapped->questiontext = $answerregs[0];
516cf3eb 1037 $wrapped->questiontextformat = 0;
1038
1039 $remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES];
1040 while (preg_match('/~?'.ANSWER_ALTERNATIVE_REGEX.'/', $remainingalts, $altregs)) {
1041 if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION]) {
1042 $wrapped->fraction[] = '1';
e51efd7e 1043 } else if ($percentile = $altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION]){
516cf3eb 1044 $wrapped->fraction[] = .01 * $percentile;
1045 } else {
1046 $wrapped->fraction[] = '0';
1047 }
e51efd7e 1048 if (isset($altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK])) {
83d22f70 1049 $feedback = html_entity_decode($altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK], ENT_QUOTES, 'UTF-8');
095b599a 1050 $feedback = str_replace('\}', '}', $feedback);
1051 $wrapped->feedback[] = str_replace('\#', '#', $feedback);
e51efd7e 1052 } else {
1053 $wrapped->feedback[] = '';
1054 }
516cf3eb 1055 if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL])
6dbcacee 1056 && preg_match('~'.NUMERICAL_ALTERNATIVE_REGEX.'~', $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER], $numregs)) {
e51efd7e 1057 $wrapped->answer[] = $numregs[NUMERICAL_CORRECT_ANSWER];
516cf3eb 1058 if ($numregs[NUMERICAL_ABS_ERROR_MARGIN]) {
1059 $wrapped->tolerance[] =
e51efd7e 1060 $numregs[NUMERICAL_ABS_ERROR_MARGIN];
516cf3eb 1061 } else {
1062 $wrapped->tolerance[] = 0;
1063 }
1064 } else { // Tolerance can stay undefined for non numerical questions
1f8db780 1065 // Undo quoting done by the HTML editor.
9c61c44f 1066 $answer = html_entity_decode($altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER], ENT_QUOTES, 'UTF-8');
095b599a 1067 $answer = str_replace('\}', '}', $answer);
1068 $wrapped->answer[] = str_replace('\#', '#', $answer);
516cf3eb 1069 }
1070 $tmp = explode($altregs[0], $remainingalts, 2);
1071 $remainingalts = $tmp[1];
1072 }
1073
1074 $question->defaultgrade += $wrapped->defaultgrade;
1075 $question->options->questions[$positionkey] = clone($wrapped);
1076 $question->questiontext = implode("{#$positionkey}",
1077 explode($answerregs[0], $question->questiontext, 2));
1078 }
e51efd7e 1079 $question->questiontext = $question->questiontext;
516cf3eb 1080 return $question;
1081}