MDL-25104 Fixing some other error about $files in $instructions param of numerical
[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             foreach ($files as $file) {
732                 $data = new stdclass;
733                 $data->content = $file['#'];
734                 $data->encoding = $file['@']['encoding'];
735                 $data->name = $file['@']['name'];
736                 $qo->instructionsfiles[] = $data;
737             }
738         }
739         $datasets = $question['#']['dataset_definitions'][0]['#']['dataset_definition'];
740         $qo->dataset = array();
741         $qo->datasetindex= 0 ;
742         foreach ($datasets as $dataset) {
743             $qo->datasetindex++;
744             $qo->dataset[$qo->datasetindex] = new stdClass();
745             $qo->dataset[$qo->datasetindex]->status = $this->import_text( $dataset['#']['status'][0]['#']['text']);
746             $qo->dataset[$qo->datasetindex]->name = $this->import_text( $dataset['#']['name'][0]['#']['text']);
747             $qo->dataset[$qo->datasetindex]->type =  $dataset['#']['type'][0]['#'];
748             $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']);
749             $qo->dataset[$qo->datasetindex]->max = $this->import_text( $dataset['#']['maximum'][0]['#']['text']);
750             $qo->dataset[$qo->datasetindex]->min = $this->import_text( $dataset['#']['minimum'][0]['#']['text']);
751             $qo->dataset[$qo->datasetindex]->length = $this->import_text( $dataset['#']['decimals'][0]['#']['text']);
752             $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']);
753             $qo->dataset[$qo->datasetindex]->itemcount = $dataset['#']['itemcount'][0]['#'];
754             $qo->dataset[$qo->datasetindex]->datasetitem = array();
755             $qo->dataset[$qo->datasetindex]->itemindex = 0;
756             $qo->dataset[$qo->datasetindex]->number_of_items=$dataset['#']['number_of_items'][0]['#'];
757             $datasetitems = $dataset['#']['dataset_items'][0]['#']['dataset_item'];
758             foreach ($datasetitems as $datasetitem) {
759                 $qo->dataset[$qo->datasetindex]->itemindex++;
760                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex] = new stdClass();
761                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->itemnumber =  $datasetitem['#']['number'][0]['#']; //[0]['#']['number'][0]['#'] ; // [0]['numberitems'] ;//['#']['number'][0]['#'];// $datasetitems['#']['number'][0]['#'];
762                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->value = $datasetitem['#']['value'][0]['#'] ;//$datasetitem['#']['value'][0]['#'];
763             }
764         }
766         // echo "<pre>loaded qo";print_r($qo);echo "</pre>";
767         return $qo;
768     }
770     /**
771      * this is not a real question type. It's a dummy type used
772      * to specify the import category
773      * format is:
774      * <question type="category">
775      *     <category>tom/dick/harry</category>
776      * </question>
777      */
778     function import_category( $question ) {
779         $qo = new stdClass;
780         $qo->qtype = 'category';
781         $qo->category = $this->import_text($question['#']['category'][0]['#']['text']);
782         return $qo;
783     }
785     /**
786      * parse the array of lines into an array of questions
787      * this *could* burn memory - but it won't happen that much
788      * so fingers crossed!
789      * @param array lines array of lines from the input file
790      * @return array (of objects) question objects
791      */
792     function readquestions($lines) {
793         // we just need it as one big string
794         $text = implode($lines, " ");
795         unset($lines);
797         // this converts xml to big nasty data structure
798         // the 0 means keep white space as it is (important for markdown format)
799         // print_r it if you want to see what it looks like!
800         $xml = xmlize($text, 0);
802         // set up array to hold all our questions
803         $questions = array();
805         // iterate through questions
806         foreach ($xml['quiz']['#']['question'] as $question) {
807             $question_type = $question['@']['type'];
808             $questiontype = get_string( 'questiontype','quiz',$question_type );
810             if ($question_type=='multichoice') {
811                 $qo = $this->import_multichoice( $question );
812             }
813             elseif ($question_type=='truefalse') {
814                 $qo = $this->import_truefalse( $question );
815             }
816             elseif ($question_type=='shortanswer') {
817                 $qo = $this->import_shortanswer( $question );
818             }
819             elseif ($question_type=='numerical') {
820                 $qo = $this->import_numerical( $question );
821             }
822             elseif ($question_type=='description') {
823                 $qo = $this->import_description( $question );
824             }
825             elseif ($question_type=='matching') {
826                 $qo = $this->import_matching( $question );
827             }
828             elseif ($question_type=='cloze') {
829                 $qo = $this->import_multianswer( $question );
830             }
831             elseif ($question_type=='essay') {
832                 $qo = $this->import_essay( $question );
833             }
834             elseif ($question_type=='calculated') {
835                 $qo = $this->import_calculated( $question,CALCULATED  );
836             }
837             elseif ($question_type=='calculatedsimple') {
838                 $qo = $this->import_calculated( $question,CALCULATEDMULTI  );
839                 $qo->qtype = CALCULATEDSIMPLE ;
840             }
841             elseif ($question_type=='calculatedmulti') {
842                 $qo = $this->import_calculated( $question,CALCULATEDMULTI );
843                 $qo->qtype = CALCULATEDMULTI ;
844             }
845             elseif ($question_type=='category') {
846                 $qo = $this->import_category( $question );
847             }
848             else {
849                 // try for plugin support
850                 // no default question, as the plugin can call
851                 // import_headers() itself if it wants to
852                 if (!$qo = $this->try_importing_using_qtypes( $question, null, null, $question_type)) {
853                     $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type );
854                     $this->error( $notsupported );
855                     $qo = null;
856                 }
857             }
859             // stick the result in the $questions array
860             if ($qo) {
861                 $questions[] = $qo;
862             }
863         }
864         return $questions;
865     }
867     // EXPORT FUNCTIONS START HERE
869     function export_file_extension() {
870         return '.xml';
871     }
873     /**
874      * Turn the internal question code into a human readable form
875      * (The code used to be numeric, but this remains as some of
876      * the names don't match the new internal format)
877      * @param mixed type_id Internal code
878      * @return string question type string
879      */
880     function get_qtype( $type_id ) {
881         switch( $type_id ) {
882         case TRUEFALSE:
883             $name = 'truefalse';
884             break;
885         case MULTICHOICE:
886             $name = 'multichoice';
887             break;
888         case SHORTANSWER:
889             $name = 'shortanswer';
890             break;
891         case NUMERICAL:
892             $name = 'numerical';
893             break;
894         case MATCH:
895             $name = 'matching';
896             break;
897         case DESCRIPTION:
898             $name = 'description';
899             break;
900         case MULTIANSWER:
901             $name = 'cloze';
902             break;
903         case ESSAY:
904             $name = 'essay';
905             break;
906         case CALCULATED:
907             $name = 'calculated';
908             break;
909         case CALCULATEDSIMPLE:
910             $name = 'calculatedsimple';
911             break;
912         case CALCULATEDMULTI:
913             $name = 'calculatedmulti';
914             break;
915         default:
916             $name = false;
917         }
918         return $name;
919     }
921     /**
922      * Convert internal Moodle text format code into
923      * human readable form
924      * @param int id internal code
925      * @return string format text
926      */
927     function get_format( $id ) {
928         switch( $id ) {
929         case 0:
930             $name = "moodle_auto_format";
931             break;
932         case 1:
933             $name = "html";
934             break;
935         case 2:
936             $name = "plain_text";
937             break;
938         case 3:
939             $name = "wiki_like";
940             break;
941         case 4:
942             $name = "markdown";
943             break;
944         default:
945             $name = "unknown";
946         }
947         return $name;
948     }
950     /**
951      * Convert internal single question code into
952      * human readable form
953      * @param int id single question code
954      * @return string single question string
955      */
956     function get_single( $id ) {
957         switch( $id ) {
958         case 0:
959             $name = "false";
960             break;
961         case 1:
962             $name = "true";
963             break;
964         default:
965             $name = "unknown";
966         }
967         return $name;
968     }
970     /**
971      * generates <text></text> tags, processing raw text therein
972      * @param int ilev the current indent level
973      * @param boolean short stick it on one line
974      * @return string formatted text
975      */
976     function writetext($raw, $ilev=0, $short=true) {
977         $indent = str_repeat( "  ",$ilev );
979         // if required add CDATA tags
980         if (!empty($raw) and (htmlspecialchars($raw)!=$raw)) {
981             $raw = "<![CDATA[$raw]]>";
982         }
984         if ($short) {
985             $xml = "$indent<text>$raw</text>\n";
986         }
987         else {
988             $xml = "$indent<text>\n$raw\n$indent</text>\n";
989         }
991         return $xml;
992     }
994     function presave_process( $content ) {
995     // override method to allow us to add xml headers and footers
997         // add the xml headers and footers
998         $content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
999                        "<quiz>\n" .
1000                        $content . "\n" .
1001                        "</quiz>";
1002         return $content;
1003     }
1005     /**
1006      * Turns question into an xml segment
1007      * @param object question object
1008      * @param int context id
1009      * @return string xml segment
1010      */
1011     function writequestion($question) {
1012         global $CFG, $QTYPES, $OUTPUT;
1014         $fs = get_file_storage();
1015         $contextid = $question->contextid;
1016         // initial string;
1017         $expout = "";
1019         // add comment
1020         $expout .= "\n\n<!-- question: $question->id  -->\n";
1022         // check question type
1023         if (!$question_type = $this->get_qtype( $question->qtype )) {
1024             // must be a plugin then, so just accept the name supplied
1025             $question_type = $question->qtype;
1026         }
1028         // add opening tag
1029         // generates specific header for Cloze and category type question
1030         if ($question->qtype == 'category') {
1031             $categorypath = $this->writetext( $question->category );
1032             $expout .= "  <question type=\"category\">\n";
1033             $expout .= "    <category>\n";
1034             $expout .= "        $categorypath\n";
1035             $expout .= "    </category>\n";
1036             $expout .= "  </question>\n";
1037             return $expout;
1038         } elseif ($question->qtype != MULTIANSWER) {
1039             // for all question types except Close
1040             $name_text = $this->writetext($question->name);
1041             $qtformat = $this->get_format($question->questiontextformat);
1042             $generalfeedbackformat = $this->get_format($question->generalfeedbackformat);
1044             $question_text = $this->writetext($question->questiontext);
1045             $question_text_files = $this->writefiles($question->questiontextfiles);
1047             $generalfeedback = $this->writetext($question->generalfeedback);
1048             $generalfeedback_files = $this->writefiles($question->generalfeedbackfiles);
1050             $expout .= "  <question type=\"$question_type\">\n";
1051             $expout .= "    <name>$name_text</name>\n";
1052             $expout .= "    <questiontext format=\"$qtformat\">\n";
1053             $expout .= $question_text;
1054             $expout .= $question_text_files;
1055             $expout .= "    </questiontext>\n";
1056             $expout .= "    <generalfeedback format=\"$generalfeedbackformat\">\n";
1057             $expout .= $generalfeedback;
1058             $expout .= $generalfeedback_files;
1059             $expout .= "    </generalfeedback>\n";
1060             $expout .= "    <defaultgrade>{$question->defaultgrade}</defaultgrade>\n";
1061             $expout .= "    <penalty>{$question->penalty}</penalty>\n";
1062             $expout .= "    <hidden>{$question->hidden}</hidden>\n";
1063         } else {
1064             // for Cloze type only
1065             $name_text = $this->writetext( $question->name );
1066             $question_text = $this->writetext( $question->questiontext );
1067             $generalfeedback = $this->writetext( $question->generalfeedback );
1068             $expout .= "  <question type=\"$question_type\">\n";
1069             $expout .= "    <name>$name_text</name>\n";
1070             $expout .= "    <questiontext>\n";
1071             $expout .= $question_text;
1072             $expout .= "    </questiontext>\n";
1073             $expout .= "    <generalfeedback>\n";
1074             $expout .= $generalfeedback;
1075             $expout .= "    </generalfeedback>\n";
1076         }
1078         if (!empty($question->options->shuffleanswers)) {
1079             $expout .= "    <shuffleanswers>{$question->options->shuffleanswers}</shuffleanswers>\n";
1080         }
1081         else {
1082             $expout .= "    <shuffleanswers>0</shuffleanswers>\n";
1083         }
1085         // output depends on question type
1086         switch($question->qtype) {
1087         case 'category':
1088             // not a qtype really - dummy used for category switching
1089             break;
1090         case TRUEFALSE:
1091             foreach ($question->options->answers as $answer) {
1092                 $fraction_pc = round( $answer->fraction * 100 );
1093                 if ($answer->id == $question->options->trueanswer) {
1094                     $answertext = 'true';
1095                 } else {
1096                     $answertext = 'false';
1097                 }
1098                 $expout .= "    <answer fraction=\"$fraction_pc\">\n";
1099                 $expout .= $this->writetext($answertext, 3) . "\n";
1100                 $feedbackformat = $this->get_format($answer->feedbackformat);
1101                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1102                 $expout .= $this->writetext($answer->feedback,4,false);
1103                 $expout .= $this->writefiles($answer->feedbackfiles);
1104                 $expout .= "      </feedback>\n";
1105                 $expout .= "    </answer>\n";
1106             }
1107             break;
1108         case MULTICHOICE:
1109             $expout .= "    <single>".$this->get_single($question->options->single)."</single>\n";
1110             $expout .= "    <shuffleanswers>".$this->get_single($question->options->shuffleanswers)."</shuffleanswers>\n";
1112             $textformat = $this->get_format($question->options->correctfeedbackformat);
1113             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'correctfeedback', $question->id);
1114             $expout .= "    <correctfeedback format=\"$textformat\">\n";
1115             $expout .= $this->writetext($question->options->correctfeedback, 3);
1116             $expout .= $this->writefiles($files);
1117             $expout .= "    </correctfeedback>\n";
1119             $textformat = $this->get_format($question->options->partiallycorrectfeedbackformat);
1120             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'partiallycorrectfeedback', $question->id);
1121             $expout .= "    <partiallycorrectfeedback format=\"$textformat\">\n";
1122             $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3);
1123             $expout .= $this->writefiles($files);
1124             $expout .= "    </partiallycorrectfeedback>\n";
1126             $textformat = $this->get_format($question->options->incorrectfeedbackformat);
1127             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'incorrectfeedback', $question->id);
1128             $expout .= "    <incorrectfeedback format=\"$textformat\">\n";
1129             $expout .= $this->writetext($question->options->incorrectfeedback, 3);
1130             $expout .= $this->writefiles($files);
1131             $expout .= "    </incorrectfeedback>\n";
1133             $expout .= "    <answernumbering>".$this->writetext($question->options->answernumbering, 3)."</answernumbering>\n";
1134             foreach($question->options->answers as $answer) {
1135                 $percent = $answer->fraction * 100;
1136                 $expout .= "      <answer fraction=\"$percent\">\n";
1137                 $expout .= $this->writetext($answer->answer,4,false);
1138                 $feedbackformat = $this->get_format($answer->feedbackformat);
1139                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1140                 $expout .= $this->writetext($answer->feedback,5,false);
1141                 $expout .= $this->writefiles($answer->feedbackfiles);
1142                 $expout .= "      </feedback>\n";
1143                 $expout .= "    </answer>\n";
1144                 }
1145             break;
1146         case SHORTANSWER:
1147             $expout .= "    <usecase>{$question->options->usecase}</usecase>\n ";
1148             foreach($question->options->answers as $answer) {
1149                 $percent = 100 * $answer->fraction;
1150                 $expout .= "    <answer fraction=\"$percent\">\n";
1151                 $expout .= $this->writetext( $answer->answer,3,false );
1152                 $feedbackformat = $this->get_format($answer->feedbackformat);
1153                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1154                 $expout .= $this->writetext($answer->feedback);
1155                 $expout .= $this->writefiles($answer->feedbackfiles);
1156                 $expout .= "      </feedback>\n";
1157                 $expout .= "    </answer>\n";
1158             }
1159             break;
1160         case NUMERICAL:
1161             foreach ($question->options->answers as $answer) {
1162                 $tolerance = $answer->tolerance;
1163                 $percent = 100 * $answer->fraction;
1164                 $expout .= "<answer fraction=\"$percent\">\n";
1165                 // <text> tags are an added feature, old filed won't have them
1166                 $expout .= "    <text>{$answer->answer}</text>\n";
1167                 $expout .= "    <tolerance>$tolerance</tolerance>\n";
1168                 $feedbackformat = $this->get_format($answer->feedbackformat);
1169                 $expout .= "    <feedback format=\"$feedbackformat\">\n";
1170                 $expout .= $this->writetext($answer->feedback);
1171                 $expout .= $this->writefiles($answer->feedbackfiles);
1172                 $expout .= "    </feedback>\n";
1173                 // fraction tag is deprecated
1174                 // $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
1175                 $expout .= "</answer>\n";
1176             }
1178             $units = $question->options->units;
1179             if (count($units)) {
1180                 $expout .= "<units>\n";
1181                 foreach ($units as $unit) {
1182                     $expout .= "  <unit>\n";
1183                     $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
1184                     $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
1185                     $expout .= "  </unit>\n";
1186                 }
1187                 $expout .= "</units>\n";
1188             }
1189             if (isset($question->options->unitgradingtype)) {
1190                 $expout .= "    <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n";
1191             }
1192             if (isset($question->options->unitpenalty)) {
1193                 $expout .= "    <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1194             }
1195             if (isset($question->options->showunits)) {
1196                 $expout .= "    <showunits>{$question->options->showunits}</showunits>\n";
1197             }
1198             if (isset($question->options->unitsleft)) {
1199                 $expout .= "    <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1200             }
1201             if (!empty($question->options->instructionsformat)) {
1202                 $textformat = $this->get_format($question->options->instructionsformat);
1203                 $files = $fs->get_area_files($contextid, 'qtype_numerical', 'instruction', $question->id);
1204                 $expout .= "    <instructions format=\"$textformat\">\n";
1205                 $expout .= $this->writetext($question->options->instructions, 3);
1206                 $expout .= $this->writefiles($files);
1207                 $expout .= "    </instructions>\n";
1208             }
1209             break;
1210         case MATCH:
1211             foreach($question->options->subquestions as $subquestion) {
1212                 $files = $fs->get_area_files($contextid, 'qtype_match', 'subquestion', $subquestion->id);
1213                 $textformat = $this->get_format($subquestion->questiontextformat);
1214                 $expout .= "<subquestion format=\"$textformat\">\n";
1215                 $expout .= $this->writetext($subquestion->questiontext);
1216                 $expout .= $this->writefiles($files);
1217                 $expout .= "<answer>";
1218                 $expout .= $this->writetext($subquestion->answertext);
1219                 $expout .= "</answer>\n";
1220                 $expout .= "</subquestion>\n";
1221             }
1222             break;
1223         case DESCRIPTION:
1224             // nothing more to do for this type
1225             break;
1226         case MULTIANSWER:
1227             $a_count=1;
1228             foreach($question->options->questions as $question) {
1229                 $thispattern = preg_quote("{#".$a_count."}"); //TODO: is this really necessary?
1230                 $thisreplace = $question->questiontext;
1231                 $expout=preg_replace("~$thispattern~", $thisreplace, $expout );
1232                 $a_count++;
1233             }
1234         break;
1235         case ESSAY:
1236             if (!empty($question->options->answers)) {
1237                 foreach ($question->options->answers as $answer) {
1238                     $percent = 100 * $answer->fraction;
1239                     $expout .= "<answer fraction=\"$percent\">\n";
1240                     $feedbackformat = $this->get_format($answer->feedbackformat);
1241                     $expout .= "    <feedback format=\"$feedbackformat\">\n";
1242                     $expout .= $this->writetext($answer->feedback);
1243                     $expout .= $this->writefiles($answer->feedbackfiles);
1244                     $expout .= "    </feedback>\n";
1245                     // fraction tag is deprecated
1246                     // $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
1247                     $expout .= "</answer>\n";
1248                 }
1249             }
1250             break;
1251         case CALCULATED:
1252         case CALCULATEDSIMPLE:
1253         case CALCULATEDMULTI:
1254             $expout .= "    <synchronize>{$question->options->synchronize}</synchronize>\n";
1255             $expout .= "    <single>{$question->options->single}</single>\n";
1256             $expout .= "    <answernumbering>{$question->options->answernumbering}</answernumbering>\n";
1257             $expout .= "    <shuffleanswers>".$this->writetext($question->options->shuffleanswers, 3)."</shuffleanswers>\n";
1259             $component = 'qtype_' . $question->qtype;
1260             $files = $fs->get_area_files($contextid, $component, 'correctfeedback', $question->id);
1261             $expout .= "    <correctfeedback>\n";
1262             $expout .= $this->writetext($question->options->correctfeedback, 3);
1263             $expout .= $this->writefiles($files);
1264             $expout .= "    </correctfeedback>\n";
1266             $files = $fs->get_area_files($contextid, $component, 'partiallycorrectfeedback', $question->id);
1267             $expout .= "    <partiallycorrectfeedback>\n";
1268             $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3);
1269             $expout .= $this->writefiles($files);
1270             $expout .= "    </partiallycorrectfeedback>\n";
1272             $files = $fs->get_area_files($contextid, $component, 'incorrectfeedback', $question->id);
1273             $expout .= "    <incorrectfeedback>\n";
1274             $expout .= $this->writetext($question->options->incorrectfeedback, 3);
1275             $expout .= $this->writefiles($files);
1276             $expout .= "    </incorrectfeedback>\n";
1278             foreach ($question->options->answers as $answer) {
1279                 $tolerance = $answer->tolerance;
1280                 $tolerancetype = $answer->tolerancetype;
1281                 $correctanswerlength= $answer->correctanswerlength ;
1282                 $correctanswerformat= $answer->correctanswerformat;
1283                 $percent = 100 * $answer->fraction;
1284                 $expout .= "<answer fraction=\"$percent\">\n";
1285                 // "<text/>" tags are an added feature, old files won't have them
1286                 $expout .= "    <text>{$answer->answer}</text>\n";
1287                 $expout .= "    <tolerance>$tolerance</tolerance>\n";
1288                 $expout .= "    <tolerancetype>$tolerancetype</tolerancetype>\n";
1289                 $expout .= "    <correctanswerformat>$correctanswerformat</correctanswerformat>\n";
1290                 $expout .= "    <correctanswerlength>$correctanswerlength</correctanswerlength>\n";
1291                 $feedbackformat = $this->get_format($answer->feedbackformat);
1292                 $expout .= "    <feedback format=\"$feedbackformat\">\n";
1293                 $expout .= $this->writetext($answer->feedback);
1294                 $expout .= $this->writefiles($answer->feedbackfiles);
1295                 $expout .= "    </feedback>\n";
1296                 $expout .= "</answer>\n";
1297             }
1298             if (isset($question->options->unitgradingtype)) {
1299                 $expout .= "    <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n";
1300             }
1301             if (isset($question->options->unitpenalty)) {
1302                 $expout .= "    <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1303             }
1304             if (isset($question->options->showunits)) {
1305                 $expout .= "    <showunits>{$question->options->showunits}</showunits>\n";
1306             }
1307             if (isset($question->options->unitsleft)) {
1308                 $expout .= "    <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1309             }
1311             if (isset($question->options->instructionsformat)) {
1312                 $textformat = $this->get_format($question->options->instructionsformat);
1313                 $files = $fs->get_area_files($contextid, $component, 'instruction', $question->id);
1314                 $expout .= "    <instructions format=\"$textformat\">\n";
1315                 $expout .= $this->writetext($question->options->instructions, 3);
1316                 $expout .= $this->writefiles($files);
1317                 $expout .= "    </instructions>\n";
1318             }
1320             if (isset($question->options->units)) {
1321                 $units = $question->options->units;
1322                 if (count($units)) {
1323                     $expout .= "<units>\n";
1324                     foreach ($units as $unit) {
1325                         $expout .= "  <unit>\n";
1326                         $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
1327                         $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
1328                         $expout .= "  </unit>\n";
1329                     }
1330                     $expout .= "</units>\n";
1331                 }
1332             }
1333             //The tag $question->export_process has been set so we get all the data items in the database
1334             //   from the function $QTYPES['calculated']->get_question_options(&$question);
1335             //  calculatedsimple defaults to calculated
1336             if( isset($question->options->datasets)&&count($question->options->datasets)){// there should be
1337                 $expout .= "<dataset_definitions>\n";
1338                 foreach ($question->options->datasets as $def) {
1339                     $expout .= "<dataset_definition>\n";
1340                     $expout .= "    <status>".$this->writetext($def->status)."</status>\n";
1341                     $expout .= "    <name>".$this->writetext($def->name)."</name>\n";
1342                     if ( $question->qtype == CALCULATED){
1343                         $expout .= "    <type>calculated</type>\n";
1344                     }else {
1345                         $expout .= "    <type>calculatedsimple</type>\n";
1346                     }
1347                     $expout .= "    <distribution>".$this->writetext($def->distribution)."</distribution>\n";
1348                     $expout .= "    <minimum>".$this->writetext($def->minimum)."</minimum>\n";
1349                     $expout .= "    <maximum>".$this->writetext($def->maximum)."</maximum>\n";
1350                     $expout .= "    <decimals>".$this->writetext($def->decimals)."</decimals>\n";
1351                     $expout .= "    <itemcount>$def->itemcount</itemcount>\n";
1352                     if ($def->itemcount > 0 ) {
1353                         $expout .= "    <dataset_items>\n";
1354                         foreach ($def->items as $item ){
1355                               $expout .= "        <dataset_item>\n";
1356                               $expout .= "           <number>".$item->itemnumber."</number>\n";
1357                               $expout .= "           <value>".$item->value."</value>\n";
1358                               $expout .= "        </dataset_item>\n";
1359                         }
1360                         $expout .= "    </dataset_items>\n";
1361                         $expout .= "    <number_of_items>".$def-> number_of_items."</number_of_items>\n";
1362                      }
1363                     $expout .= "</dataset_definition>\n";
1364                 }
1365                 $expout .= "</dataset_definitions>\n";
1366             }
1367             break;
1368         default:
1369             // try support by optional plugin
1370             if (!$data = $this->try_exporting_using_qtypes( $question->qtype, $question )) {
1371                 echo $OUTPUT->notification( get_string( 'unsupportedexport','qformat_xml',$QTYPES[$question->qtype]->local_name() ) );
1372             }
1373             $expout .= $data;
1374         }
1376         // close the question tag
1377         $expout .= "</question>\n";
1379         // XXX: debuging
1380         //echo '<textarea cols=100 rows=20>';
1381         //echo $expout;
1382         //echo '</textarea>';
1384         return $expout;
1385     }