Commit | Line | Data |
---|---|---|
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 | 12 | class 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 | 766 | question_register_questiontype(new question_match_qtype()); |
aeb15530 | 767 |