84769fd8 |
1 | <?php // $Id$ |
2 | // |
3 | /////////////////////////////////////////////////////////////// |
4 | // XML import/export |
5 | // |
6 | ////////////////////////////////////////////////////////////////////////// |
7 | // Based on default.php, included by ../import.php |
8 | |
84769fd8 |
9 | require_once( "$CFG->libdir/xmlize.php" ); |
10 | |
f5565b69 |
11 | class qformat_xml extends qformat_default { |
84769fd8 |
12 | |
13 | function provide_import() { |
14 | return true; |
15 | } |
16 | |
17 | function provide_export() { |
18 | return true; |
19 | } |
20 | |
21 | // IMPORT FUNCTIONS START HERE |
22 | |
6e557c08 |
23 | /** |
c81415c7 |
24 | * Translate human readable format name |
25 | * into internal Moodle code number |
6e557c08 |
26 | * @param string name format name from xml file |
27 | * @return int Moodle format code |
c81415c7 |
28 | */ |
84769fd8 |
29 | function trans_format( $name ) { |
84769fd8 |
30 | $name = trim($name); |
31 | |
32 | if ($name=='moodle_auto_format') { |
33 | $id = 0; |
34 | } |
35 | elseif ($name=='html') { |
36 | $id = 1; |
37 | } |
38 | elseif ($name=='plain_text') { |
39 | $id = 2; |
40 | } |
41 | elseif ($name=='wiki_like') { |
42 | $id = 3; |
43 | } |
44 | elseif ($name=='markdown') { |
45 | $id = 4; |
46 | } |
47 | else { |
48 | $id = 0; // or maybe warning required |
49 | } |
50 | return $id; |
51 | } |
52 | |
6e557c08 |
53 | /** |
c81415c7 |
54 | * Translate human readable single answer option |
55 | * to internal code number |
6e557c08 |
56 | * @param string name true/false |
57 | * @return int internal code number |
c81415c7 |
58 | */ |
84769fd8 |
59 | function trans_single( $name ) { |
2da44816 |
60 | $name = trim($name); |
61 | if ($name == "false" || !$name) { |
62 | return 0; |
63 | } else { |
64 | return 1; |
65 | } |
84769fd8 |
66 | } |
67 | |
6e557c08 |
68 | /** |
c81415c7 |
69 | * process text string from xml file |
6e557c08 |
70 | * @param array $text bit of xml tree after ['text'] |
71 | * @return string processed text |
c81415c7 |
72 | */ |
84769fd8 |
73 | function import_text( $text ) { |
84769fd8 |
74 | $data = $text[0]['#']; |
75 | $data = html_entity_decode( $data ); |
76 | return addslashes(trim( $data )); |
77 | } |
78 | |
307f045f |
79 | /** |
80 | * Process text from an element in the XML that may or not be there. |
81 | * @param string $subelement the name of the element which is either present or missing. |
82 | * @param array $question a bit of xml tree, this method looks for $question['#'][$subelement][0]['#']['text']. |
83 | * @return string If $subelement is present, return the content of the text tag inside it. |
84 | * Otherwise returns an empty string. |
85 | */ |
86 | function import_optional_text($subelement, $question) { |
87 | if (array_key_exists($subelement, $question['#'])) { |
88 | return $this->import_text($question['#'][$subelement][0]['#']['text']); |
89 | } else { |
90 | return ''; |
91 | } |
92 | } |
93 | |
6e557c08 |
94 | /** |
c81415c7 |
95 | * import parts of question common to all types |
6e557c08 |
96 | * @param array question question array from xml tree |
97 | * @return object question object |
c81415c7 |
98 | */ |
84769fd8 |
99 | function import_headers( $question ) { |
84769fd8 |
100 | // this routine initialises the question object |
5bed54e1 |
101 | $qo = $this->defaultquestion(); |
84769fd8 |
102 | $name = $this->import_text( $question['#']['name'][0]['#']['text'] ); |
103 | $qtext = $this->import_text( $question['#']['questiontext'][0]['#']['text'] ); |
104 | $qformat = $question['#']['questiontext'][0]['@']['format']; |
105 | $image = $question['#']['image'][0]['#']; |
d08e16b2 |
106 | if (!empty($question['#']['image_base64'][0]['#'])) { |
107 | $image_base64 = stripslashes( trim( $question['#']['image_base64'][0]['#'] ) ); |
108 | $image = $this->importimagefile( $image, $image_base64 ); |
109 | } |
a4514d91 |
110 | if (array_key_exists('generalfeedback', $question['#'])) { |
111 | $generalfeedback = $this->import_text( $question['#']['generalfeedback'][0]['#']['text'] ); |
1b8a7434 |
112 | } else { |
a4514d91 |
113 | $generalfeedback = ''; |
1b8a7434 |
114 | } |
c81415c7 |
115 | if (!empty($question['#']['defaultgrade'][0]['#'])) { |
116 | $qo->defaultgrade = $question['#']['defaultgrade'][0]['#']; |
117 | } |
5bed54e1 |
118 | |
84769fd8 |
119 | $penalty = $question['#']['penalty'][0]['#']; |
120 | |
84769fd8 |
121 | $qo->name = $name; |
122 | $qo->questiontext = $qtext; |
123 | $qo->questiontextformat = $this->trans_format( $qformat ); |
124 | $qo->image = ((!empty($image)) ? $image : ''); |
a4514d91 |
125 | $qo->generalfeedback = $generalfeedback; |
84769fd8 |
126 | $qo->penalty = $penalty; |
127 | |
128 | return $qo; |
129 | } |
130 | |
6e557c08 |
131 | /** |
c81415c7 |
132 | * import the common parts of a single answer |
6e557c08 |
133 | * @param array answer xml tree for single answer |
134 | * @return object answer object |
c81415c7 |
135 | */ |
84769fd8 |
136 | function import_answer( $answer ) { |
84769fd8 |
137 | $fraction = $answer['@']['fraction']; |
138 | $text = $this->import_text( $answer['#']['text']); |
139 | $feedback = $this->import_text( $answer['#']['feedback'][0]['#']['text'] ); |
140 | |
141 | $ans = null; |
142 | $ans->answer = $text; |
143 | $ans->fraction = $fraction / 100; |
144 | $ans->feedback = $feedback; |
145 | |
146 | return $ans; |
147 | } |
148 | |
6e557c08 |
149 | /** |
c81415c7 |
150 | * import multiple choice question |
6e557c08 |
151 | * @param array question question array from xml tree |
152 | * @return object question object |
c81415c7 |
153 | */ |
84769fd8 |
154 | function import_multichoice( $question ) { |
84769fd8 |
155 | // get common parts |
156 | $qo = $this->import_headers( $question ); |
157 | |
158 | // 'header' parts particular to multichoice |
159 | $qo->qtype = MULTICHOICE; |
160 | $single = $question['#']['single'][0]['#']; |
161 | $qo->single = $this->trans_single( $single ); |
307f045f |
162 | if (array_key_exists('shuffleanswers', $question['#'])) { |
163 | $shuffleanswers = $question['#']['shuffleanswers'][0]['#']; |
164 | } else { |
165 | $shuffleanswers = 'false'; |
166 | } |
2da44816 |
167 | $qo->shuffleanswers = $this->trans_single($shuffleanswers); |
307f045f |
168 | $qo->correctfeedback = $this->import_optional_text('correctfeedback', $question); |
169 | $qo->partiallycorrectfeedback = $this->import_optional_text('partiallycorrectfeedback', $question); |
170 | $qo->incorrectfeedback = $this->import_optional_text('incorrectfeedback', $question); |
171 | |
84769fd8 |
172 | // run through the answers |
173 | $answers = $question['#']['answer']; |
174 | $a_count = 0; |
175 | foreach ($answers as $answer) { |
176 | $ans = $this->import_answer( $answer ); |
177 | $qo->answer[$a_count] = $ans->answer; |
178 | $qo->fraction[$a_count] = $ans->fraction; |
179 | $qo->feedback[$a_count] = $ans->feedback; |
180 | ++$a_count; |
181 | } |
182 | |
183 | return $qo; |
184 | } |
185 | |
6e557c08 |
186 | /** |
c81415c7 |
187 | * import cloze type question |
6e557c08 |
188 | * @param array question question array from xml tree |
189 | * @return object question object |
c81415c7 |
190 | */ |
7b8bc256 |
191 | function import_multianswer( $questions ) { |
7b8bc256 |
192 | $qo = qtype_multianswer_extract_question($this->import_text( |
193 | $questions['#']['questiontext'][0]['#']['text'] )); |
194 | |
195 | // 'header' parts particular to multianswer |
196 | $qo->qtype = MULTIANSWER; |
197 | $qo->course = $this->course; |
198 | |
71ffbac2 |
199 | if (!empty($questions)) { |
7b8bc256 |
200 | $qo->name = $this->import_text( $questions['#']['name'][0]['#']['text'] ); |
201 | } |
202 | |
203 | return $qo; |
204 | } |
205 | |
6e557c08 |
206 | /** |
c81415c7 |
207 | * import true/false type question |
6e557c08 |
208 | * @param array question question array from xml tree |
209 | * @return object question object |
c81415c7 |
210 | */ |
84769fd8 |
211 | function import_truefalse( $question ) { |
84769fd8 |
212 | // get common parts |
213 | $qo = $this->import_headers( $question ); |
214 | |
215 | // 'header' parts particular to true/false |
216 | $qo->qtype = TRUEFALSE; |
217 | |
218 | // get answer info |
3246ed33 |
219 | // |
220 | // In the past, it used to be assumed that the two answers were in the file |
221 | // true first, then false. Howevever that was not always true. Now, we |
222 | // try to match on the answer text, but in old exports, this will be a localised |
223 | // string, so if we don't find true or false, we fall back to the old system. |
224 | $first = true; |
225 | $warning = false; |
226 | foreach ($question['#']['answer'] as $answer) { |
227 | $answertext = $this->import_text($answer['#']['text']); |
228 | $feedback = $this->import_text($answer['#']['feedback'][0]['#']['text']); |
229 | if ($answertext != 'true' && $answertext != 'false') { |
230 | $warning = true; |
231 | $answertext = $first ? 'true' : 'false'; // Old style file, assume order is true/false. |
232 | } |
233 | if ($answertext == 'true') { |
234 | $qo->answer = ($answer['@']['fraction'] == 100); |
7939a4a0 |
235 | $qo->correctanswer = $qo->answer; |
3246ed33 |
236 | $qo->feedbacktrue = $feedback; |
237 | } else { |
238 | $qo->answer = ($answer['@']['fraction'] != 100); |
7939a4a0 |
239 | $qo->correctanswer = $qo->answer; |
3246ed33 |
240 | $qo->feedbackfalse = $feedback; |
241 | } |
242 | $first = false; |
84769fd8 |
243 | } |
3246ed33 |
244 | |
245 | if ($warning) { |
246 | $a = new stdClass; |
247 | $a->questiontext = stripslashes($qo->questiontext); |
248 | $a->answer = get_string($qo->answer ? 'true' : 'false', 'quiz'); |
249 | notify(get_string('truefalseimporterror', 'quiz', $a)); |
84769fd8 |
250 | } |
251 | |
252 | return $qo; |
253 | } |
254 | |
6e557c08 |
255 | /** |
c81415c7 |
256 | * import short answer type question |
6e557c08 |
257 | * @param array question question array from xml tree |
258 | * @return object question object |
c81415c7 |
259 | */ |
84769fd8 |
260 | function import_shortanswer( $question ) { |
84769fd8 |
261 | // get common parts |
262 | $qo = $this->import_headers( $question ); |
263 | |
264 | // header parts particular to shortanswer |
265 | $qo->qtype = SHORTANSWER; |
266 | |
267 | // get usecase |
268 | $qo->usecase = $question['#']['usecase'][0]['#']; |
269 | |
270 | // run through the answers |
271 | $answers = $question['#']['answer']; |
272 | $a_count = 0; |
273 | foreach ($answers as $answer) { |
274 | $ans = $this->import_answer( $answer ); |
275 | $qo->answer[$a_count] = $ans->answer; |
276 | $qo->fraction[$a_count] = $ans->fraction; |
277 | $qo->feedback[$a_count] = $ans->feedback; |
278 | ++$a_count; |
279 | } |
280 | |
281 | return $qo; |
282 | } |
7b8bc256 |
283 | |
6e557c08 |
284 | /** |
c81415c7 |
285 | * import regexp type question |
6e557c08 |
286 | * @param array question question array from xml tree |
287 | * @return object question object |
c81415c7 |
288 | */ |
7b8bc256 |
289 | function import_regexp( $question ) { |
7b8bc256 |
290 | // get common parts |
291 | $qo = $this->import_headers( $question ); |
292 | |
293 | // header parts particular to shortanswer |
294 | $qo->qtype = regexp; |
295 | |
296 | // get usecase |
297 | $qo->usecase = $question['#']['usecase'][0]['#']; |
298 | |
299 | // run through the answers |
300 | $answers = $question['#']['answer']; |
301 | $a_count = 0; |
302 | foreach ($answers as $answer) { |
303 | $ans = $this->import_answer( $answer ); |
304 | $qo->answer[$a_count] = $ans->answer; |
305 | $qo->fraction[$a_count] = $ans->fraction; |
306 | $qo->feedback[$a_count] = $ans->feedback; |
307 | ++$a_count; |
308 | } |
84769fd8 |
309 | |
7b8bc256 |
310 | return $qo; |
311 | } |
312 | |
6e557c08 |
313 | /** |
c81415c7 |
314 | * import description type question |
6e557c08 |
315 | * @param array question question array from xml tree |
316 | * @return object question object |
c81415c7 |
317 | */ |
7b8bc256 |
318 | function import_description( $question ) { |
319 | // get common parts |
320 | $qo = $this->import_headers( $question ); |
321 | // header parts particular to shortanswer |
322 | $qo->qtype = DESCRIPTION; |
323 | return $qo; |
324 | } |
84769fd8 |
325 | |
6e557c08 |
326 | /** |
c81415c7 |
327 | * import numerical type question |
6e557c08 |
328 | * @param array question question array from xml tree |
329 | * @return object question object |
c81415c7 |
330 | */ |
331 | function import_numerical( $question ) { |
84769fd8 |
332 | // get common parts |
333 | $qo = $this->import_headers( $question ); |
334 | |
335 | // header parts particular to numerical |
336 | $qo->qtype = NUMERICAL; |
337 | |
338 | // get answers array |
339 | $answers = $question['#']['answer']; |
340 | $qo->answer = array(); |
341 | $qo->feedback = array(); |
342 | $qo->fraction = array(); |
343 | $qo->tolerance = array(); |
344 | foreach ($answers as $answer) { |
55894a42 |
345 | $answertext = trim($answer['#'][0]); |
346 | if ($answertext == '') { |
347 | $qo->answer[] = '*'; |
348 | } else { |
349 | $qo->answer[] = $answertext; |
350 | } |
a0d187bf |
351 | $qo->feedback[] = $this->import_text( $answer['#']['feedback'][0]['#']['text'] ); |
84769fd8 |
352 | $qo->fraction[] = $answer['#']['fraction'][0]['#']; |
353 | $qo->tolerance[] = $answer['#']['tolerance'][0]['#']; |
354 | } |
355 | |
356 | // get units array |
84769fd8 |
357 | $qo->unit = array(); |
a0d187bf |
358 | if (isset($question['#']['units'][0]['#']['unit'])) { |
359 | $units = $question['#']['units'][0]['#']['unit']; |
360 | $qo->multiplier = array(); |
361 | foreach ($units as $unit) { |
362 | $qo->multiplier[] = $unit['#']['multiplier'][0]['#']; |
363 | $qo->unit[] = $unit['#']['unit_name'][0]['#']; |
364 | } |
84769fd8 |
365 | } |
84769fd8 |
366 | return $qo; |
367 | } |
368 | |
6e557c08 |
369 | /** |
c81415c7 |
370 | * import matching type question |
6e557c08 |
371 | * @param array question question array from xml tree |
372 | * @return object question object |
c81415c7 |
373 | */ |
51bcdf28 |
374 | function import_matching( $question ) { |
51bcdf28 |
375 | // get common parts |
376 | $qo = $this->import_headers( $question ); |
377 | |
378 | // header parts particular to matching |
379 | $qo->qtype = MATCH; |
ee800653 |
380 | if (!empty($question['#']['shuffleanswers'])) { |
381 | $qo->shuffleanswers = $question['#']['shuffleanswers'][0]['#']; |
382 | } else { |
383 | $qo->shuffleanswers = false; |
384 | } |
51bcdf28 |
385 | |
386 | // get subquestions |
387 | $subquestions = $question['#']['subquestion']; |
388 | $qo->subquestions = array(); |
389 | $qo->subanswers = array(); |
390 | |
391 | // run through subquestions |
392 | foreach ($subquestions as $subquestion) { |
a0d187bf |
393 | $qtext = $this->import_text( $subquestion['#']['text'] ); |
394 | $atext = $this->import_text( $subquestion['#']['answer'][0]['#']['text'] ); |
51bcdf28 |
395 | $qo->subquestions[] = $qtext; |
396 | $qo->subanswers[] = $atext; |
397 | } |
51bcdf28 |
398 | return $qo; |
399 | } |
400 | |
6e557c08 |
401 | /** |
c81415c7 |
402 | * import essay type question |
6e557c08 |
403 | * @param array question question array from xml tree |
404 | * @return object question object |
c81415c7 |
405 | */ |
406 | function import_essay( $question ) { |
407 | // get common parts |
408 | $qo = $this->import_headers( $question ); |
409 | |
410 | // header parts particular to essay |
411 | $qo->qtype = ESSAY; |
412 | |
413 | // get feedback |
414 | $qo->feedback = $this->import_text( $question['#']['answer'][0]['#']['feedback'][0]['#']['text'] ); |
415 | |
416 | // get fraction |
417 | $qo->fraction = $question['#']['answer'][0]['#']['fraction'][0]['#']; |
418 | |
419 | return $qo; |
420 | } |
84769fd8 |
421 | |
ee259d0c |
422 | /** |
423 | * this is not a real question type. It's a dummy type used |
424 | * to specify the import category |
425 | * format is: |
426 | * <question type="category"> |
427 | * <category>tom/dick/harry</category> |
428 | * </question> |
429 | */ |
430 | function import_category( $question ) { |
431 | $qo->qtype = 'category'; |
432 | $qo->category = $question['#']['category'][0]['#']; |
433 | return $qo; |
434 | } |
435 | |
c81415c7 |
436 | /** |
437 | * parse the array of lines into an array of questions |
438 | * this *could* burn memory - but it won't happen that much |
439 | * so fingers crossed! |
6e557c08 |
440 | * @param array lines array of lines from the input file |
441 | * @return array (of objects) question objects |
c81415c7 |
442 | */ |
443 | function readquestions($lines) { |
84769fd8 |
444 | // we just need it as one big string |
445 | $text = implode($lines, " "); |
446 | unset( $lines ); |
447 | |
448 | // this converts xml to big nasty data structure |
449 | // the 0 means keep white space as it is (important for markdown format) |
450 | // print_r it if you want to see what it looks like! |
451 | $xml = xmlize( $text, 0 ); |
452 | |
453 | // set up array to hold all our questions |
454 | $questions = array(); |
455 | |
456 | // iterate through questions |
457 | foreach ($xml['quiz']['#']['question'] as $question) { |
458 | $question_type = $question['@']['type']; |
459 | $questiontype = get_string( 'questiontype','quiz',$question_type ); |
84769fd8 |
460 | |
461 | if ($question_type=='multichoice') { |
462 | $qo = $this->import_multichoice( $question ); |
463 | } |
464 | elseif ($question_type=='truefalse') { |
465 | $qo = $this->import_truefalse( $question ); |
466 | } |
467 | elseif ($question_type=='shortanswer') { |
468 | $qo = $this->import_shortanswer( $question ); |
469 | } |
7b8bc256 |
470 | //elseif ($question_type=='regexp') { |
471 | // $qo = $this->import_regexp( $question ); |
472 | //} |
84769fd8 |
473 | elseif ($question_type=='numerical') { |
474 | $qo = $this->import_numerical( $question ); |
475 | } |
7b8bc256 |
476 | elseif ($question_type=='description') { |
477 | $qo = $this->import_description( $question ); |
478 | } |
51bcdf28 |
479 | elseif ($question_type=='matching') { |
480 | $qo = $this->import_matching( $question ); |
481 | } |
7b8bc256 |
482 | elseif ($question_type=='cloze') { |
c81415c7 |
483 | $qo = $this->import_multianswer( $question ); |
484 | } |
485 | elseif ($question_type=='essay') { |
486 | $qo = $this->import_essay( $question ); |
7b8bc256 |
487 | } |
ee259d0c |
488 | elseif ($question_type=='category') { |
489 | $qo = $this->import_category( $question ); |
490 | } |
84769fd8 |
491 | else { |
ee800653 |
492 | $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type ); |
84769fd8 |
493 | echo "<p>$notsupported</p>"; |
494 | $qo = null; |
495 | } |
496 | |
497 | // stick the result in the $questions array |
498 | if ($qo) { |
499 | $questions[] = $qo; |
500 | } |
501 | } |
84769fd8 |
502 | return $questions; |
503 | } |
504 | |
505 | // EXPORT FUNCTIONS START HERE |
506 | |
84769fd8 |
507 | function export_file_extension() { |
508 | // override default type so extension is .xml |
509 | |
510 | return ".xml"; |
511 | } |
512 | |
c81415c7 |
513 | |
514 | /** |
515 | * Turn the internal question code into a human readable form |
516 | * (The code used to be numeric, but this remains as some of |
517 | * the names don't match the new internal format) |
6e557c08 |
518 | * @param mixed type_id Internal code |
519 | * @return string question type string |
c81415c7 |
520 | */ |
84769fd8 |
521 | function get_qtype( $type_id ) { |
84769fd8 |
522 | switch( $type_id ) { |
523 | case TRUEFALSE: |
524 | $name = 'truefalse'; |
525 | break; |
526 | case MULTICHOICE: |
527 | $name = 'multichoice'; |
528 | break; |
529 | case SHORTANSWER: |
530 | $name = 'shortanswer'; |
531 | break; |
7b8bc256 |
532 | //case regexp: |
533 | // $name = 'regexp'; |
534 | // break; |
84769fd8 |
535 | case NUMERICAL: |
536 | $name = 'numerical'; |
537 | break; |
538 | case MATCH: |
539 | $name = 'matching'; |
540 | break; |
541 | case DESCRIPTION: |
542 | $name = 'description'; |
543 | break; |
544 | case MULTIANSWER: |
545 | $name = 'cloze'; |
546 | break; |
c81415c7 |
547 | case ESSAY: |
548 | $name = 'essay'; |
549 | break; |
84769fd8 |
550 | default: |
551 | $name = 'unknown'; |
552 | } |
553 | return $name; |
554 | } |
555 | |
6e557c08 |
556 | /** |
c81415c7 |
557 | * Convert internal Moodle text format code into |
558 | * human readable form |
6e557c08 |
559 | * @param int id internal code |
560 | * @return string format text |
c81415c7 |
561 | */ |
84769fd8 |
562 | function get_format( $id ) { |
84769fd8 |
563 | switch( $id ) { |
564 | case 0: |
565 | $name = "moodle_auto_format"; |
566 | break; |
567 | case 1: |
568 | $name = "html"; |
569 | break; |
570 | case 2: |
571 | $name = "plain_text"; |
572 | break; |
573 | case 3: |
574 | $name = "wiki_like"; |
575 | break; |
576 | case 4: |
577 | $name = "markdown"; |
578 | break; |
579 | default: |
580 | $name = "unknown"; |
581 | } |
582 | return $name; |
583 | } |
584 | |
6e557c08 |
585 | /** |
c81415c7 |
586 | * Convert internal single question code into |
587 | * human readable form |
6e557c08 |
588 | * @param int id single question code |
589 | * @return string single question string |
c81415c7 |
590 | */ |
84769fd8 |
591 | function get_single( $id ) { |
84769fd8 |
592 | switch( $id ) { |
593 | case 0: |
594 | $name = "false"; |
595 | break; |
596 | case 1: |
597 | $name = "true"; |
598 | break; |
599 | default: |
600 | $name = "unknown"; |
601 | } |
602 | return $name; |
603 | } |
604 | |
6e557c08 |
605 | /** |
c81415c7 |
606 | * generates <text></text> tags, processing raw text therein |
6e557c08 |
607 | * @param int ilev the current indent level |
608 | * @param boolean short stick it on one line |
609 | * @return string formatted text |
c81415c7 |
610 | */ |
84769fd8 |
611 | function writetext( $raw, $ilev=0, $short=true) { |
84769fd8 |
612 | $indent = str_repeat( " ",$ilev ); |
613 | |
614 | // encode the text to 'disguise' HTML content |
615 | $raw = htmlspecialchars( $raw ); |
616 | |
617 | if ($short) { |
618 | $xml = "$indent<text>$raw</text>\n"; |
619 | } |
620 | else { |
621 | $xml = "$indent<text>\n$raw\n$indent</text>\n"; |
622 | } |
623 | |
624 | return $xml; |
625 | } |
626 | |
627 | function presave_process( $content ) { |
628 | // override method to allow us to add xml headers and footers |
629 | |
630 | $content = "<?xml version=\"1.0\"?>\n" . |
631 | "<quiz>\n" . |
632 | $content . "\n" . |
633 | "</quiz>"; |
634 | |
635 | return $content; |
636 | } |
637 | |
6e557c08 |
638 | /** |
c81415c7 |
639 | * Include an image encoded in base 64 |
6e557c08 |
640 | * @param string imagepath The location of the image file |
641 | * @return string xml code segment |
c81415c7 |
642 | */ |
d08e16b2 |
643 | function writeimage( $imagepath ) { |
d08e16b2 |
644 | global $CFG; |
645 | |
646 | if (empty($imagepath)) { |
647 | return ''; |
648 | } |
649 | |
650 | $courseid = $this->course->id; |
651 | if (!$binary = file_get_contents( "{$CFG->dataroot}/$courseid/$imagepath" )) { |
652 | return ''; |
653 | } |
654 | |
655 | $content = " <image_base64>\n".addslashes(base64_encode( $binary ))."\n". |
656 | "\n </image_base64>\n"; |
657 | return $content; |
658 | } |
659 | |
6e557c08 |
660 | /** |
c81415c7 |
661 | * Turns question into an xml segment |
6e557c08 |
662 | * @param array question question array |
663 | * @return string xml segment |
c81415c7 |
664 | */ |
84769fd8 |
665 | function writequestion( $question ) { |
84769fd8 |
666 | // initial string; |
667 | $expout = ""; |
668 | |
669 | // add comment |
670 | $expout .= "\n\n<!-- question: $question->id -->\n"; |
671 | |
672 | // add opening tag |
f1abd39f |
673 | // generates specific header for Cloze and category type question |
674 | if ($question->qtype == 'category') { |
675 | $expout .= " <question type=\"category\">\n"; |
676 | $expout .= " <category>\n"; |
677 | $expout .= " $question->category\n"; |
678 | $expout .= " </category>\n"; |
679 | $expout .= " </question>\n"; |
680 | return $expout; |
681 | } |
682 | elseif ($question->qtype != MULTIANSWER) { |
7b8bc256 |
683 | // for all question types except Close |
684 | $question_type = $this->get_qtype( $question->qtype ); |
685 | $name_text = $this->writetext( $question->name ); |
686 | $qtformat = $this->get_format($question->questiontextformat); |
687 | $question_text = $this->writetext( $question->questiontext ); |
a4514d91 |
688 | $generalfeedback = $this->writetext( $question->generalfeedback ); |
7b8bc256 |
689 | $expout .= " <question type=\"$question_type\">\n"; |
690 | $expout .= " <name>$name_text</name>\n"; |
691 | $expout .= " <questiontext format=\"$qtformat\">\n"; |
692 | $expout .= $question_text; |
693 | $expout .= " </questiontext>\n"; |
694 | $expout .= " <image>{$question->image}</image>\n"; |
695 | $expout .= $this->writeimage($question->image); |
a4514d91 |
696 | $expout .= " <generalfeedback>\n"; |
697 | $expout .= $generalfeedback; |
698 | $expout .= " </generalfeedback>\n"; |
c81415c7 |
699 | $expout .= " <defaultgrade>{$question->defaultgrade}</defaultgrade>\n"; |
7b8bc256 |
700 | $expout .= " <penalty>{$question->penalty}</penalty>\n"; |
701 | $expout .= " <hidden>{$question->hidden}</hidden>\n"; |
702 | } |
703 | else { |
704 | // for Cloze type only |
705 | $question_type = $this->get_qtype( $question->qtype ); |
706 | $name_text = $this->writetext( $question->name ); |
707 | $question_text = $this->writetext( $question->questiontext ); |
708 | $expout .= " <question type=\"$question_type\">\n"; |
709 | $expout .= " <name>$name_text</name>\n"; |
710 | $expout .= " <questiontext>\n"; |
711 | $expout .= $question_text; |
712 | $expout .= " </questiontext>\n"; |
713 | } |
714 | |
d08e16b2 |
715 | if (!empty($question->options->shuffleanswers)) { |
716 | $expout .= " <shuffleanswers>{$question->options->shuffleanswers}</shuffleanswers>\n"; |
717 | } |
718 | else { |
719 | $expout .= " <shuffleanswers>0</shuffleanswers>\n"; |
720 | } |
84769fd8 |
721 | |
722 | // output depends on question type |
723 | switch($question->qtype) { |
f1abd39f |
724 | case 'category': |
725 | // not a qtype really - dummy used for category switching |
726 | break; |
84769fd8 |
727 | case TRUEFALSE: |
36e2232e |
728 | foreach ($question->options->answers as $answer) { |
729 | $fraction_pc = round( $answer->fraction * 100 ); |
3246ed33 |
730 | if ($answer->id == $question->options->trueanswer) { |
731 | $answertext = 'true'; |
732 | } else { |
733 | $answertext = 'false'; |
734 | } |
36e2232e |
735 | $expout .= " <answer fraction=\"$fraction_pc\">\n"; |
3246ed33 |
736 | $expout .= $this->writetext($answertext, 3) . "\n"; |
36e2232e |
737 | $expout .= " <feedback>\n"; |
738 | $expout .= $this->writetext( $answer->feedback,4,false ); |
739 | $expout .= " </feedback>\n"; |
740 | $expout .= " </answer>\n"; |
741 | } |
84769fd8 |
742 | break; |
743 | case MULTICHOICE: |
744 | $expout .= " <single>".$this->get_single($question->options->single)."</single>\n"; |
307f045f |
745 | $expout .= " <shuffleanswers>".$this->get_single($question->options->shuffleanswers)."</shuffleanswers>\n"; |
746 | $expout .= " <correctfeedback>".$this->writetext($question->options->correctfeedback, 3)."</correctfeedback>\n"; |
747 | $expout .= " <partiallycorrectfeedback>".$this->writetext($question->options->partiallycorrectfeedback, 3)."</partiallycorrectfeedback>\n"; |
748 | $expout .= " <incorrectfeedback>".$this->writetext($question->options->incorrectfeedback, 3)."</incorrectfeedback>\n"; |
84769fd8 |
749 | foreach($question->options->answers as $answer) { |
750 | $percent = $answer->fraction * 100; |
751 | $expout .= " <answer fraction=\"$percent\">\n"; |
752 | $expout .= $this->writetext( $answer->answer,4,false ); |
753 | $expout .= " <feedback>\n"; |
6e557c08 |
754 | $expout .= $this->writetext( $answer->feedback,5,false ); |
84769fd8 |
755 | $expout .= " </feedback>\n"; |
756 | $expout .= " </answer>\n"; |
757 | } |
758 | break; |
759 | case SHORTANSWER: |
760 | $expout .= " <usecase>{$question->options->usecase}</usecase>\n "; |
761 | foreach($question->options->answers as $answer) { |
762 | $percent = 100 * $answer->fraction; |
763 | $expout .= " <answer fraction=\"$percent\">\n"; |
764 | $expout .= $this->writetext( $answer->answer,3,false ); |
765 | $expout .= " <feedback>\n"; |
766 | $expout .= $this->writetext( $answer->feedback,4,false ); |
767 | $expout .= " </feedback>\n"; |
768 | $expout .= " </answer>\n"; |
769 | } |
770 | break; |
7b8bc256 |
771 | //case regexp: |
772 | //$expout .= " <usecase>{$question->options->usecase}</usecase>\n "; |
773 | // foreach($question->options->answers as $answer) { |
774 | // $percent = 100 * $answer->fraction; |
775 | // $expout .= " <answer fraction=\"$percent\">\n"; |
776 | // $expout .= $this->writetext( $answer->answer,3,false ); |
777 | // $expout .= " <feedback>\n"; |
778 | // $expout .= $this->writetext( $answer->feedback,4,false ); |
779 | // $expout .= " </feedback>\n"; |
780 | // $expout .= " </answer>\n"; |
781 | // } |
782 | // break; |
84769fd8 |
783 | case NUMERICAL: |
784 | foreach ($question->options->answers as $answer) { |
785 | $tolerance = $answer->tolerance; |
786 | $expout .= "<answer>\n"; |
787 | $expout .= " {$answer->answer}\n"; |
788 | $expout .= " <tolerance>$tolerance</tolerance>\n"; |
789 | $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n"; |
790 | $expout .= " <fraction>{$answer->fraction}</fraction>\n"; |
791 | $expout .= "</answer>\n"; |
792 | } |
793 | |
794 | $units = $question->options->units; |
795 | if (count($units)) { |
796 | $expout .= "<units>\n"; |
797 | foreach ($units as $unit) { |
798 | $expout .= " <unit>\n"; |
799 | $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n"; |
800 | $expout .= " <unit_name>{$unit->unit}</unit_name>\n"; |
801 | $expout .= " </unit>\n"; |
802 | } |
803 | $expout .= "</units>\n"; |
804 | } |
805 | break; |
806 | case MATCH: |
807 | foreach($question->options->subquestions as $subquestion) { |
808 | $expout .= "<subquestion>\n"; |
809 | $expout .= $this->writetext( $subquestion->questiontext ); |
810 | $expout .= "<answer>".$this->writetext( $subquestion->answertext )."</answer>\n"; |
811 | $expout .= "</subquestion>\n"; |
812 | } |
813 | break; |
814 | case DESCRIPTION: |
c81415c7 |
815 | // nothing more to do for this type |
84769fd8 |
816 | break; |
817 | case MULTIANSWER: |
7b8bc256 |
818 | $a_count=1; |
819 | foreach($question->options->questions as $question) { |
820 | $thispattern = addslashes("{#".$a_count."}"); |
821 | $thisreplace = $question->questiontext; |
822 | $expout=ereg_replace($thispattern, $thisreplace, $expout ); |
823 | $a_count++; |
824 | } |
825 | break; |
c81415c7 |
826 | case ESSAY: |
827 | foreach ($question->options->answers as $answer) { |
828 | $expout .= "<answer>\n"; |
829 | $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n"; |
830 | $expout .= " <fraction>{$answer->fraction}</fraction>\n"; |
831 | $expout .= "</answer>\n"; |
832 | } |
833 | |
834 | break; |
84769fd8 |
835 | default: |
836 | $expout .= "<!-- Question type is unknown or not supported (Type=$question->qtype) -->\n"; |
837 | } |
838 | |
839 | // close the question tag |
840 | $expout .= "</question>\n"; |
841 | |
84769fd8 |
842 | return $expout; |
843 | } |
844 | } |
845 | |
846 | ?> |