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