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