curl now mandatory
[moodle.git] / question / type / match / questiontype.php
CommitLineData
aeb15530 1<?php
516cf3eb 2
3/////////////
4/// MATCH ///
5/////////////
6
7/// QUESTION TYPE CLASS //////////////////
1976496e 8/**
9 * @package questionbank
10 * @subpackage questiontypes
7375c542 11 */
af3830ee 12class question_match_qtype extends default_questiontype {
516cf3eb 13
14 function name() {
15 return 'match';
16 }
17
18 function get_question_options(&$question) {
f34488b2 19 global $DB;
20 $question->options = $DB->get_record('question_match', array('question' => $question->id));
21 $question->options->subquestions = $DB->get_records('question_match_sub', array('question' => $question->id), 'id ASC');
516cf3eb 22 return true;
23 }
24
25 function save_question_options($question) {
f34488b2 26 global $DB;
a58ffe3f 27 $result = new stdClass;
069a820a 28
f34488b2 29 if (!$oldsubquestions = $DB->get_records("question_match_sub", array("question" => $question->id), "id ASC")) {
516cf3eb 30 $oldsubquestions = array();
31 }
32
516cf3eb 33 // $subquestions will be an array with subquestion ids
34 $subquestions = array();
35
36 // Insert all the new question+answer pairs
37 foreach ($question->subquestions as $key => $questiontext) {
9ce0983b 38 $questiontext = trim($questiontext);
39 $answertext = trim($question->subanswers[$key]);
ebf83e2c 40 if ($questiontext != '' || $answertext != '') {
516cf3eb 41 if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it
42 $subquestion->questiontext = $questiontext;
43 $subquestion->answertext = $answertext;
bb4b6010 44 $DB->update_record("question_match_sub", $subquestion);
516cf3eb 45 } else {
a58ffe3f 46 $subquestion = new stdClass;
516cf3eb 47 // Determine a unique random code
48 $subquestion->code = rand(1,999999999);
f34488b2 49 while ($DB->record_exists('question_match_sub', array('code' => $subquestion->code, 'question' => $question->id))) {
516cf3eb 50 $subquestion->code = rand();
51 }
52 $subquestion->question = $question->id;
53 $subquestion->questiontext = $questiontext;
54 $subquestion->answertext = $answertext;
bb4b6010 55 $subquestion->id = $DB->insert_record("question_match_sub", $subquestion);
516cf3eb 56 }
57 $subquestions[] = $subquestion->id;
58 }
ebf83e2c 59 if ($questiontext != '' && $answertext == '') {
a58ffe3f 60 $result->notice = get_string('nomatchinganswer', 'quiz', $questiontext);
61 }
516cf3eb 62 }
63
64 // delete old subquestions records
65 if (!empty($oldsubquestions)) {
66 foreach($oldsubquestions as $os) {
f34488b2 67 $DB->delete_records('question_match_sub', array('id' => $os->id));
516cf3eb 68 }
69 }
70
f34488b2 71 if ($options = $DB->get_record("question_match", array("question" => $question->id))) {
516cf3eb 72 $options->subquestions = implode(",",$subquestions);
73 $options->shuffleanswers = $question->shuffleanswers;
bb4b6010 74 $DB->update_record("question_match", $options);
516cf3eb 75 } else {
76 unset($options);
77 $options->question = $question->id;
78 $options->subquestions = implode(",",$subquestions);
79 $options->shuffleanswers = $question->shuffleanswers;
bb4b6010 80 $DB->insert_record("question_match", $options);
516cf3eb 81 }
a58ffe3f 82
83 if (!empty($result->notice)) {
84 return $result;
85 }
86
87 if (count($subquestions) < 3) {
88 $result->notice = get_string('notenoughanswers', 'quiz', 3);
89 return $result;
90 }
91
516cf3eb 92 return true;
93 }
94
95 /**
96 * Deletes question from the question-type specific tables
97 *
98 * @return boolean Success/Failure
99 * @param integer $question->id
100 */
90c3f310 101 function delete_question($questionid) {
f34488b2 102 global $DB;
103 $DB->delete_records("question_match", array("question" => $questionid));
104 $DB->delete_records("question_match_sub", array("question" => $questionid));
516cf3eb 105 return true;
106 }
107
108 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
fef8f84e 109 global $DB, $OUTPUT;
f34488b2 110 if (!$state->options->subquestions = $DB->get_records('question_match_sub', array('question' => $question->id), 'id ASC')) {
fef8f84e 111 echo $OUTPUT->notification('Error: Missing subquestions!');
00c30069 112 return false;
516cf3eb 113 }
114
115 foreach ($state->options->subquestions as $key => $subquestion) {
116 // This seems rather over complicated, but it is useful for the
117 // randomsamatch questiontype, which can then inherit the print
118 // and grading functions. This way it is possible to define multiple
119 // answers per question, each with different marks and feedback.
120 $answer = new stdClass();
121 $answer->id = $subquestion->code;
122 $answer->answer = $subquestion->answertext;
123 $answer->fraction = 1.0;
f8394114 124 $state->options->subquestions[$key]->options->answers[$subquestion->code] = clone($answer);
516cf3eb 125
126 $state->responses[$key] = '';
127 }
128
129 // Shuffle the answers if required
130 if ($cmoptions->shuffleanswers and $question->options->shuffleanswers) {
131 $state->options->subquestions = swapshuffle_assoc($state->options->subquestions);
132 }
133
134 return true;
135 }
136
137 function restore_session_and_responses(&$question, &$state) {
fef8f84e 138 global $DB, $OUTPUT;
869309b8 139 static $subquestions = array();
140 if (!isset($subquestions[$question->id])){
141 if (!$subquestions[$question->id] = $DB->get_records('question_match_sub', array('question' => $question->id), 'id ASC')) {
fef8f84e 142 echo $OUTPUT->notification('Error: Missing subquestions!');
869309b8 143 return false;
144 }
145 }
f8394114 146
516cf3eb 147 // The serialized format for matching questions is a comma separated
148 // list of question answer pairs (e.g. 1-1,2-3,3-2), where the ids of
32a189d6 149 // both refer to the id in the table question_match_sub.
516cf3eb 150 $responses = explode(',', $state->responses['']);
f8394114 151 $responses = array_map(create_function('$val', 'return explode("-", $val);'), $responses);
516cf3eb 152
153 // Restore the previous responses and place the questions into the state options
154 $state->responses = array();
155 $state->options->subquestions = array();
156 foreach ($responses as $response) {
157 $state->responses[$response[0]] = $response[1];
869309b8 158 $state->options->subquestions[$response[0]] = clone($subquestions[$question->id][$response[0]]);
516cf3eb 159 }
160
161 foreach ($state->options->subquestions as $key => $subquestion) {
162 // This seems rather over complicated, but it is useful for the
163 // randomsamatch questiontype, which can then inherit the print
164 // and grading functions. This way it is possible to define multiple
165 // answers per question, each with different marks and feedback.
166 $answer = new stdClass();
167 $answer->id = $subquestion->code;
299d9fb8 168 $answer->answer = $subquestion->answertext;
516cf3eb 169 $answer->fraction = 1.0;
f8394114 170 $state->options->subquestions[$key]->options->answers[$subquestion->code] = clone($answer);
516cf3eb 171 }
172
173 return true;
174 }
175
176 function save_session_and_responses(&$question, &$state) {
f34488b2 177 global $DB;
87ee4968 178 $subquestions = &$state->options->subquestions;
179
180 // Prepare an array to help when disambiguating equal answers.
181 $answertexts = array();
182 foreach ($subquestions as $subquestion) {
183 $ans = reset($subquestion->options->answers);
184 $answertexts[$ans->id] = $ans->answer;
185 }
069a820a 186
516cf3eb 187 // Serialize responses
188 $responses = array();
87ee4968 189 foreach ($subquestions as $key => $subquestion) {
7d6af8ca 190 $response = 0;
bcda9611 191 if ($subquestion->questiontext !== '' && !is_null($subquestion->questiontext)) {
87ee4968 192 if ($state->responses[$key]) {
193 $response = $state->responses[$key];
194 if (!array_key_exists($response, $subquestion->options->answers)) {
bcda9611 195 // If student's answer did not match by id, but there may be
87ee4968 196 // two answers with the same text, but different ids,
197 // so we need to try matching the answer text.
198 $expected_answer = reset($subquestion->options->answers);
199 if ($answertexts[$response] == $expected_answer->answer) {
200 $response = $expected_answer->id;
201 $state->responses[$key] = $response;
202 }
203 }
87ee4968 204 }
0c24ee0f 205 }
7d6af8ca 206 $responses[] = $key.'-'.$response;
516cf3eb 207 }
208 $responses = implode(',', $responses);
209
210 // Set the legacy answer field
f34488b2 211 if (!$DB->set_field('question_states', 'answer', $responses, array('id' => $state->id))) {
516cf3eb 212 return false;
213 }
214 return true;
215 }
216
217 function get_correct_responses(&$question, &$state) {
218 $responses = array();
219 foreach ($state->options->subquestions as $sub) {
220 foreach ($sub->options->answers as $answer) {
bcda9611 221 if (1 == $answer->fraction && $sub->questiontext != '' && !is_null($sub->questiontext)) {
516cf3eb 222 $responses[$sub->id] = $answer->id;
223 }
224 }
225 }
226 return empty($responses) ? null : $responses;
227 }
228
229 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
44c64ad8 230 global $CFG, $OUTPUT;
516cf3eb 231 $subquestions = $state->options->subquestions;
232 $correctanswers = $this->get_correct_responses($question, $state);
233 $nameprefix = $question->name_prefix;
299d9fb8 234 $answers = array(); // Answer choices formatted ready for output.
235 $allanswers = array(); // This and the next used to detect identical answers
236 $answerids = array(); // and adjust ids.
516cf3eb 237 $responses = &$state->responses;
238
069a820a 239 // Prepare a list of answers, removing duplicates.
516cf3eb 240 foreach ($subquestions as $subquestion) {
241 foreach ($subquestion->options->answers as $ans) {
0b4ce29d 242 $allanswers[$ans->id] = $ans->answer;
87ee4968 243 if (!in_array($ans->answer, $answers)) {
299d9fb8 244 $answers[$ans->id] = strip_tags(format_string($ans->answer, false));
87ee4968 245 $answerids[$ans->answer] = $ans->id;
87ee4968 246 }
516cf3eb 247 }
248 }
069a820a 249
0b4ce29d 250 // Fix up the ids of any responses that point the the eliminated duplicates.
251 foreach ($responses as $subquestionid => $ignored) {
252 if ($responses[$subquestionid]) {
253 $responses[$subquestionid] = $answerids[$allanswers[$responses[$subquestionid]]];
254 }
255 }
256 foreach ($correctanswers as $subquestionid => $ignored) {
257 $correctanswers[$subquestionid] = $answerids[$allanswers[$correctanswers[$subquestionid]]];
258 }
516cf3eb 259
260 // Shuffle the answers
261 $answers = draw_rand_array($answers, count($answers));
262
37a12367 263 // Print formulation
1b8a7434 264 $questiontext = $this->format_text($question->questiontext,
265 $question->questiontextformat, $cmoptions);
9fc3100f 266 $image = get_question_image($question);
516cf3eb 267
069a820a 268 // Print the input controls
516cf3eb 269 foreach ($subquestions as $key => $subquestion) {
bcda9611 270 if ($subquestion->questiontext !== '' && !is_null($subquestion->questiontext)) {
069a820a 271 // Subquestion text:
a58ffe3f 272 $a = new stdClass;
1b8a7434 273 $a->text = $this->format_text($subquestion->questiontext,
274 $question->questiontextformat, $cmoptions);
2b087056 275
069a820a 276 // Drop-down list:
a58ffe3f 277 $menuname = $nameprefix.$subquestion->id;
278 $response = isset($state->responses[$subquestion->id])
279 ? $state->responses[$subquestion->id] : '0';
2b087056 280
281 $a->class = ' ';
282 $a->feedbackimg = ' ';
283
069a820a 284 if ($options->readonly and $options->correct_responses) {
285 if (isset($correctanswers[$subquestion->id])
2b087056 286 and ($correctanswers[$subquestion->id] == $response)) {
069a820a 287 $correctresponse = 1;
288 } else {
289 $correctresponse = 0;
290 }
2b087056 291
1b16ecd1 292 if ($options->feedback && $response) {
069a820a 293 $a->class = question_get_feedback_class($correctresponse);
294 $a->feedbackimg = question_get_feedback_image($correctresponse);
295 }
2b087056 296 }
aeb15530 297
c5dbaba9
PS
298 $attributes = array();
299 $attributes['disabled'] = $options->readonly ? 'disabled' : null;
300 $a->control = html_writer::select($answers, $menuname, $response, array(''=>'choosedots'), $attributes);
069a820a 301
a58ffe3f 302 // Neither the editing interface or the database allow to provide
303 // fedback for this question type.
304 // However (as was pointed out in bug bug 3294) the randomsamatch
305 // type which reuses this method can have feedback defined for
306 // the wrapped shortanswer questions.
307 //if ($options->feedback
308 // && !empty($subquestion->options->answers[$responses[$key]]->feedback)) {
309 // print_comment($subquestion->options->answers[$responses[$key]]->feedback);
310 //}
2b087056 311
a58ffe3f 312 $anss[] = $a;
516cf3eb 313 }
516cf3eb 314 }
aaae75b0 315 include("$CFG->dirroot/question/type/match/display.html");
516cf3eb 316 }
317
318 function grade_responses(&$question, &$state, $cmoptions) {
319 $subquestions = &$state->options->subquestions;
320 $responses = &$state->responses;
321
87ee4968 322 // Prepare an array to help when disambiguating equal answers.
323 $answertexts = array();
324 foreach ($subquestions as $subquestion) {
325 $ans = reset($subquestion->options->answers);
326 $answertexts[$ans->id] = $ans->answer;
327 }
069a820a 328
87ee4968 329 // Add up the grades from each subquestion.
516cf3eb 330 $sumgrade = 0;
a58ffe3f 331 $totalgrade = 0;
516cf3eb 332 foreach ($subquestions as $key => $sub) {
bcda9611 333 if ($sub->questiontext !== '' && !is_null($sub->questiontext)) {
a58ffe3f 334 $totalgrade += 1;
87ee4968 335 $response = $responses[$key];
336 if ($response && !array_key_exists($response, $sub->options->answers)) {
337 // If studen's answer did not match by id, but there may be
338 // two answers with the same text, but different ids,
339 // so we need to try matching the answer text.
340 $expected_answer = reset($sub->options->answers);
341 if ($answertexts[$response] == $expected_answer->answer) {
342 $response = $expected_answer->id;
343 }
344 }
345 if (array_key_exists($response, $sub->options->answers)) {
346 $sumgrade += $sub->options->answers[$response]->fraction;
a58ffe3f 347 }
516cf3eb 348 }
349 }
350
a58ffe3f 351 $state->raw_grade = $sumgrade/$totalgrade;
516cf3eb 352 if (empty($state->raw_grade)) {
353 $state->raw_grade = 0;
354 }
355
356 // Make sure we don't assign negative or too high marks
357 $state->raw_grade = min(max((float) $state->raw_grade,
358 0.0), 1.0) * $question->maxgrade;
359 $state->penalty = $question->penalty * $question->maxgrade;
360
f30bbcaf 361 // mark the state as graded
362 $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
363
516cf3eb 364 return true;
365 }
366
b25486fc 367 function compare_responses($question, $state, $teststate) {
2c89cfb5 368 foreach ($state->responses as $i=>$sr) {
369 if (empty($teststate->responses[$i])) {
370 if (!empty($state->responses[$i])) {
371 return false;
372 }
373 } else if ($state->responses[$i] != $teststate->responses[$i]) {
b25486fc 374 return false;
375 }
376 }
377 return true;
378 }
379
516cf3eb 380 // ULPGC ecastro for stats report
381 function get_all_responses($question, $state) {
974383f9 382 $answers = array();
516cf3eb 383 if (is_array($question->options->subquestions)) {
645d7610 384 foreach ($question->options->subquestions as $aid => $answer) {
bcda9611 385 if ($answer->questiontext !== '' && !is_null($answer->questiontext)) {
645d7610 386 $r = new stdClass;
387 $r->answer = $answer->questiontext . ": " . $answer->answertext;
388 $r->credit = 1;
389 $answers[$aid] = $r;
390 }
516cf3eb 391 }
516cf3eb 392 }
a58ffe3f 393 $result = new stdClass;
516cf3eb 394 $result->id = $question->id;
395 $result->responses = $answers;
396 return $result;
397 }
398
869309b8 399 function get_possible_responses(&$question) {
400 $answers = array();
401 if (is_array($question->options->subquestions)) {
402 foreach ($question->options->subquestions as $subqid => $answer) {
403 if ($answer->questiontext) {
404 $r = new stdClass;
405 $r->answer = $answer->questiontext . ": " . $answer->answertext;
406 $r->credit = 1;
407 $answers[$subqid] = array($answer->id =>$r);
408 }
409 }
410 }
411 return $answers;
412 }
aeb15530 413
516cf3eb 414 // ULPGC ecastro
415 function get_actual_response($question, $state) {
6d6b454a
TH
416 $subquestions = &$state->options->subquestions;
417 $responses = &$state->responses;
418 $results=array();
419 foreach ($responses as $ind => $code) {
420 foreach ($subquestions as $key => $sub) {
421 if (isset($sub->options->answers[$code])) {
422 $results[$ind] = $subquestions[$ind]->questiontext . ": " . $sub->options->answers[$code]->answer;
423 }
424 }
425 }
426 return $results;
427 }
aeb15530 428
869309b8 429 function get_actual_response_details($question, $state) {
430 $responses = $this->get_actual_response($question, $state);
431 $teacherresponses = $this->get_possible_responses($question, $state);
432 //only one response
433 $responsedetails =array();
434 foreach ($responses as $tsubqid => $response){
435 $responsedetail = new object();
436 $responsedetail->subqid = $tsubqid;
437 $responsedetail->response = $response;
438 foreach ($teacherresponses[$tsubqid] as $aid => $tresponse){
439 if ($tresponse->answer == $response){
440 $responsedetail->aid = $aid;
441 break;
442 }
443 }
444 if (isset($responsedetail->aid)){
445 $responsedetail->credit = $teacherresponses[$tsubqid][$aid]->credit;
446 } else {
447 $responsedetail->aid = 0;
448 $responsedetail->credit = 0;
449 }
450 $responsedetails[] = $responsedetail;
451 }
452 return $responsedetails;
453 }
2280e147 454
a3b70fa9 455
6f51ed72 456 /**
457 * @param object $question
455c3efa 458 * @return mixed either a integer score out of 1 that the average random
459 * guess by a student might give or an empty string which means will not
460 * calculate.
6f51ed72 461 */
462 function get_random_guess_score($question) {
463 return 1 / count($question->options->subquestions);
464 }
069a820a 465
c5d94c41 466/// BACKUP FUNCTIONS ////////////////////////////
467
468 /*
469 * Backup the data in the question
470 *
471 * This is used in question/backuplib.php
472 */
473 function backup($bf,$preferences,$question,$level=6) {
f34488b2 474 global $DB;
c5d94c41 475 $status = true;
476
7028ab33 477 // Output the shuffleanswers setting.
478 $matchoptions = $DB->get_record('question_match', array('question' => $question));
479 if ($matchoptions) {
480 $status = fwrite ($bf,start_tag("MATCHOPTIONS",6,true));
481 fwrite ($bf,full_tag("SHUFFLEANSWERS",7,false,$matchoptions->shuffleanswers));
482 $status = fwrite ($bf,end_tag("MATCHOPTIONS",6,true));
483 }
484
f34488b2 485 $matchs = $DB->get_records('question_match_sub', array('question' => $question), 'id ASC');
c5d94c41 486 //If there are matchs
487 if ($matchs) {
7028ab33 488 //Print match contents
c5d94c41 489 $status = fwrite ($bf,start_tag("MATCHS",6,true));
490 //Iterate over each match
491 foreach ($matchs as $match) {
492 $status = fwrite ($bf,start_tag("MATCH",7,true));
493 //Print match contents
494 fwrite ($bf,full_tag("ID",8,false,$match->id));
495 fwrite ($bf,full_tag("CODE",8,false,$match->code));
496 fwrite ($bf,full_tag("QUESTIONTEXT",8,false,$match->questiontext));
497 fwrite ($bf,full_tag("ANSWERTEXT",8,false,$match->answertext));
498 $status = fwrite ($bf,end_tag("MATCH",7,true));
499 }
500 $status = fwrite ($bf,end_tag("MATCHS",6,true));
501 }
502 return $status;
503 }
516cf3eb 504
315559d3 505/// RESTORE FUNCTIONS /////////////////
506
507 /*
508 * Restores the data in the question
509 *
510 * This is used in question/restorelib.php
511 */
512 function restore($old_question_id,$new_question_id,$info,$restore) {
9db7dab2 513 global $DB;
315559d3 514 $status = true;
515
516 //Get the matchs array
517 $matchs = $info['#']['MATCHS']['0']['#']['MATCH'];
518
519 //We have to build the subquestions field (a list of match_sub id)
520 $subquestions_field = "";
521 $in_first = true;
522
523 //Iterate over matchs
524 for($i = 0; $i < sizeof($matchs); $i++) {
525 $mat_info = $matchs[$i];
526
527 //We'll need this later!!
528 $oldid = backup_todb($mat_info['#']['ID']['0']['#']);
529
530 //Now, build the question_match_SUB record structure
a58ffe3f 531 $match_sub = new stdClass;
315559d3 532 $match_sub->question = $new_question_id;
1f4d6e9a 533 $match_sub->code = isset($mat_info['#']['CODE']['0']['#'])?backup_todb($mat_info['#']['CODE']['0']['#']):'';
315559d3 534 if (!$match_sub->code) {
535 $match_sub->code = $oldid;
536 }
537 $match_sub->questiontext = backup_todb($mat_info['#']['QUESTIONTEXT']['0']['#']);
538 $match_sub->answertext = backup_todb($mat_info['#']['ANSWERTEXT']['0']['#']);
539
540 //The structure is equal to the db, so insert the question_match_sub
9db7dab2 541 $newid = $DB->insert_record ("question_match_sub",$match_sub);
315559d3 542
543 //Do some output
544 if (($i+1) % 50 == 0) {
545 if (!defined('RESTORE_SILENTLY')) {
546 echo ".";
547 if (($i+1) % 1000 == 0) {
548 echo "<br />";
549 }
550 }
551 backup_flush(300);
552 }
553
554 if ($newid) {
555 //We have the newid, update backup_ids
556 backup_putid($restore->backup_unique_code,"question_match_sub",$oldid,
557 $newid);
558 //We have a new match_sub, append it to subquestions_field
559 if ($in_first) {
560 $subquestions_field .= $newid;
561 $in_first = false;
562 } else {
563 $subquestions_field .= ",".$newid;
564 }
565 } else {
566 $status = false;
567 }
568 }
569
570 //We have created every match_sub, now create the match
87ee4968 571 $match = new stdClass;
315559d3 572 $match->question = $new_question_id;
573 $match->subquestions = $subquestions_field;
574
7028ab33 575 // Get the shuffleanswers option, if it is there.
576 if (!empty($info['#']['MATCHOPTIONS']['0']['#']['SHUFFLEANSWERS'])) {
577 $match->shuffleanswers = backup_todb($info['#']['MATCHOPTIONS']['0']['#']['SHUFFLEANSWERS']['0']['#']);
578 } else {
579 $match->shuffleanswers = 1;
580 }
581
315559d3 582 //The structure is equal to the db, so insert the question_match_sub
9db7dab2 583 $newid = $DB->insert_record ("question_match",$match);
315559d3 584
585 if (!$newid) {
586 $status = false;
587 }
588
589 return $status;
590 }
591
592 function restore_map($old_question_id,$new_question_id,$info,$restore) {
f34488b2 593 global $DB;
315559d3 594 $status = true;
595
596 //Get the matchs array
597 $matchs = $info['#']['MATCHS']['0']['#']['MATCH'];
598
599 //We have to build the subquestions field (a list of match_sub id)
600 $subquestions_field = "";
601 $in_first = true;
602
603 //Iterate over matchs
604 for($i = 0; $i < sizeof($matchs); $i++) {
605 $mat_info = $matchs[$i];
606
607 //We'll need this later!!
608 $oldid = backup_todb($mat_info['#']['ID']['0']['#']);
609
610 //Now, build the question_match_SUB record structure
611 $match_sub->question = $new_question_id;
612 $match_sub->questiontext = backup_todb($mat_info['#']['QUESTIONTEXT']['0']['#']);
613 $match_sub->answertext = backup_todb($mat_info['#']['ANSWERTEXT']['0']['#']);
614
615 //If we are in this method is because the question exists in DB, so its
616 //match_sub must exist too.
617 //Now, we are going to look for that match_sub in DB and to create the
618 //mappings in backup_ids to use them later where restoring states (user level).
619
620 //Get the match_sub from DB (by question, questiontext and answertext)
f34488b2 621 $db_match_sub = $DB->get_record ("question_match_sub",array("question"=>$new_question_id,
622 "questiontext"=>$match_sub->questiontext,
623 "answertext"=>$match_sub->answertext));
315559d3 624 //Do some output
625 if (($i+1) % 50 == 0) {
626 if (!defined('RESTORE_SILENTLY')) {
627 echo ".";
628 if (($i+1) % 1000 == 0) {
629 echo "<br />";
630 }
631 }
632 backup_flush(300);
633 }
634
635 //We have the database match_sub, so update backup_ids
636 if ($db_match_sub) {
637 //We have the newid, update backup_ids
638 backup_putid($restore->backup_unique_code,"question_match_sub",$oldid,
639 $db_match_sub->id);
640 } else {
641 $status = false;
642 }
643 }
644
645 return $status;
646 }
647
648 function restore_recode_answer($state, $restore) {
649
650 //The answer is a comma separated list of hypen separated math_subs (for question and answer)
651 $answer_field = "";
652 $in_first = true;
653 $tok = strtok($state->answer,",");
654 while ($tok) {
655 //Extract the match_sub for the question and the answer
656 $exploded = explode("-",$tok);
657 $match_question_id = $exploded[0];
3ff8a187 658 $match_answer_id = $exploded[1];
315559d3 659 //Get the match_sub from backup_ids (for the question)
660 if (!$match_que = backup_getid($restore->backup_unique_code,"question_match_sub",$match_question_id)) {
3ff8a187 661 echo 'Could not recode question in question_match_sub '.$match_question_id.'<br />';
7028ab33 662 } else {
3ff8a187 663 if ($in_first) {
3ff8a187 664 $in_first = false;
665 } else {
7028ab33 666 $answer_field .= ',';
3ff8a187 667 }
7028ab33 668 $answer_field .= $match_que->new_id.'-'.$match_answer_id;
315559d3 669 }
670 //check for next
671 $tok = strtok(",");
672 }
673 return $answer_field;
674 }
675
c85607f0 676 /**
677 * Decode links in question type specific tables.
678 * @return bool success or failure.
271e6dec 679 */
c85607f0 680 function decode_content_links_caller($questionids, $restore, &$i) {
44e1b7d7 681 global $DB;
682
e3b2eb60 683 $status = true;
684
c85607f0 685 // Decode links in the question_match_sub table.
44e1b7d7 686 if ($subquestions = $DB->get_records_list('question_match_sub', 'question', $questionids, '', 'id, questiontext')) {
c85607f0 687
688 foreach ($subquestions as $subquestion) {
689 $questiontext = restore_decode_content_links_worker($subquestion->questiontext, $restore);
690 if ($questiontext != $subquestion->questiontext) {
294ce987 691 $subquestion->questiontext = $questiontext;
bb4b6010 692 $DB->update_record('question_match_sub', $subquestion);
c85607f0 693 }
694
695 // Do some output.
696 if (++$i % 5 == 0 && !defined('RESTORE_SILENTLY')) {
697 echo ".";
698 if ($i % 100 == 0) {
699 echo "<br />";
700 }
701 backup_flush(300);
702 }
703 }
704 }
e3b2eb60 705
706 return $status;
c85607f0 707 }
271e6dec 708
709 function find_file_links($question, $courseid){
710 // find links in the question_match_sub table.
711 $urls = array();
6f9ce926 712 if (isset($question->options->subquestions)){
713 foreach ($question->options->subquestions as $subquestion) {
714 $urls += question_find_file_links_from_html($subquestion->questiontext, $courseid);
715 }
271e6dec 716
6f9ce926 717 //set all the values of the array to the question object
718 if ($urls){
719 $urls = array_combine(array_keys($urls), array_fill(0, count($urls), array($question->id)));
720 }
271e6dec 721 }
722 $urls = array_merge_recursive($urls, parent::find_file_links($question, $courseid));
6f9ce926 723
271e6dec 724 return $urls;
725 }
726
727 function replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination){
f34488b2 728 global $DB;
271e6dec 729 parent::replace_file_links($question, $fromcourseid, $tocourseid, $url, $destination);
730 // replace links in the question_match_sub table.
731 if (isset($question->options->subquestions)){
732 foreach ($question->options->subquestions as $subquestion) {
733 $subquestionchanged = false;
734 $subquestion->questiontext = question_replace_file_links_in_html($subquestion->questiontext, $fromcourseid, $tocourseid, $url, $destination, $subquestionchanged);
735 if ($subquestionchanged){//need to update rec in db
bb4b6010 736 $DB->update_record('question_match_sub', $subquestion);
271e6dec 737 }
738 }
739 }
740 }
b9bd6da4 741
742 /**
743 * Runs all the code required to set up and save an essay question for testing purposes.
744 * Alternate DB table prefix may be used to facilitate data deletion.
745 */
746 function generate_test($name, $courseid = null) {
747 global $DB;
748 list($form, $question) = parent::generate_test($name, $courseid);
749 $form->shuffleanswers = 1;
750 $form->noanswers = 3;
751 $form->subquestions = array('cat', 'dog', 'cow');
752 $form->subanswers = array('feline', 'canine', 'bovine');
753
754 if ($courseid) {
755 $course = $DB->get_record('course', array('id' => $courseid));
756 }
757
758 return $this->save_question($question, $form, $course);
759 }
516cf3eb 760}
761//// END OF CLASS ////
762
763//////////////////////////////////////////////////////////////////////////
764//// INITIATION - Without this line the question type is not in use... ///
765//////////////////////////////////////////////////////////////////////////
a2156789 766question_register_questiontype(new question_match_qtype());
aeb15530 767