82dbd5d8f274cf8627cfeaa99f120a5e0c471f7f
[moodle.git] / question / format / xml / format.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Moodle XML question importer.
20  *
21  * @package qformat
22  * @subpackage qformat_xml
23  * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
24  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
28 /**
29  * Importer for Moodle XML question format.
30  *
31  * See http://docs.moodle.org/en/Moodle_XML_format for a description of the format.
32  *
33  * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
34  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 require_once($CFG->libdir . '/xmlize.php');
38 class qformat_xml extends qformat_default {
40     function provide_import() {
41         return true;
42     }
44     function provide_export() {
45         return true;
46     }
48     function mime_type() {
49         return 'application/xml';
50     }
52     // IMPORT FUNCTIONS START HERE
54     /**
55      * Translate human readable format name
56      * into internal Moodle code number
57      * @param string name format name from xml file
58      * @return int Moodle format code
59      */
60     function trans_format($name) {
61         $name = trim($name);
63         if ($name == 'moodle_auto_format') {
64             $id = 0;
65         } else if ($name == 'html') {
66             $id = 1;
67         } else if ($name == 'plain_text') {
68             $id = 2;
69         } else if ($name == 'wiki_like') {
70             $id = 3;
71         } else if ($name == 'markdown') {
72             $id = 4;
73         } else {
74             $id = 0; // or maybe warning required
75         }
76         return $id;
77     }
79     /**
80      * Translate human readable single answer option
81      * to internal code number
82      * @param string name true/false
83      * @return int internal code number
84      */
85     function trans_single( $name ) {
86         $name = trim($name);
87         if ($name == "false" || !$name) {
88             return 0;
89         } else {
90             return 1;
91         }
92     }
94     /**
95      * process text string from xml file
96      * @param array $text bit of xml tree after ['text']
97      * @return string processed text
98      */
99     function import_text( $text ) {
100         // quick sanity check
101         if (empty($text)) {
102             return '';
103         }
104         $data = $text[0]['#'];
105         return trim($data);
106     }
108     /**
109      * return the value of a node, given a path to the node
110      * if it doesn't exist return the default value
111      * @param array xml data to read
112      * @param array path path to node expressed as array
113      * @param mixed default
114      * @param bool istext process as text
115      * @param string error if set value must exist, return false and issue message if not
116      * @return mixed value
117      */
118     function getpath($xml, $path, $default, $istext=false, $error='') {
119         foreach ($path as $index) {
120             if (!isset($xml[$index])) {
121                 if (!empty($error)) {
122                     $this->error( $error );
123                     return false;
124                 } else {
125                     return $default;
126                 }
127             }
128             else $xml = $xml[$index];
129         }
130         if ($istext) {
131             if (!is_string($xml)) {
132                 $this->error( get_string('invalidxml','qformat_xml') );
133             }
134             $xml = trim($xml);
135         }
137         return $xml;
138     }
141     /**
142      * import parts of question common to all types
143      * @param $question array question question array from xml tree
144      * @return object question object
145      */
146     function import_headers($question) {
147         // get some error strings
148         $error_noname = get_string('xmlimportnoname','quiz');
149         $error_noquestion = get_string('xmlimportnoquestion','quiz');
151         // this routine initialises the question object
152         $qo = $this->defaultquestion();
154         // question name
155         $qo->name = $this->getpath( $question, array('#','name',0,'#','text',0,'#'), '', true, $error_noname );
156         $qo->questiontext       = $this->getpath($question, array('#','questiontext',0,'#','text',0,'#'), '', true );
157         $qo->questiontextformat = $this->trans_format(
158                 $this->getpath($question, array('#','questiontext',0,'@','format'), 'moodle_auto_format'));
159         $qo->image = $this->getpath($question, array('#','image',0,'#'), $qo->image );
160         $image_base64 = $this->getpath( $question, array('#','image_base64','0','#'),'' );
161         if (!empty($image_base64)) {
162             $qo->image = $this->importimagefile($qo->image, $image_base64);
163         }
165         $qo->questiontextfiles = array();
167         // restore files in questiontext
168         $files = $this->getpath($question, array('#', 'questiontext', 0, '#','file'), array(), false);
169         foreach ($files as $file) {
170             $data = new stdclass;
171             $data->content = $file['#'];
172             $data->encoding = $file['@']['encoding'];
173             $data->name = $file['@']['name'];
174             $qo->questiontextfiles[] = $data;
175         }
177         // restore files in generalfeedback
178         $qo->generalfeedback = $this->getpath($question, array('#','generalfeedback',0,'#','text',0,'#'), $qo->generalfeedback, true);
179         $qo->generalfeedbackfiles = array();
180         $qo->generalfeedbackformat = $this->trans_format(
181                 $this->getpath($question, array('#', 'generalfeedback', 0, '@', 'format'), 'moodle_auto_format'));
182         $files = $this->getpath($question, array('#', 'generalfeedback', 0, '#', 'file'), array(), false);
183         foreach ($files as $file) {
184             $data = new stdclass;
185             $data->content = $file['#'];
186             $data->encoding = $file['@']['encoding'];
187             $data->name = $file['@']['name'];
188             $qo->generalfeedbackfiles[] = $data;
189         }
191         $qo->defaultgrade = $this->getpath( $question, array('#','defaultgrade',0,'#'), $qo->defaultgrade );
192         $qo->penalty = $this->getpath( $question, array('#','penalty',0,'#'), $qo->penalty );
194         return $qo;
195     }
197     /**
198      * import the common parts of a single answer
199      * @param array answer xml tree for single answer
200      * @return object answer object
201      */
202     function import_answer($answer) {
203         $fraction = $this->getpath($answer, array('@', 'fraction'), 0);
204         $answertext = $this->getpath($answer, array('#', 'text', 0, '#'), '', true);
205         $answerformat = $this->trans_format($this->getpath($answer,
206                 array('#', 'text', 0, '#'), 'moodle_auto_format'));
207         $answerfiles = array();
208         $files = $this->getpath($answer, array('#', 'answer', 0, '#', 'file'), array());
209         foreach ($files as $file) {
210             $data = new stdclass;
211             $data->content = $file['#'];
212             $data->name = $file['@']['name'];
213             $data->encoding = $file['@']['encoding'];
214             $answerfiles[] = $data;
215         }
217         $feedbacktext = $this->getpath($answer, array('#', 'feedback', 0, '#', 'text', 0, '#'), '', true);
218         $feedbackformat = $this->trans_format($this->getpath($answer,
219                 array('#', 'feedback', 0, '@', 'format'), 'moodle_auto_format'));
220         $feedbackfiles = array();
221         $files = $this->getpath($answer, array('#', 'feedback', 0, '#', 'file'), array());
222         foreach ($files as $file) {
223             $data = new stdclass;
224             $data->content = $file['#'];
225             $data->name = $file['@']['name'];
226             $data->encoding = $file['@']['encoding'];
227             $feedbackfiles[] = $data;
228         }
230         $ans = new stdclass;
232         $ans->answer = array();
233         $ans->answer['text']   = $answertext;
234         $ans->answer['format'] = $answerformat;
235         $ans->answer['files']  = $answerfiles;
237         $ans->feedback = array();
238         $ans->feedback['text']   = $feedbacktext;
239         $ans->feedback['format'] = $feedbackformat;
240         $ans->feedback['files']  = $feedbackfiles;
242         $ans->fraction = $fraction / 100;
243         return $ans;
244     }
246     /**
247      * import multiple choice question
248      * @param array question question array from xml tree
249      * @return object question object
250      */
251     function import_multichoice($question) {
252         // get common parts
253         $qo = $this->import_headers($question);
255         // 'header' parts particular to multichoice
256         $qo->qtype = MULTICHOICE;
257         $single = $this->getpath( $question, array('#','single',0,'#'), 'true' );
258         $qo->single = $this->trans_single( $single );
259         $shuffleanswers = $this->getpath( $question, array('#','shuffleanswers',0,'#'), 'false' );
260         $qo->answernumbering = $this->getpath( $question, array('#','answernumbering',0,'#'), 'abc' );
261         $qo->shuffleanswers = $this->trans_single($shuffleanswers);
263         $qo->correctfeedback = array();
264         $qo->correctfeedback['text'] = $this->getpath($question, array('#', 'correctfeedback', 0, '#', 'text', 0, '#'), '', true);
265         $qo->correctfeedback['format'] = $this->trans_format(
266                 $this->getpath($question, array('#', 'correctfeedback', 0, '@', 'format'), 'moodle_auto_format'));
267         $qo->correctfeedback['files'] = array();
268         // restore files in correctfeedback
269         $files = $this->getpath($question, array('#', 'correctfeedback', 0, '#','file'), array(), false);
270         foreach ($files as $file) {
271             $data = new stdclass;
272             $data->content = $file['#'];
273             $data->encoding = $file['@']['encoding'];
274             $data->name = $file['@']['name'];
275             $qo->correctfeedback['files'][] = $data;
276         }
278         $qo->partiallycorrectfeedback = array();
279         $qo->partiallycorrectfeedback['text'] = $this->getpath( $question, array('#','partiallycorrectfeedback',0,'#','text',0,'#'), '', true );
280         $qo->partiallycorrectfeedback['format'] = $this->trans_format(
281                 $this->getpath($question, array('#', 'partiallycorrectfeedback', 0, '@', 'format'), 'moodle_auto_format'));
282         $qo->partiallycorrectfeedback['files'] = array();
283         // restore files in partiallycorrectfeedback
284         $files = $this->getpath($question, array('#', 'partiallycorrectfeedback', 0, '#','file'), array(), false);
285         foreach ($files as $file) {
286             $data = new stdclass;
287             $data->content = $file['#'];
288             $data->encoding = $file['@']['encoding'];
289             $data->name = $file['@']['name'];
290             $qo->partiallycorrectfeedback['files'][] = $data;
291         }
293         $qo->incorrectfeedback = array();
294         $qo->incorrectfeedback['text'] = $this->getpath( $question, array('#','incorrectfeedback',0,'#','text',0,'#'), '', true );
295         $qo->incorrectfeedback['format'] = $this->trans_format(
296                 $this->getpath($question, array('#', 'incorrectfeedback', 0, '@', 'format'), 'moodle_auto_format'));
297         $qo->incorrectfeedback['files'] = array();
298         // restore files in incorrectfeedback
299         $files = $this->getpath($question, array('#', 'incorrectfeedback', 0, '#','file'), array(), false);
300         foreach ($files as $file) {
301             $data = new stdclass;
302             $data->content = $file['#'];
303             $data->encoding = $file['@']['encoding'];
304             $data->name = $file['@']['name'];
305             $qo->incorrectfeedback['files'][] = $data;
306         }
308         // There was a time on the 1.8 branch when it could output an empty answernumbering tag, so fix up any found.
309         if (empty($qo->answernumbering)) {
310             $qo->answernumbering = 'abc';
311         }
313         // run through the answers
314         $answers = $question['#']['answer'];
315         $a_count = 0;
316         foreach ($answers as $answer) {
317             $ans = $this->import_answer($answer);
318             $qo->answer[$a_count] = $ans->answer;
319             $qo->fraction[$a_count] = $ans->fraction;
320             $qo->feedback[$a_count] = $ans->feedback;
321             ++$a_count;
322         }
324         return $qo;
325     }
327     /**
328      * import cloze type question
329      * @param array question question array from xml tree
330      * @return object question object
331      */
332     function import_multianswer( $questions ) {
333         $questiontext = $questions['#']['questiontext'][0]['#']['text'];
334         $qo = qtype_multianswer_extract_question($this->import_text($questiontext));
336         // 'header' parts particular to multianswer
337         $qo->qtype = MULTIANSWER;
338         $qo->course = $this->course;
339         $qo->generalfeedback = $this->getpath( $questions, array('#','generalfeedback',0,'#','text',0,'#'), '', true );
341         if (!empty($questions)) {
342             $qo->name = $this->import_text( $questions['#']['name'][0]['#']['text'] );
343         }
345         return $qo;
346     }
348     /**
349      * import true/false type question
350      * @param array question question array from xml tree
351      * @return object question object
352      */
353     function import_truefalse( $question ) {
354         // get common parts
355         global $OUTPUT;
356         $qo = $this->import_headers( $question );
358         // 'header' parts particular to true/false
359         $qo->qtype = TRUEFALSE;
361         // get answer info
362         //
363         // In the past, it used to be assumed that the two answers were in the file
364         // true first, then false. Howevever that was not always true. Now, we
365         // try to match on the answer text, but in old exports, this will be a localised
366         // string, so if we don't find true or false, we fall back to the old system.
367         $first = true;
368         $warning = false;
369         foreach ($question['#']['answer'] as $answer) {
370             $answertext = $this->getpath( $answer, array('#','text',0,'#'), '', true);
371             $feedback = $this->getpath($answer, array('#','feedback',0,'#','text',0,'#'), '', true);
372             $feedbackformat = $this->getpath($answer, array('#','feedback',0, '@', 'format'), 'moodle_auto_format');
373             $feedbackfiles = $this->getpath($answer, array('#', 'feedback', 0, '#', 'file'), array());
374             $files = array();
375             foreach ($feedbackfiles as $file) {
376                 $data = new stdclass;
377                 $data->content = $file['#'];
378                 $data->encoding = $file['@']['encoding'];
379                 $data->name = $file['@']['name'];
380                 $files[] = $data;
381             }
382             if ($answertext != 'true' && $answertext != 'false') {
383                 $warning = true;
384                 $answertext = $first ? 'true' : 'false'; // Old style file, assume order is true/false.
385             }
386             if ($answertext == 'true') {
387                 $qo->answer = ($answer['@']['fraction'] == 100);
388                 $qo->correctanswer = $qo->answer;
389                 $qo->feedbacktrue = array();
390                 $qo->feedbacktrue['text'] = $feedback;
391                 $qo->feedbacktrue['format'] = $this->trans_format($feedbackformat);
392                 $qo->feedbacktrue['itemid'] = null;
393                 $qo->feedbacktruefiles = $files;
394             } else {
395                 $qo->answer = ($answer['@']['fraction'] != 100);
396                 $qo->correctanswer = $qo->answer;
397                 $qo->feedbackfalse = array();
398                 $qo->feedbackfalse['text'] = $feedback;
399                 $qo->feedbackfalse['format'] = $this->trans_format($feedbackformat);
400                 $qo->feedbackfalse['itemid'] = null;
401                 $qo->feedbackfalsefiles = $files;
402             }
403             $first = false;
404         }
406         if ($warning) {
407             $a = new stdClass;
408             $a->questiontext = $qo->questiontext;
409             $a->answer = get_string($qo->answer ? 'true' : 'false', 'quiz');
410             echo $OUTPUT->notification(get_string('truefalseimporterror', 'quiz', $a));
411         }
412         return $qo;
413     }
415     /**
416      * import short answer type question
417      * @param array question question array from xml tree
418      * @return object question object
419      */
420     function import_shortanswer( $question ) {
421         // get common parts
422         $qo = $this->import_headers( $question );
424         // header parts particular to shortanswer
425         $qo->qtype = SHORTANSWER;
427         // get usecase
428         $qo->usecase = $this->getpath($question, array('#','usecase',0,'#'), $qo->usecase );
430         // run through the answers
431         $answers = $question['#']['answer'];
432         $a_count = 0;
433         foreach ($answers as $answer) {
434             $ans = $this->import_answer( $answer );
435             $qo->answer[$a_count] = $ans->answer;
436             $qo->fraction[$a_count] = $ans->fraction;
437             $qo->feedback[$a_count] = $ans->feedback;
438             ++$a_count;
439         }
441         return $qo;
442     }
444     /**
445      * import description type question
446      * @param array question question array from xml tree
447      * @return object question object
448      */
449     function import_description( $question ) {
450         // get common parts
451         $qo = $this->import_headers( $question );
452         // header parts particular to shortanswer
453         $qo->qtype = DESCRIPTION;
454         $qo->defaultgrade = 0;
455         $qo->length = 0;
456         return $qo;
457     }
459     /**
460      * import numerical type question
461      * @param array question question array from xml tree
462      * @return object question object
463      */
464     function import_numerical($question) {
465         // get common parts
466         $qo = $this->import_headers($question);
468         // header parts particular to numerical
469         $qo->qtype = NUMERICAL;
471         // get answers array
472         $answers = $question['#']['answer'];
473         $qo->answer = array();
474         $qo->feedback = array();
475         $qo->fraction = array();
476         $qo->tolerance = array();
477         foreach ($answers as $answer) {
478             // answer outside of <text> is deprecated
479             $obj = $this->import_answer($answer);
480             $qo->answer[] = $obj->answer;
481             if (empty($qo->answer)) {
482                 $qo->answer = '*';
483             }
484             $qo->feedback[]  = $obj->feedback;
485             $qo->tolerance[] = $this->getpath($answer, array('#', 'tolerance', 0, '#'), 0);
487             // fraction as a tag is deprecated
488             $fraction = $this->getpath($answer, array('@', 'fraction'), 0) / 100;
489             $qo->fraction[] = $this->getpath($answer, array('#', 'fraction', 0, '#'), $fraction); // deprecated
490         }
492         // get units array
493         $qo->unit = array();
494         $units = $this->getpath( $question, array('#','units',0,'#','unit'), array() );
495         if (!empty($units)) {
496             $qo->multiplier = array();
497             foreach ($units as $unit) {
498                 $qo->multiplier[] = $this->getpath( $unit, array('#','multiplier',0,'#'), 1 );
499                 $qo->unit[] = $this->getpath( $unit, array('#','unit_name',0,'#'), '', true );
500             }
501         }
502         $qo->unitgradingtype = $this->getpath( $question, array('#','unitgradingtype',0,'#'), 0 );
503         $qo->unitpenalty = $this->getpath( $question, array('#','unitpenalty',0,'#'), 0 );
504         $qo->showunits = $this->getpath( $question, array('#','showunits',0,'#'), 0 );
505         $qo->unitsleft = $this->getpath( $question, array('#','unitsleft',0,'#'), 0 );
506         $instructions = $this->getpath($question, array('#', 'instructions'), array());
507         if (!empty($instructions)) {
508             $qo->instructions = array();
509             $qo->instructions['text'] = $this->getpath($instructions,
510                     array('0', '#', 'text', '0', '#'), '', true);
511             $qo->instructions['format'] = $this->trans_format($this->getpath($instructions,
512                     array('0', '@', 'format'), 'moodle_auto_format'));
513             $files = $this->getpath($instructions, array('0', '#', 'file'), array());
514             $qo->instructionsfiles = array();
515             foreach ($files as $file) {
516                 $data = new stdclass;
517                 $data->content = $file['#'];
518                 $data->encoding = $file['@']['encoding'];
519                 $data->name = $file['@']['name'];
520                 $qo->instructionsfiles[] = $data;
521             }
522         }
523         return $qo;
524     }
526     /**
527      * import matching type question
528      * @param array question question array from xml tree
529      * @return object question object
530      */
531     function import_matching($question) {
532         // get common parts
533         $qo = $this->import_headers($question);
535         // header parts particular to matching
536         $qo->qtype = MATCH;
537         $qo->shuffleanswers = $this->getpath($question, array('#', 'shuffleanswers', 0, '#'), 1);
539         // get subquestions
540         $subquestions = $question['#']['subquestion'];
541         $qo->subquestions = array();
542         $qo->subanswers = array();
544         // run through subquestions
545         foreach ($subquestions as $subquestion) {
546             $question = array();
547             $question['text'] = $this->getpath($subquestion, array('#', 'text', 0, '#'), '', true);
548             $question['format'] = $this->trans_format(
549                     $this->getpath($subquestion, array('@', 'format'), 'moodle_auto_format'));
550             $question['files'] = array();
552             $files = $this->getpath($subquestion, array('#', 'file'), array());
553             foreach ($files as $file) {
554                 $data = new stdclass();
555                 $data->content = $file['#'];
556                 $data->encoding = $file['@']['encoding'];
557                 $data->name = $file['@']['name'];
558                 $question['files'][] = $data;
559             }
560             $qo->subquestions[] = $question;
561             $answers = $this->getpath($subquestion, array('#', 'answer'), array());
562             $qo->subanswers[] = $this->getpath($subquestion, array('#','answer',0,'#','text',0,'#'), '', true);
563         }
564         return $qo;
565     }
567     /**
568      * import  essay type question
569      * @param array question question array from xml tree
570      * @return object question object
571      */
572     function import_essay( $question ) {
573         // get common parts
574         $qo = $this->import_headers( $question );
576         // header parts particular to essay
577         $qo->qtype = ESSAY;
579         $answers = $this->getpath($question, array('#', 'answer'), null);
580         if ($answers) {
581             $answer = array_pop($answers);
582             $answer = $this->import_answer($answer);
583             // get feedback
584             $qo->feedback = $answer->feedback;
585         } else {
586             $qo->feedback = array('text' => '', 'format' => FORMAT_MOODLE, 'files' => array());
587         }
589         // get fraction - <fraction> tag is deprecated
590         $qo->fraction = $this->getpath($question, array('@','fraction'), 0 ) / 100;
591         $q0->fraction = $this->getpath($question, array('#','fraction',0,'#'), $qo->fraction );
593         return $qo;
594     }
596     function import_calculated($question,$qtype) {
597     // import calculated question
599         // get common parts
600         $qo = $this->import_headers( $question );
602         // header parts particular to calculated
603         $qo->qtype = CALCULATED ;//CALCULATED;
604         $qo->synchronize = $this->getpath( $question, array( '#','synchronize',0,'#' ), 0 );
605         $single = $this->getpath( $question, array('#','single',0,'#'), 'true' );
606         $qo->single = $this->trans_single( $single );
607         $shuffleanswers = $this->getpath( $question, array('#','shuffleanswers',0,'#'), 'false' );
608         $qo->answernumbering = $this->getpath( $question, array('#','answernumbering',0,'#'), 'abc' );
609         $qo->shuffleanswers = $this->trans_single($shuffleanswers);
611         $qo->correctfeedback = array();
612         $qo->correctfeedback['text'] = $this->getpath($question, array('#','correctfeedback',0,'#','text',0,'#'), '', true );
613         $qo->correctfeedback['format'] = $this->trans_format($this->getpath(
614                 $question, array('#', 'correctfeedback', 0, '@', 'formath'), 'moodle_auto_format'));
615         $qo->correctfeedback['files'] = array();
617         $files = $this->getpath($question, array('#', 'correctfeedback', '0', '#', 'file'), array());
618         foreach ($files as $file) {
619             $data = new stdclass();
620             $data->content = $file['#'];
621             $data->name = $file['@']['name'];
622             $data->encoding = $file['@']['encoding'];
623             $qo->correctfeedback['files'][] = $data;
624         }
626         $qo->partiallycorrectfeedback = array();
627         $qo->partiallycorrectfeedback['text'] = $this->getpath( $question, array('#','partiallycorrectfeedback',0,'#','text',0,'#'), '', true );
628         $qo->partiallycorrectfeedback['format'] = $this->trans_format(
629                 $this->getpath($question, array('#','partiallycorrectfeedback', 0, '@','format'), 'moodle_auto_format'));
630         $qo->partiallycorrectfeedback['files'] = array();
632         $files = $this->getpath($question, array('#', 'partiallycorrectfeedback', '0', '#', 'file'), array());
633         foreach ($files as $file) {
634             $data = new stdclass();
635             $data->content = $file['#'];
636             $data->name = $file['@']['name'];
637             $data->encoding = $file['@']['encoding'];
638             $qo->partiallycorrectfeedback['files'][] = $data;
639         }
641         $qo->incorrectfeedback = array();
642         $qo->incorrectfeedback['text'] = $this->getpath( $question, array('#','incorrectfeedback',0,'#','text',0,'#'), '', true );
643         $qo->incorrectfeedback['format'] = $this->trans_format($this->getpath(
644                 $question, array('#','incorrectfeedback', 0, '@','format'), 'moodle_auto_format'));
645         $qo->incorrectfeedback['files'] = array();
647         $files = $this->getpath($question, array('#', 'incorrectfeedback', '0', '#', 'file'), array());
648         foreach ($files as $file) {
649             $data = new stdclass();
650             $data->content = $file['#'];
651             $data->name = $file['@']['name'];
652             $data->encoding = $file['@']['encoding'];
653             $qo->incorrectfeedback['files'][] = $data;
654         }
656         $qo->unitgradingtype = $this->getpath($question, array('#','unitgradingtype',0,'#'), 0 );
657         $qo->unitpenalty = $this->getpath($question, array('#','unitpenalty',0,'#'), 0 );
658         $qo->showunits = $this->getpath($question, array('#','showunits',0,'#'), 0 );
659         $qo->unitsleft = $this->getpath($question, array('#','unitsleft',0,'#'), 0 );
660 //        $qo->instructions = $this->getpath( $question, array('#','instructions',0,'#','text',0,'#'), '', true );
661         if (!empty($instructions)) {
662             $qo->instructions = array();
663             $qo->instructions['text'] = $this->getpath($instructions,
664                     array('0', '#', 'text', '0', '#'), '', true);
665             $qo->instructions['format'] = $this->trans_format($this->getpath($instructions,
666                     array('0', '@', 'format'), 'moodle_auto_format'));
667             $files = $this->getpath($instructions,
668                     array('0', '#', 'file'), array());
669             $qo->instructionsfiles = array();
670             foreach ($files as $file) {
671                 $data = new stdclass;
672                 $data->content = $file['#'];
673                 $data->encoding = $file['@']['encoding'];
674                 $data->name = $file['@']['name'];
675                 $qo->instructionsfiles[] = $data;
676             }
677         }
679         $files = $this->getpath($question, array('#', 'instructions', 0, '#', 'file', 0, '@'), '', false);
681         // get answers array
682         // echo "<pre> question";print_r($question);echo "</pre>";
683         $answers = $question['#']['answer'];
684         $qo->answers = array();
685         $qo->feedback = array();
686         $qo->fraction = array();
687         $qo->tolerance = array();
688         $qo->tolerancetype = array();
689         $qo->correctanswerformat = array();
690         $qo->correctanswerlength = array();
691         $qo->feedback = array();
692         foreach ($answers as $answer) {
693             $ans = $this->import_answer($answer);
694             // answer outside of <text> is deprecated
695             if (empty($ans->answer['text'])) {
696                 $ans->answer['text'] = '*';
697             }
698             $qo->answers[] = $ans->answer;
699             $qo->feedback[] = $ans->feedback;
700             $qo->tolerance[] = $answer['#']['tolerance'][0]['#'];
701             // fraction as a tag is deprecated
702             if (!empty($answer['#']['fraction'][0]['#'])) {
703                 $qo->fraction[] = $answer['#']['fraction'][0]['#'];
704             } else {
705                 $qo->fraction[] = $answer['@']['fraction'] / 100;
706             }
707             $qo->tolerancetype[] = $answer['#']['tolerancetype'][0]['#'];
708             $qo->correctanswerformat[] = $answer['#']['correctanswerformat'][0]['#'];
709             $qo->correctanswerlength[] = $answer['#']['correctanswerlength'][0]['#'];
710         }
711         // get units array
712         $qo->unit = array();
713         if (isset($question['#']['units'][0]['#']['unit'])) {
714             $units = $question['#']['units'][0]['#']['unit'];
715             $qo->multiplier = array();
716             foreach ($units as $unit) {
717                 $qo->multiplier[] = $unit['#']['multiplier'][0]['#'];
718                 $qo->unit[] = $unit['#']['unit_name'][0]['#'];
719             }
720         }
721         $instructions = $this->getpath($question, array('#', 'instructions'), array());
722         if (!empty($instructions)) {
723             $qo->instructions = array();
724             $qo->instructions['text'] = $this->getpath($instructions,
725                     array('0', '#', 'text', '0', '#'), '', true);
726             $qo->instructions['format'] = $this->trans_format($this->getpath($instructions,
727                     array('0', '@', 'format'), 'moodle_auto_format'));
728             $files = $this->getpath($instructions,
729                     array('0', '#', 'file'), array());
730             $qo->instructionsfiles = array();
731             $files = $instructions[0]['#']['file'];
732             foreach ($files as $file) {
733                 $data = new stdclass;
734                 $data->content = $file['#'];
735                 $data->encoding = $file['@']['encoding'];
736                 $data->name = $file['@']['name'];
737                 $qo->instructionsfiles[] = $data;
738             }
739         }
740         $datasets = $question['#']['dataset_definitions'][0]['#']['dataset_definition'];
741         $qo->dataset = array();
742         $qo->datasetindex= 0 ;
743         foreach ($datasets as $dataset) {
744             $qo->datasetindex++;
745             $qo->dataset[$qo->datasetindex] = new stdClass();
746             $qo->dataset[$qo->datasetindex]->status = $this->import_text( $dataset['#']['status'][0]['#']['text']);
747             $qo->dataset[$qo->datasetindex]->name = $this->import_text( $dataset['#']['name'][0]['#']['text']);
748             $qo->dataset[$qo->datasetindex]->type =  $dataset['#']['type'][0]['#'];
749             $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']);
750             $qo->dataset[$qo->datasetindex]->max = $this->import_text( $dataset['#']['maximum'][0]['#']['text']);
751             $qo->dataset[$qo->datasetindex]->min = $this->import_text( $dataset['#']['minimum'][0]['#']['text']);
752             $qo->dataset[$qo->datasetindex]->length = $this->import_text( $dataset['#']['decimals'][0]['#']['text']);
753             $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']);
754             $qo->dataset[$qo->datasetindex]->itemcount = $dataset['#']['itemcount'][0]['#'];
755             $qo->dataset[$qo->datasetindex]->datasetitem = array();
756             $qo->dataset[$qo->datasetindex]->itemindex = 0;
757             $qo->dataset[$qo->datasetindex]->number_of_items=$dataset['#']['number_of_items'][0]['#'];
758             $datasetitems = $dataset['#']['dataset_items'][0]['#']['dataset_item'];
759             foreach ($datasetitems as $datasetitem) {
760                 $qo->dataset[$qo->datasetindex]->itemindex++;
761                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex] = new stdClass();
762                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->itemnumber =  $datasetitem['#']['number'][0]['#']; //[0]['#']['number'][0]['#'] ; // [0]['numberitems'] ;//['#']['number'][0]['#'];// $datasetitems['#']['number'][0]['#'];
763                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->value = $datasetitem['#']['value'][0]['#'] ;//$datasetitem['#']['value'][0]['#'];
764             }
765         }
767         // echo "<pre>loaded qo";print_r($qo);echo "</pre>";
768         return $qo;
769     }
771     /**
772      * this is not a real question type. It's a dummy type used
773      * to specify the import category
774      * format is:
775      * <question type="category">
776      *     <category>tom/dick/harry</category>
777      * </question>
778      */
779     function import_category( $question ) {
780         $qo = new stdClass;
781         $qo->qtype = 'category';
782         $qo->category = $this->import_text($question['#']['category'][0]['#']['text']);
783         return $qo;
784     }
786     /**
787      * parse the array of lines into an array of questions
788      * this *could* burn memory - but it won't happen that much
789      * so fingers crossed!
790      * @param array lines array of lines from the input file
791      * @return array (of objects) question objects
792      */
793     function readquestions($lines) {
794         // we just need it as one big string
795         $text = implode($lines, " ");
796         unset($lines);
798         // this converts xml to big nasty data structure
799         // the 0 means keep white space as it is (important for markdown format)
800         // print_r it if you want to see what it looks like!
801         $xml = xmlize($text, 0);
803         // set up array to hold all our questions
804         $questions = array();
806         // iterate through questions
807         foreach ($xml['quiz']['#']['question'] as $question) {
808             $question_type = $question['@']['type'];
809             $questiontype = get_string( 'questiontype','quiz',$question_type );
811             if ($question_type=='multichoice') {
812                 $qo = $this->import_multichoice( $question );
813             }
814             elseif ($question_type=='truefalse') {
815                 $qo = $this->import_truefalse( $question );
816             }
817             elseif ($question_type=='shortanswer') {
818                 $qo = $this->import_shortanswer( $question );
819             }
820             elseif ($question_type=='numerical') {
821                 $qo = $this->import_numerical( $question );
822             }
823             elseif ($question_type=='description') {
824                 $qo = $this->import_description( $question );
825             }
826             elseif ($question_type=='matching') {
827                 $qo = $this->import_matching( $question );
828             }
829             elseif ($question_type=='cloze') {
830                 $qo = $this->import_multianswer( $question );
831             }
832             elseif ($question_type=='essay') {
833                 $qo = $this->import_essay( $question );
834             }
835             elseif ($question_type=='calculated') {
836                 $qo = $this->import_calculated( $question,CALCULATED  );
837             }
838             elseif ($question_type=='calculatedsimple') {
839                 $qo = $this->import_calculated( $question,CALCULATEDMULTI  );
840                 $qo->qtype = CALCULATEDSIMPLE ;
841             }
842             elseif ($question_type=='calculatedmulti') {
843                 $qo = $this->import_calculated( $question,CALCULATEDMULTI );
844                 $qo->qtype = CALCULATEDMULTI ;
845             }
846             elseif ($question_type=='category') {
847                 $qo = $this->import_category( $question );
848             }
849             else {
850                 // try for plugin support
851                 // no default question, as the plugin can call
852                 // import_headers() itself if it wants to
853                 if (!$qo = $this->try_importing_using_qtypes( $question, null, null, $question_type)) {
854                     $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type );
855                     $this->error( $notsupported );
856                     $qo = null;
857                 }
858             }
860             // stick the result in the $questions array
861             if ($qo) {
862                 $questions[] = $qo;
863             }
864         }
865         return $questions;
866     }
868     // EXPORT FUNCTIONS START HERE
870     function export_file_extension() {
871         return '.xml';
872     }
874     /**
875      * Turn the internal question code into a human readable form
876      * (The code used to be numeric, but this remains as some of
877      * the names don't match the new internal format)
878      * @param mixed type_id Internal code
879      * @return string question type string
880      */
881     function get_qtype( $type_id ) {
882         switch( $type_id ) {
883         case TRUEFALSE:
884             $name = 'truefalse';
885             break;
886         case MULTICHOICE:
887             $name = 'multichoice';
888             break;
889         case SHORTANSWER:
890             $name = 'shortanswer';
891             break;
892         case NUMERICAL:
893             $name = 'numerical';
894             break;
895         case MATCH:
896             $name = 'matching';
897             break;
898         case DESCRIPTION:
899             $name = 'description';
900             break;
901         case MULTIANSWER:
902             $name = 'cloze';
903             break;
904         case ESSAY:
905             $name = 'essay';
906             break;
907         case CALCULATED:
908             $name = 'calculated';
909             break;
910         case CALCULATEDSIMPLE:
911             $name = 'calculatedsimple';
912             break;
913         case CALCULATEDMULTI:
914             $name = 'calculatedmulti';
915             break;
916         default:
917             $name = false;
918         }
919         return $name;
920     }
922     /**
923      * Convert internal Moodle text format code into
924      * human readable form
925      * @param int id internal code
926      * @return string format text
927      */
928     function get_format( $id ) {
929         switch( $id ) {
930         case 0:
931             $name = "moodle_auto_format";
932             break;
933         case 1:
934             $name = "html";
935             break;
936         case 2:
937             $name = "plain_text";
938             break;
939         case 3:
940             $name = "wiki_like";
941             break;
942         case 4:
943             $name = "markdown";
944             break;
945         default:
946             $name = "unknown";
947         }
948         return $name;
949     }
951     /**
952      * Convert internal single question code into
953      * human readable form
954      * @param int id single question code
955      * @return string single question string
956      */
957     function get_single( $id ) {
958         switch( $id ) {
959         case 0:
960             $name = "false";
961             break;
962         case 1:
963             $name = "true";
964             break;
965         default:
966             $name = "unknown";
967         }
968         return $name;
969     }
971     /**
972      * generates <text></text> tags, processing raw text therein
973      * @param int ilev the current indent level
974      * @param boolean short stick it on one line
975      * @return string formatted text
976      */
977     function writetext($raw, $ilev=0, $short=true) {
978         $indent = str_repeat( "  ",$ilev );
980         // if required add CDATA tags
981         if (!empty($raw) and (htmlspecialchars($raw)!=$raw)) {
982             $raw = "<![CDATA[$raw]]>";
983         }
985         if ($short) {
986             $xml = "$indent<text>$raw</text>\n";
987         }
988         else {
989             $xml = "$indent<text>\n$raw\n$indent</text>\n";
990         }
992         return $xml;
993     }
995     function presave_process( $content ) {
996     // override method to allow us to add xml headers and footers
998         // add the xml headers and footers
999         $content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
1000                        "<quiz>\n" .
1001                        $content . "\n" .
1002                        "</quiz>";
1003         return $content;
1004     }
1006     /**
1007      * Turns question into an xml segment
1008      * @param object question object
1009      * @param int context id
1010      * @return string xml segment
1011      */
1012     function writequestion($question) {
1013         global $CFG, $QTYPES, $OUTPUT;
1015         $fs = get_file_storage();
1016         $contextid = $question->contextid;
1017         // initial string;
1018         $expout = "";
1020         // add comment
1021         $expout .= "\n\n<!-- question: $question->id  -->\n";
1023         // check question type
1024         if (!$question_type = $this->get_qtype( $question->qtype )) {
1025             // must be a plugin then, so just accept the name supplied
1026             $question_type = $question->qtype;
1027         }
1029         // add opening tag
1030         // generates specific header for Cloze and category type question
1031         if ($question->qtype == 'category') {
1032             $categorypath = $this->writetext( $question->category );
1033             $expout .= "  <question type=\"category\">\n";
1034             $expout .= "    <category>\n";
1035             $expout .= "        $categorypath\n";
1036             $expout .= "    </category>\n";
1037             $expout .= "  </question>\n";
1038             return $expout;
1039         } elseif ($question->qtype != MULTIANSWER) {
1040             // for all question types except Close
1041             $name_text = $this->writetext($question->name);
1042             $qtformat = $this->get_format($question->questiontextformat);
1043             $generalfeedbackformat = $this->get_format($question->generalfeedbackformat);
1045             $question_text = $this->writetext($question->questiontext);
1046             $question_text_files = $this->writefiles($question->questiontextfiles);
1048             $generalfeedback = $this->writetext($question->generalfeedback);
1049             $generalfeedback_files = $this->writefiles($question->generalfeedbackfiles);
1051             $expout .= "  <question type=\"$question_type\">\n";
1052             $expout .= "    <name>$name_text</name>\n";
1053             $expout .= "    <questiontext format=\"$qtformat\">\n";
1054             $expout .= $question_text;
1055             $expout .= $question_text_files;
1056             $expout .= "    </questiontext>\n";
1057             $expout .= "    <generalfeedback format=\"$generalfeedbackformat\">\n";
1058             $expout .= $generalfeedback;
1059             $expout .= $generalfeedback_files;
1060             $expout .= "    </generalfeedback>\n";
1061             $expout .= "    <defaultgrade>{$question->defaultgrade}</defaultgrade>\n";
1062             $expout .= "    <penalty>{$question->penalty}</penalty>\n";
1063             $expout .= "    <hidden>{$question->hidden}</hidden>\n";
1064         } else {
1065             // for Cloze type only
1066             $name_text = $this->writetext( $question->name );
1067             $question_text = $this->writetext( $question->questiontext );
1068             $generalfeedback = $this->writetext( $question->generalfeedback );
1069             $expout .= "  <question type=\"$question_type\">\n";
1070             $expout .= "    <name>$name_text</name>\n";
1071             $expout .= "    <questiontext>\n";
1072             $expout .= $question_text;
1073             $expout .= "    </questiontext>\n";
1074             $expout .= "    <generalfeedback>\n";
1075             $expout .= $generalfeedback;
1076             $expout .= "    </generalfeedback>\n";
1077         }
1079         if (!empty($question->options->shuffleanswers)) {
1080             $expout .= "    <shuffleanswers>{$question->options->shuffleanswers}</shuffleanswers>\n";
1081         }
1082         else {
1083             $expout .= "    <shuffleanswers>0</shuffleanswers>\n";
1084         }
1086         // output depends on question type
1087         switch($question->qtype) {
1088         case 'category':
1089             // not a qtype really - dummy used for category switching
1090             break;
1091         case TRUEFALSE:
1092             foreach ($question->options->answers as $answer) {
1093                 $fraction_pc = round( $answer->fraction * 100 );
1094                 if ($answer->id == $question->options->trueanswer) {
1095                     $answertext = 'true';
1096                 } else {
1097                     $answertext = 'false';
1098                 }
1099                 $expout .= "    <answer fraction=\"$fraction_pc\">\n";
1100                 $expout .= $this->writetext($answertext, 3) . "\n";
1101                 $feedbackformat = $this->get_format($answer->feedbackformat);
1102                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1103                 $expout .= $this->writetext($answer->feedback,4,false);
1104                 $expout .= $this->writefiles($answer->feedbackfiles);
1105                 $expout .= "      </feedback>\n";
1106                 $expout .= "    </answer>\n";
1107             }
1108             break;
1109         case MULTICHOICE:
1110             $expout .= "    <single>".$this->get_single($question->options->single)."</single>\n";
1111             $expout .= "    <shuffleanswers>".$this->get_single($question->options->shuffleanswers)."</shuffleanswers>\n";
1113             $textformat = $this->get_format($question->options->correctfeedbackformat);
1114             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'correctfeedback', $question->id);
1115             $expout .= "    <correctfeedback format=\"$textformat\">\n";
1116             $expout .= $this->writetext($question->options->correctfeedback, 3);
1117             $expout .= $this->writefiles($files);
1118             $expout .= "    </correctfeedback>\n";
1120             $textformat = $this->get_format($question->options->partiallycorrectfeedbackformat);
1121             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'partiallycorrectfeedback', $question->id);
1122             $expout .= "    <partiallycorrectfeedback format=\"$textformat\">\n";
1123             $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3);
1124             $expout .= $this->writefiles($files);
1125             $expout .= "    </partiallycorrectfeedback>\n";
1127             $textformat = $this->get_format($question->options->incorrectfeedbackformat);
1128             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'incorrectfeedback', $question->id);
1129             $expout .= "    <incorrectfeedback format=\"$textformat\">\n";
1130             $expout .= $this->writetext($question->options->incorrectfeedback, 3);
1131             $expout .= $this->writefiles($files);
1132             $expout .= "    </incorrectfeedback>\n";
1134             $expout .= "    <answernumbering>".$this->writetext($question->options->answernumbering, 3)."</answernumbering>\n";
1135             foreach($question->options->answers as $answer) {
1136                 $percent = $answer->fraction * 100;
1137                 $expout .= "      <answer fraction=\"$percent\">\n";
1138                 $expout .= $this->writetext($answer->answer,4,false);
1139                 $feedbackformat = $this->get_format($answer->feedbackformat);
1140                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1141                 $expout .= $this->writetext($answer->feedback,5,false);
1142                 $expout .= $this->writefiles($answer->feedbackfiles);
1143                 $expout .= "      </feedback>\n";
1144                 $expout .= "    </answer>\n";
1145                 }
1146             break;
1147         case SHORTANSWER:
1148             $expout .= "    <usecase>{$question->options->usecase}</usecase>\n ";
1149             foreach($question->options->answers as $answer) {
1150                 $percent = 100 * $answer->fraction;
1151                 $expout .= "    <answer fraction=\"$percent\">\n";
1152                 $expout .= $this->writetext( $answer->answer,3,false );
1153                 $feedbackformat = $this->get_format($answer->feedbackformat);
1154                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1155                 $expout .= $this->writetext($answer->feedback);
1156                 $expout .= $this->writefiles($answer->feedbackfiles);
1157                 $expout .= "      </feedback>\n";
1158                 $expout .= "    </answer>\n";
1159             }
1160             break;
1161         case NUMERICAL:
1162             foreach ($question->options->answers as $answer) {
1163                 $tolerance = $answer->tolerance;
1164                 $percent = 100 * $answer->fraction;
1165                 $expout .= "<answer fraction=\"$percent\">\n";
1166                 // <text> tags are an added feature, old filed won't have them
1167                 $expout .= "    <text>{$answer->answer}</text>\n";
1168                 $expout .= "    <tolerance>$tolerance</tolerance>\n";
1169                 $feedbackformat = $this->get_format($answer->feedbackformat);
1170                 $expout .= "    <feedback format=\"$feedbackformat\">\n";
1171                 $expout .= $this->writetext($answer->feedback);
1172                 $expout .= $this->writefiles($answer->feedbackfiles);
1173                 $expout .= "    </feedback>\n";
1174                 // fraction tag is deprecated
1175                 // $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
1176                 $expout .= "</answer>\n";
1177             }
1179             $units = $question->options->units;
1180             if (count($units)) {
1181                 $expout .= "<units>\n";
1182                 foreach ($units as $unit) {
1183                     $expout .= "  <unit>\n";
1184                     $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
1185                     $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
1186                     $expout .= "  </unit>\n";
1187                 }
1188                 $expout .= "</units>\n";
1189             }
1190             if (isset($question->options->unitgradingtype)) {
1191                 $expout .= "    <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n";
1192             }
1193             if (isset($question->options->unitpenalty)) {
1194                 $expout .= "    <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1195             }
1196             if (isset($question->options->showunits)) {
1197                 $expout .= "    <showunits>{$question->options->showunits}</showunits>\n";
1198             }
1199             if (isset($question->options->unitsleft)) {
1200                 $expout .= "    <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1201             }
1202             if (!empty($question->options->instructionsformat)) {
1203                 $textformat = $this->get_format($question->options->instructionsformat);
1204                 $files = $fs->get_area_files($contextid, 'qtype_numerical', 'instruction', $question->id);
1205                 $expout .= "    <instructions format=\"$textformat\">\n";
1206                 $expout .= $this->writetext($question->options->instructions, 3);
1207                 $expout .= $this->writefiles($files);
1208                 $expout .= "    </instructions>\n";
1209             }
1210             break;
1211         case MATCH:
1212             foreach($question->options->subquestions as $subquestion) {
1213                 $files = $fs->get_area_files($contextid, 'qtype_match', 'subquestion', $subquestion->id);
1214                 $textformat = $this->get_format($subquestion->questiontextformat);
1215                 $expout .= "<subquestion format=\"$textformat\">\n";
1216                 $expout .= $this->writetext($subquestion->questiontext);
1217                 $expout .= $this->writefiles($files);
1218                 $expout .= "<answer>";
1219                 $expout .= $this->writetext($subquestion->answertext);
1220                 $expout .= "</answer>\n";
1221                 $expout .= "</subquestion>\n";
1222             }
1223             break;
1224         case DESCRIPTION:
1225             // nothing more to do for this type
1226             break;
1227         case MULTIANSWER:
1228             $a_count=1;
1229             foreach($question->options->questions as $question) {
1230                 $thispattern = preg_quote("{#".$a_count."}"); //TODO: is this really necessary?
1231                 $thisreplace = $question->questiontext;
1232                 $expout=preg_replace("~$thispattern~", $thisreplace, $expout );
1233                 $a_count++;
1234             }
1235         break;
1236         case ESSAY:
1237             if (!empty($question->options->answers)) {
1238                 foreach ($question->options->answers as $answer) {
1239                     $percent = 100 * $answer->fraction;
1240                     $expout .= "<answer fraction=\"$percent\">\n";
1241                     $feedbackformat = $this->get_format($answer->feedbackformat);
1242                     $expout .= "    <feedback format=\"$feedbackformat\">\n";
1243                     $expout .= $this->writetext($answer->feedback);
1244                     $expout .= $this->writefiles($answer->feedbackfiles);
1245                     $expout .= "    </feedback>\n";
1246                     // fraction tag is deprecated
1247                     // $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
1248                     $expout .= "</answer>\n";
1249                 }
1250             }
1251             break;
1252         case CALCULATED:
1253         case CALCULATEDSIMPLE:
1254         case CALCULATEDMULTI:
1255             $expout .= "    <synchronize>{$question->options->synchronize}</synchronize>\n";
1256             $expout .= "    <single>{$question->options->single}</single>\n";
1257             $expout .= "    <answernumbering>{$question->options->answernumbering}</answernumbering>\n";
1258             $expout .= "    <shuffleanswers>".$this->writetext($question->options->shuffleanswers, 3)."</shuffleanswers>\n";
1260             $component = 'qtype_' . $question->qtype;
1261             $files = $fs->get_area_files($contextid, $component, 'correctfeedback', $question->id);
1262             $expout .= "    <correctfeedback>\n";
1263             $expout .= $this->writetext($question->options->correctfeedback, 3);
1264             $expout .= $this->writefiles($files);
1265             $expout .= "    </correctfeedback>\n";
1267             $files = $fs->get_area_files($contextid, $component, 'partiallycorrectfeedback', $question->id);
1268             $expout .= "    <partiallycorrectfeedback>\n";
1269             $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3);
1270             $expout .= $this->writefiles($files);
1271             $expout .= "    </partiallycorrectfeedback>\n";
1273             $files = $fs->get_area_files($contextid, $component, 'incorrectfeedback', $question->id);
1274             $expout .= "    <incorrectfeedback>\n";
1275             $expout .= $this->writetext($question->options->incorrectfeedback, 3);
1276             $expout .= $this->writefiles($files);
1277             $expout .= "    </incorrectfeedback>\n";
1279             foreach ($question->options->answers as $answer) {
1280                 $tolerance = $answer->tolerance;
1281                 $tolerancetype = $answer->tolerancetype;
1282                 $correctanswerlength= $answer->correctanswerlength ;
1283                 $correctanswerformat= $answer->correctanswerformat;
1284                 $percent = 100 * $answer->fraction;
1285                 $expout .= "<answer fraction=\"$percent\">\n";
1286                 // "<text/>" tags are an added feature, old files won't have them
1287                 $expout .= "    <text>{$answer->answer}</text>\n";
1288                 $expout .= "    <tolerance>$tolerance</tolerance>\n";
1289                 $expout .= "    <tolerancetype>$tolerancetype</tolerancetype>\n";
1290                 $expout .= "    <correctanswerformat>$correctanswerformat</correctanswerformat>\n";
1291                 $expout .= "    <correctanswerlength>$correctanswerlength</correctanswerlength>\n";
1292                 $feedbackformat = $this->get_format($answer->feedbackformat);
1293                 $expout .= "    <feedback format=\"$feedbackformat\">\n";
1294                 $expout .= $this->writetext($answer->feedback);
1295                 $expout .= $this->writefiles($answer->feedbackfiles);
1296                 $expout .= "    </feedback>\n";
1297                 $expout .= "</answer>\n";
1298             }
1299             if (isset($question->options->unitgradingtype)) {
1300                 $expout .= "    <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n";
1301             }
1302             if (isset($question->options->unitpenalty)) {
1303                 $expout .= "    <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1304             }
1305             if (isset($question->options->showunits)) {
1306                 $expout .= "    <showunits>{$question->options->showunits}</showunits>\n";
1307             }
1308             if (isset($question->options->unitsleft)) {
1309                 $expout .= "    <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1310             }
1312             if (isset($question->options->instructionsformat)) {
1313                 $textformat = $this->get_format($question->options->instructionsformat);
1314                 $files = $fs->get_area_files($contextid, $component, 'instruction', $question->id);
1315                 $expout .= "    <instructions format=\"$textformat\">\n";
1316                 $expout .= $this->writetext($question->options->instructions, 3);
1317                 $expout .= $this->writefiles($files);
1318                 $expout .= "    </instructions>\n";
1319             }
1321             if (isset($question->options->units)) {
1322                 $units = $question->options->units;
1323                 if (count($units)) {
1324                     $expout .= "<units>\n";
1325                     foreach ($units as $unit) {
1326                         $expout .= "  <unit>\n";
1327                         $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
1328                         $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
1329                         $expout .= "  </unit>\n";
1330                     }
1331                     $expout .= "</units>\n";
1332                 }
1333             }
1334             //The tag $question->export_process has been set so we get all the data items in the database
1335             //   from the function $QTYPES['calculated']->get_question_options(&$question);
1336             //  calculatedsimple defaults to calculated
1337             if( isset($question->options->datasets)&&count($question->options->datasets)){// there should be
1338                 $expout .= "<dataset_definitions>\n";
1339                 foreach ($question->options->datasets as $def) {
1340                     $expout .= "<dataset_definition>\n";
1341                     $expout .= "    <status>".$this->writetext($def->status)."</status>\n";
1342                     $expout .= "    <name>".$this->writetext($def->name)."</name>\n";
1343                     if ( $question->qtype == CALCULATED){
1344                         $expout .= "    <type>calculated</type>\n";
1345                     }else {
1346                         $expout .= "    <type>calculatedsimple</type>\n";
1347                     }
1348                     $expout .= "    <distribution>".$this->writetext($def->distribution)."</distribution>\n";
1349                     $expout .= "    <minimum>".$this->writetext($def->minimum)."</minimum>\n";
1350                     $expout .= "    <maximum>".$this->writetext($def->maximum)."</maximum>\n";
1351                     $expout .= "    <decimals>".$this->writetext($def->decimals)."</decimals>\n";
1352                     $expout .= "    <itemcount>$def->itemcount</itemcount>\n";
1353                     if ($def->itemcount > 0 ) {
1354                         $expout .= "    <dataset_items>\n";
1355                         foreach ($def->items as $item ){
1356                               $expout .= "        <dataset_item>\n";
1357                               $expout .= "           <number>".$item->itemnumber."</number>\n";
1358                               $expout .= "           <value>".$item->value."</value>\n";
1359                               $expout .= "        </dataset_item>\n";
1360                         }
1361                         $expout .= "    </dataset_items>\n";
1362                         $expout .= "    <number_of_items>".$def-> number_of_items."</number_of_items>\n";
1363                      }
1364                     $expout .= "</dataset_definition>\n";
1365                 }
1366                 $expout .= "</dataset_definitions>\n";
1367             }
1368             break;
1369         default:
1370             // try support by optional plugin
1371             if (!$data = $this->try_exporting_using_qtypes( $question->qtype, $question )) {
1372                 echo $OUTPUT->notification( get_string( 'unsupportedexport','qformat_xml',$QTYPES[$question->qtype]->local_name() ) );
1373             }
1374             $expout .= $data;
1375         }
1377         // close the question tag
1378         $expout .= "</question>\n";
1380         // XXX: debuging
1381         //echo '<textarea cols=100 rows=20>';
1382         //echo $expout;
1383         //echo '</textarea>';
1385         return $expout;
1386     }