6ce5c1bd7828790c395c761c290f43376719d490
[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(
512                     array('0', '@', 'format'), 'moodle_auto_format'));
513             $files = $this->getpath($question, array('#', 'questiontext', 0, '#','file'), array(), false);
514             $files = $this->getpath($instructions, array('0', '#', 'file'), array());
515             $qo->instructionsfiles = array();
516             foreach ($files as $file) {
517                 $data = new stdclass;
518                 $data->content = $file['#'];
519                 $data->encoding = $file['@']['encoding'];
520                 $data->name = $file['@']['name'];
521                 $qo->instructionsfiles[] = $data;
522             }
523         }
524         return $qo;
525     }
527     /**
528      * import matching type question
529      * @param array question question array from xml tree
530      * @return object question object
531      */
532     function import_matching($question) {
533         // get common parts
534         $qo = $this->import_headers($question);
536         // header parts particular to matching
537         $qo->qtype = MATCH;
538         $qo->shuffleanswers = $this->getpath($question, array('#', 'shuffleanswers', 0, '#'), 1);
540         // get subquestions
541         $subquestions = $question['#']['subquestion'];
542         $qo->subquestions = array();
543         $qo->subanswers = array();
545         // run through subquestions
546         foreach ($subquestions as $subquestion) {
547             $question = array();
548             $question['text'] = $this->getpath($subquestion, array('#', 'text', 0, '#'), '', true);
549             $question['format'] = $this->trans_format(
550                     $this->getpath($subquestion, array('@', 'format'), 'moodle_auto_format'));
551             $question['files'] = array();
553             $files = $this->getpath($subquestion, array('#', 'file'), array());
554             foreach ($files as $file) {
555                 $data = new stdclass();
556                 $data->content = $file['#'];
557                 $data->encoding = $file['@']['encoding'];
558                 $data->name = $file['@']['name'];
559                 $question['files'][] = $data;
560             }
561             $qo->subquestions[] = $question;
562             $answers = $this->getpath($subquestion, array('#', 'answer'), array());
563             $qo->subanswers[] = $this->getpath($subquestion, array('#','answer',0,'#','text',0,'#'), '', true);
564         }
565         return $qo;
566     }
568     /**
569      * import  essay type question
570      * @param array question question array from xml tree
571      * @return object question object
572      */
573     function import_essay( $question ) {
574         // get common parts
575         $qo = $this->import_headers( $question );
577         // header parts particular to essay
578         $qo->qtype = ESSAY;
580         $answers = $this->getpath($question, array('#', 'answer'), null);
581         if ($answers) {
582             $answer = array_pop($answers);
583             $answer = $this->import_answer($answer);
584             // get feedback
585             $qo->feedback = $answer->feedback;
586         } else {
587             $qo->feedback = array('text' => '', 'format' => FORMAT_MOODLE, 'files' => array());
588         }
590         // get fraction - <fraction> tag is deprecated
591         $qo->fraction = $this->getpath($question, array('@','fraction'), 0 ) / 100;
592         $q0->fraction = $this->getpath($question, array('#','fraction',0,'#'), $qo->fraction );
594         return $qo;
595     }
597     function import_calculated($question,$qtype) {
598     // import calculated question
600         // get common parts
601         $qo = $this->import_headers( $question );
603         // header parts particular to calculated
604         $qo->qtype = CALCULATED ;//CALCULATED;
605         $qo->synchronize = $this->getpath( $question, array( '#','synchronize',0,'#' ), 0 );
606         $single = $this->getpath( $question, array('#','single',0,'#'), 'true' );
607         $qo->single = $this->trans_single( $single );
608         $shuffleanswers = $this->getpath( $question, array('#','shuffleanswers',0,'#'), 'false' );
609         $qo->answernumbering = $this->getpath( $question, array('#','answernumbering',0,'#'), 'abc' );
610         $qo->shuffleanswers = $this->trans_single($shuffleanswers);
612         $qo->correctfeedback = array();
613         $qo->correctfeedback['text'] = $this->getpath($question, array('#','correctfeedback',0,'#','text',0,'#'), '', true );
614         $qo->correctfeedback['format'] = $this->trans_format($this->getpath(
615                 $question, array('#', 'correctfeedback', 0, '@', 'formath'), 'moodle_auto_format'));
616         $qo->correctfeedback['files'] = array();
618         $files = $this->getpath($question, array('#', 'correctfeedback', '0', '#', 'file'), array());
619         foreach ($files as $file) {
620             $data = new stdclass();
621             $data->content = $file['#'];
622             $data->name = $file['@']['name'];
623             $data->encoding = $file['@']['encoding'];
624             $qo->correctfeedback['files'][] = $data;
625         }
627         $qo->partiallycorrectfeedback = array();
628         $qo->partiallycorrectfeedback['text'] = $this->getpath( $question, array('#','partiallycorrectfeedback',0,'#','text',0,'#'), '', true );
629         $qo->partiallycorrectfeedback['format'] = $this->trans_format(
630                 $this->getpath($question, array('#','partiallycorrectfeedback', 0, '@','format'), 'moodle_auto_format'));
631         $qo->partiallycorrectfeedback['files'] = array();
633         $files = $this->getpath($question, array('#', 'partiallycorrectfeedback', '0', '#', 'file'), array());
634         foreach ($files as $file) {
635             $data = new stdclass();
636             $data->content = $file['#'];
637             $data->name = $file['@']['name'];
638             $data->encoding = $file['@']['encoding'];
639             $qo->partiallycorrectfeedback['files'][] = $data;
640         }
642         $qo->incorrectfeedback = array();
643         $qo->incorrectfeedback['text'] = $this->getpath( $question, array('#','incorrectfeedback',0,'#','text',0,'#'), '', true );
644         $qo->incorrectfeedback['format'] = $this->trans_format($this->getpath(
645                 $question, array('#','incorrectfeedback', 0, '@','format'), 'moodle_auto_format'));
646         $qo->incorrectfeedback['files'] = array();
648         $files = $this->getpath($question, array('#', 'incorrectfeedback', '0', '#', 'file'), array());
649         foreach ($files as $file) {
650             $data = new stdclass();
651             $data->content = $file['#'];
652             $data->name = $file['@']['name'];
653             $data->encoding = $file['@']['encoding'];
654             $qo->incorrectfeedback['files'][] = $data;
655         }
657         $qo->unitgradingtype = $this->getpath($question, array('#','unitgradingtype',0,'#'), 0 );
658         $qo->unitpenalty = $this->getpath($question, array('#','unitpenalty',0,'#'), 0 );
659         $qo->showunits = $this->getpath($question, array('#','showunits',0,'#'), 0 );
660         $qo->unitsleft = $this->getpath($question, array('#','unitsleft',0,'#'), 0 );
661 //        $qo->instructions = $this->getpath( $question, array('#','instructions',0,'#','text',0,'#'), '', true );
662         if (!empty($instructions)) {
663             $qo->instructions = array();
664             $qo->instructions['text'] = $this->getpath($instructions,
665                     array('0', '#', 'text', '0', '#'), '', true);
666             $qo->instructions['format'] = $this->trans_format($this->getpath($instructions,
667                     array('0', '@', 'format'), 'moodle_auto_format'));
668             $files = $this->getpath($instructions,
669                     array('0', '#', 'file'), array());
670             $qo->instructionsfiles = array();
671             foreach ($files as $file) {
672                 $data = new stdclass;
673                 $data->content = $file['#'];
674                 $data->encoding = $file['@']['encoding'];
675                 $data->name = $file['@']['name'];
676                 $qo->instructionsfiles[] = $data;
677             }
678         }
680         $files = $this->getpath($question, array('#', 'instructions', 0, '#', 'file', 0, '@'), '', false);
682         // get answers array
683         // echo "<pre> question";print_r($question);echo "</pre>";
684         $answers = $question['#']['answer'];
685         $qo->answers = array();
686         $qo->feedback = array();
687         $qo->fraction = array();
688         $qo->tolerance = array();
689         $qo->tolerancetype = array();
690         $qo->correctanswerformat = array();
691         $qo->correctanswerlength = array();
692         $qo->feedback = array();
693         foreach ($answers as $answer) {
694             $ans = $this->import_answer($answer);
695             // answer outside of <text> is deprecated
696             if (empty($ans->answer['text'])) {
697                 $ans->answer['text'] = '*';
698             }
699             $qo->answers[] = $ans->answer;
700             $qo->feedback[] = $ans->feedback;
701             $qo->tolerance[] = $answer['#']['tolerance'][0]['#'];
702             // fraction as a tag is deprecated
703             if (!empty($answer['#']['fraction'][0]['#'])) {
704                 $qo->fraction[] = $answer['#']['fraction'][0]['#'];
705             } else {
706                 $qo->fraction[] = $answer['@']['fraction'] / 100;
707             }
708             $qo->tolerancetype[] = $answer['#']['tolerancetype'][0]['#'];
709             $qo->correctanswerformat[] = $answer['#']['correctanswerformat'][0]['#'];
710             $qo->correctanswerlength[] = $answer['#']['correctanswerlength'][0]['#'];
711         }
712         // get units array
713         $qo->unit = array();
714         if (isset($question['#']['units'][0]['#']['unit'])) {
715             $units = $question['#']['units'][0]['#']['unit'];
716             $qo->multiplier = array();
717             foreach ($units as $unit) {
718                 $qo->multiplier[] = $unit['#']['multiplier'][0]['#'];
719                 $qo->unit[] = $unit['#']['unit_name'][0]['#'];
720             }
721         }
722         $instructions = $this->getpath($question, array('#', 'instructions'), array());
723         if (!empty($instructions)) {
724             $qo->instructions = array();
725             $qo->instructions['text'] = $this->getpath($instructions,
726                     array('0', '#', 'text', '0', '#'), '', true);
727             $qo->instructions['format'] = $this->trans_format($this->getpath($instructions,
728                     array('0', '@', 'format'), 'moodle_auto_format'));
729             $files = $this->getpath($instructions,
730                     array('0', '#', 'file'), array());
731             $qo->instructionsfiles = array();
732             $files = $instructions[0]['#']['file'];
733             foreach ($files as $file) {
734                 $data = new stdclass;
735                 $data->content = $file['#'];
736                 $data->encoding = $file['@']['encoding'];
737                 $data->name = $file['@']['name'];
738                 $qo->instructionsfiles[] = $data;
739             }
740         }
741         $datasets = $question['#']['dataset_definitions'][0]['#']['dataset_definition'];
742         $qo->dataset = array();
743         $qo->datasetindex= 0 ;
744         foreach ($datasets as $dataset) {
745             $qo->datasetindex++;
746             $qo->dataset[$qo->datasetindex] = new stdClass();
747             $qo->dataset[$qo->datasetindex]->status = $this->import_text( $dataset['#']['status'][0]['#']['text']);
748             $qo->dataset[$qo->datasetindex]->name = $this->import_text( $dataset['#']['name'][0]['#']['text']);
749             $qo->dataset[$qo->datasetindex]->type =  $dataset['#']['type'][0]['#'];
750             $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']);
751             $qo->dataset[$qo->datasetindex]->max = $this->import_text( $dataset['#']['maximum'][0]['#']['text']);
752             $qo->dataset[$qo->datasetindex]->min = $this->import_text( $dataset['#']['minimum'][0]['#']['text']);
753             $qo->dataset[$qo->datasetindex]->length = $this->import_text( $dataset['#']['decimals'][0]['#']['text']);
754             $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']);
755             $qo->dataset[$qo->datasetindex]->itemcount = $dataset['#']['itemcount'][0]['#'];
756             $qo->dataset[$qo->datasetindex]->datasetitem = array();
757             $qo->dataset[$qo->datasetindex]->itemindex = 0;
758             $qo->dataset[$qo->datasetindex]->number_of_items=$dataset['#']['number_of_items'][0]['#'];
759             $datasetitems = $dataset['#']['dataset_items'][0]['#']['dataset_item'];
760             foreach ($datasetitems as $datasetitem) {
761                 $qo->dataset[$qo->datasetindex]->itemindex++;
762                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex] = new stdClass();
763                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->itemnumber =  $datasetitem['#']['number'][0]['#']; //[0]['#']['number'][0]['#'] ; // [0]['numberitems'] ;//['#']['number'][0]['#'];// $datasetitems['#']['number'][0]['#'];
764                 $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->value = $datasetitem['#']['value'][0]['#'] ;//$datasetitem['#']['value'][0]['#'];
765             }
766         }
768         // echo "<pre>loaded qo";print_r($qo);echo "</pre>";
769         return $qo;
770     }
772     /**
773      * this is not a real question type. It's a dummy type used
774      * to specify the import category
775      * format is:
776      * <question type="category">
777      *     <category>tom/dick/harry</category>
778      * </question>
779      */
780     function import_category( $question ) {
781         $qo = new stdClass;
782         $qo->qtype = 'category';
783         $qo->category = $this->import_text($question['#']['category'][0]['#']['text']);
784         return $qo;
785     }
787     /**
788      * parse the array of lines into an array of questions
789      * this *could* burn memory - but it won't happen that much
790      * so fingers crossed!
791      * @param array lines array of lines from the input file
792      * @return array (of objects) question objects
793      */
794     function readquestions($lines) {
795         // we just need it as one big string
796         $text = implode($lines, " ");
797         unset($lines);
799         // this converts xml to big nasty data structure
800         // the 0 means keep white space as it is (important for markdown format)
801         // print_r it if you want to see what it looks like!
802         $xml = xmlize($text, 0);
804         // set up array to hold all our questions
805         $questions = array();
807         // iterate through questions
808         foreach ($xml['quiz']['#']['question'] as $question) {
809             $question_type = $question['@']['type'];
810             $questiontype = get_string( 'questiontype','quiz',$question_type );
812             if ($question_type=='multichoice') {
813                 $qo = $this->import_multichoice( $question );
814             }
815             elseif ($question_type=='truefalse') {
816                 $qo = $this->import_truefalse( $question );
817             }
818             elseif ($question_type=='shortanswer') {
819                 $qo = $this->import_shortanswer( $question );
820             }
821             elseif ($question_type=='numerical') {
822                 $qo = $this->import_numerical( $question );
823             }
824             elseif ($question_type=='description') {
825                 $qo = $this->import_description( $question );
826             }
827             elseif ($question_type=='matching') {
828                 $qo = $this->import_matching( $question );
829             }
830             elseif ($question_type=='cloze') {
831                 $qo = $this->import_multianswer( $question );
832             }
833             elseif ($question_type=='essay') {
834                 $qo = $this->import_essay( $question );
835             }
836             elseif ($question_type=='calculated') {
837                 $qo = $this->import_calculated( $question,CALCULATED  );
838             }
839             elseif ($question_type=='calculatedsimple') {
840                 $qo = $this->import_calculated( $question,CALCULATEDMULTI  );
841                 $qo->qtype = CALCULATEDSIMPLE ;
842             }
843             elseif ($question_type=='calculatedmulti') {
844                 $qo = $this->import_calculated( $question,CALCULATEDMULTI );
845                 $qo->qtype = CALCULATEDMULTI ;
846             }
847             elseif ($question_type=='category') {
848                 $qo = $this->import_category( $question );
849             }
850             else {
851                 // try for plugin support
852                 // no default question, as the plugin can call
853                 // import_headers() itself if it wants to
854                 if (!$qo = $this->try_importing_using_qtypes( $question, null, null, $question_type)) {
855                     $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type );
856                     $this->error( $notsupported );
857                     $qo = null;
858                 }
859             }
861             // stick the result in the $questions array
862             if ($qo) {
863                 $questions[] = $qo;
864             }
865         }
866         return $questions;
867     }
869     // EXPORT FUNCTIONS START HERE
871     function export_file_extension() {
872         return '.xml';
873     }
875     /**
876      * Turn the internal question code into a human readable form
877      * (The code used to be numeric, but this remains as some of
878      * the names don't match the new internal format)
879      * @param mixed type_id Internal code
880      * @return string question type string
881      */
882     function get_qtype( $type_id ) {
883         switch( $type_id ) {
884         case TRUEFALSE:
885             $name = 'truefalse';
886             break;
887         case MULTICHOICE:
888             $name = 'multichoice';
889             break;
890         case SHORTANSWER:
891             $name = 'shortanswer';
892             break;
893         case NUMERICAL:
894             $name = 'numerical';
895             break;
896         case MATCH:
897             $name = 'matching';
898             break;
899         case DESCRIPTION:
900             $name = 'description';
901             break;
902         case MULTIANSWER:
903             $name = 'cloze';
904             break;
905         case ESSAY:
906             $name = 'essay';
907             break;
908         case CALCULATED:
909             $name = 'calculated';
910             break;
911         case CALCULATEDSIMPLE:
912             $name = 'calculatedsimple';
913             break;
914         case CALCULATEDMULTI:
915             $name = 'calculatedmulti';
916             break;
917         default:
918             $name = false;
919         }
920         return $name;
921     }
923     /**
924      * Convert internal Moodle text format code into
925      * human readable form
926      * @param int id internal code
927      * @return string format text
928      */
929     function get_format( $id ) {
930         switch( $id ) {
931         case 0:
932             $name = "moodle_auto_format";
933             break;
934         case 1:
935             $name = "html";
936             break;
937         case 2:
938             $name = "plain_text";
939             break;
940         case 3:
941             $name = "wiki_like";
942             break;
943         case 4:
944             $name = "markdown";
945             break;
946         default:
947             $name = "unknown";
948         }
949         return $name;
950     }
952     /**
953      * Convert internal single question code into
954      * human readable form
955      * @param int id single question code
956      * @return string single question string
957      */
958     function get_single( $id ) {
959         switch( $id ) {
960         case 0:
961             $name = "false";
962             break;
963         case 1:
964             $name = "true";
965             break;
966         default:
967             $name = "unknown";
968         }
969         return $name;
970     }
972     /**
973      * generates <text></text> tags, processing raw text therein
974      * @param int ilev the current indent level
975      * @param boolean short stick it on one line
976      * @return string formatted text
977      */
978     function writetext($raw, $ilev=0, $short=true) {
979         $indent = str_repeat( "  ",$ilev );
981         // if required add CDATA tags
982         if (!empty($raw) and (htmlspecialchars($raw)!=$raw)) {
983             $raw = "<![CDATA[$raw]]>";
984         }
986         if ($short) {
987             $xml = "$indent<text>$raw</text>\n";
988         }
989         else {
990             $xml = "$indent<text>\n$raw\n$indent</text>\n";
991         }
993         return $xml;
994     }
996     function presave_process( $content ) {
997     // override method to allow us to add xml headers and footers
999         // add the xml headers and footers
1000         $content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
1001                        "<quiz>\n" .
1002                        $content . "\n" .
1003                        "</quiz>";
1004         return $content;
1005     }
1007     /**
1008      * Turns question into an xml segment
1009      * @param object question object
1010      * @param int context id
1011      * @return string xml segment
1012      */
1013     function writequestion($question) {
1014         global $CFG, $QTYPES, $OUTPUT;
1016         $fs = get_file_storage();
1017         $contextid = $question->contextid;
1018         // initial string;
1019         $expout = "";
1021         // add comment
1022         $expout .= "\n\n<!-- question: $question->id  -->\n";
1024         // check question type
1025         if (!$question_type = $this->get_qtype( $question->qtype )) {
1026             // must be a plugin then, so just accept the name supplied
1027             $question_type = $question->qtype;
1028         }
1030         // add opening tag
1031         // generates specific header for Cloze and category type question
1032         if ($question->qtype == 'category') {
1033             $categorypath = $this->writetext( $question->category );
1034             $expout .= "  <question type=\"category\">\n";
1035             $expout .= "    <category>\n";
1036             $expout .= "        $categorypath\n";
1037             $expout .= "    </category>\n";
1038             $expout .= "  </question>\n";
1039             return $expout;
1040         } elseif ($question->qtype != MULTIANSWER) {
1041             // for all question types except Close
1042             $name_text = $this->writetext($question->name);
1043             $qtformat = $this->get_format($question->questiontextformat);
1044             $generalfeedbackformat = $this->get_format($question->generalfeedbackformat);
1046             $question_text = $this->writetext($question->questiontext);
1047             $question_text_files = $this->writefiles($question->questiontextfiles);
1049             $generalfeedback = $this->writetext($question->generalfeedback);
1050             $generalfeedback_files = $this->writefiles($question->generalfeedbackfiles);
1052             $expout .= "  <question type=\"$question_type\">\n";
1053             $expout .= "    <name>$name_text</name>\n";
1054             $expout .= "    <questiontext format=\"$qtformat\">\n";
1055             $expout .= $question_text;
1056             $expout .= $question_text_files;
1057             $expout .= "    </questiontext>\n";
1058             $expout .= "    <generalfeedback format=\"$generalfeedbackformat\">\n";
1059             $expout .= $generalfeedback;
1060             $expout .= $generalfeedback_files;
1061             $expout .= "    </generalfeedback>\n";
1062             $expout .= "    <defaultgrade>{$question->defaultgrade}</defaultgrade>\n";
1063             $expout .= "    <penalty>{$question->penalty}</penalty>\n";
1064             $expout .= "    <hidden>{$question->hidden}</hidden>\n";
1065         } else {
1066             // for Cloze type only
1067             $name_text = $this->writetext( $question->name );
1068             $question_text = $this->writetext( $question->questiontext );
1069             $generalfeedback = $this->writetext( $question->generalfeedback );
1070             $expout .= "  <question type=\"$question_type\">\n";
1071             $expout .= "    <name>$name_text</name>\n";
1072             $expout .= "    <questiontext>\n";
1073             $expout .= $question_text;
1074             $expout .= "    </questiontext>\n";
1075             $expout .= "    <generalfeedback>\n";
1076             $expout .= $generalfeedback;
1077             $expout .= "    </generalfeedback>\n";
1078         }
1080         if (!empty($question->options->shuffleanswers)) {
1081             $expout .= "    <shuffleanswers>{$question->options->shuffleanswers}</shuffleanswers>\n";
1082         }
1083         else {
1084             $expout .= "    <shuffleanswers>0</shuffleanswers>\n";
1085         }
1087         // output depends on question type
1088         switch($question->qtype) {
1089         case 'category':
1090             // not a qtype really - dummy used for category switching
1091             break;
1092         case TRUEFALSE:
1093             foreach ($question->options->answers as $answer) {
1094                 $fraction_pc = round( $answer->fraction * 100 );
1095                 if ($answer->id == $question->options->trueanswer) {
1096                     $answertext = 'true';
1097                 } else {
1098                     $answertext = 'false';
1099                 }
1100                 $expout .= "    <answer fraction=\"$fraction_pc\">\n";
1101                 $expout .= $this->writetext($answertext, 3) . "\n";
1102                 $feedbackformat = $this->get_format($answer->feedbackformat);
1103                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1104                 $expout .= $this->writetext($answer->feedback,4,false);
1105                 $expout .= $this->writefiles($answer->feedbackfiles);
1106                 $expout .= "      </feedback>\n";
1107                 $expout .= "    </answer>\n";
1108             }
1109             break;
1110         case MULTICHOICE:
1111             $expout .= "    <single>".$this->get_single($question->options->single)."</single>\n";
1112             $expout .= "    <shuffleanswers>".$this->get_single($question->options->shuffleanswers)."</shuffleanswers>\n";
1114             $textformat = $this->get_format($question->options->correctfeedbackformat);
1115             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'correctfeedback', $question->id);
1116             $expout .= "    <correctfeedback format=\"$textformat\">\n";
1117             $expout .= $this->writetext($question->options->correctfeedback, 3);
1118             $expout .= $this->writefiles($files);
1119             $expout .= "    </correctfeedback>\n";
1121             $textformat = $this->get_format($question->options->partiallycorrectfeedbackformat);
1122             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'partiallycorrectfeedback', $question->id);
1123             $expout .= "    <partiallycorrectfeedback format=\"$textformat\">\n";
1124             $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3);
1125             $expout .= $this->writefiles($files);
1126             $expout .= "    </partiallycorrectfeedback>\n";
1128             $textformat = $this->get_format($question->options->incorrectfeedbackformat);
1129             $files = $fs->get_area_files($contextid, 'qtype_multichoice', 'incorrectfeedback', $question->id);
1130             $expout .= "    <incorrectfeedback format=\"$textformat\">\n";
1131             $expout .= $this->writetext($question->options->incorrectfeedback, 3);
1132             $expout .= $this->writefiles($files);
1133             $expout .= "    </incorrectfeedback>\n";
1135             $expout .= "    <answernumbering>".$this->writetext($question->options->answernumbering, 3)."</answernumbering>\n";
1136             foreach($question->options->answers as $answer) {
1137                 $percent = $answer->fraction * 100;
1138                 $expout .= "      <answer fraction=\"$percent\">\n";
1139                 $expout .= $this->writetext($answer->answer,4,false);
1140                 $feedbackformat = $this->get_format($answer->feedbackformat);
1141                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1142                 $expout .= $this->writetext($answer->feedback,5,false);
1143                 $expout .= $this->writefiles($answer->feedbackfiles);
1144                 $expout .= "      </feedback>\n";
1145                 $expout .= "    </answer>\n";
1146                 }
1147             break;
1148         case SHORTANSWER:
1149             $expout .= "    <usecase>{$question->options->usecase}</usecase>\n ";
1150             foreach($question->options->answers as $answer) {
1151                 $percent = 100 * $answer->fraction;
1152                 $expout .= "    <answer fraction=\"$percent\">\n";
1153                 $expout .= $this->writetext( $answer->answer,3,false );
1154                 $feedbackformat = $this->get_format($answer->feedbackformat);
1155                 $expout .= "      <feedback format=\"$feedbackformat\">\n";
1156                 $expout .= $this->writetext($answer->feedback);
1157                 $expout .= $this->writefiles($answer->feedbackfiles);
1158                 $expout .= "      </feedback>\n";
1159                 $expout .= "    </answer>\n";
1160             }
1161             break;
1162         case NUMERICAL:
1163             foreach ($question->options->answers as $answer) {
1164                 $tolerance = $answer->tolerance;
1165                 $percent = 100 * $answer->fraction;
1166                 $expout .= "<answer fraction=\"$percent\">\n";
1167                 // <text> tags are an added feature, old filed won't have them
1168                 $expout .= "    <text>{$answer->answer}</text>\n";
1169                 $expout .= "    <tolerance>$tolerance</tolerance>\n";
1170                 $feedbackformat = $this->get_format($answer->feedbackformat);
1171                 $expout .= "    <feedback format=\"$feedbackformat\">\n";
1172                 $expout .= $this->writetext($answer->feedback);
1173                 $expout .= $this->writefiles($answer->feedbackfiles);
1174                 $expout .= "    </feedback>\n";
1175                 // fraction tag is deprecated
1176                 // $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
1177                 $expout .= "</answer>\n";
1178             }
1180             $units = $question->options->units;
1181             if (count($units)) {
1182                 $expout .= "<units>\n";
1183                 foreach ($units as $unit) {
1184                     $expout .= "  <unit>\n";
1185                     $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
1186                     $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
1187                     $expout .= "  </unit>\n";
1188                 }
1189                 $expout .= "</units>\n";
1190             }
1191             if (isset($question->options->unitgradingtype)) {
1192                 $expout .= "    <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n";
1193             }
1194             if (isset($question->options->unitpenalty)) {
1195                 $expout .= "    <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1196             }
1197             if (isset($question->options->showunits)) {
1198                 $expout .= "    <showunits>{$question->options->showunits}</showunits>\n";
1199             }
1200             if (isset($question->options->unitsleft)) {
1201                 $expout .= "    <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1202             }
1203             if (!empty($question->options->instructionsformat)) {
1204                 $textformat = $this->get_format($question->options->instructionsformat);
1205                 $files = $fs->get_area_files($contextid, 'qtype_numerical', 'instruction', $question->id);
1206                 $expout .= "    <instructions format=\"$textformat\">\n";
1207                 $expout .= $this->writetext($question->options->instructions, 3);
1208                 $expout .= $this->writefiles($files);
1209                 $expout .= "    </instructions>\n";
1210             }
1211             break;
1212         case MATCH:
1213             foreach($question->options->subquestions as $subquestion) {
1214                 $files = $fs->get_area_files($contextid, 'qtype_match', 'subquestion', $subquestion->id);
1215                 $textformat = $this->get_format($subquestion->questiontextformat);
1216                 $expout .= "<subquestion format=\"$textformat\">\n";
1217                 $expout .= $this->writetext($subquestion->questiontext);
1218                 $expout .= $this->writefiles($files);
1219                 $expout .= "<answer>";
1220                 $expout .= $this->writetext($subquestion->answertext);
1221                 $expout .= "</answer>\n";
1222                 $expout .= "</subquestion>\n";
1223             }
1224             break;
1225         case DESCRIPTION:
1226             // nothing more to do for this type
1227             break;
1228         case MULTIANSWER:
1229             $a_count=1;
1230             foreach($question->options->questions as $question) {
1231                 $thispattern = preg_quote("{#".$a_count."}"); //TODO: is this really necessary?
1232                 $thisreplace = $question->questiontext;
1233                 $expout=preg_replace("~$thispattern~", $thisreplace, $expout );
1234                 $a_count++;
1235             }
1236         break;
1237         case ESSAY:
1238             if (!empty($question->options->answers)) {
1239                 foreach ($question->options->answers as $answer) {
1240                     $percent = 100 * $answer->fraction;
1241                     $expout .= "<answer fraction=\"$percent\">\n";
1242                     $feedbackformat = $this->get_format($answer->feedbackformat);
1243                     $expout .= "    <feedback format=\"$feedbackformat\">\n";
1244                     $expout .= $this->writetext($answer->feedback);
1245                     $expout .= $this->writefiles($answer->feedbackfiles);
1246                     $expout .= "    </feedback>\n";
1247                     // fraction tag is deprecated
1248                     // $expout .= "    <fraction>{$answer->fraction}</fraction>\n";
1249                     $expout .= "</answer>\n";
1250                 }
1251             }
1252             break;
1253         case CALCULATED:
1254         case CALCULATEDSIMPLE:
1255         case CALCULATEDMULTI:
1256             $expout .= "    <synchronize>{$question->options->synchronize}</synchronize>\n";
1257             $expout .= "    <single>{$question->options->single}</single>\n";
1258             $expout .= "    <answernumbering>{$question->options->answernumbering}</answernumbering>\n";
1259             $expout .= "    <shuffleanswers>".$this->writetext($question->options->shuffleanswers, 3)."</shuffleanswers>\n";
1261             $component = 'qtype_' . $question->qtype;
1262             $files = $fs->get_area_files($contextid, $component, 'correctfeedback', $question->id);
1263             $expout .= "    <correctfeedback>\n";
1264             $expout .= $this->writetext($question->options->correctfeedback, 3);
1265             $expout .= $this->writefiles($files);
1266             $expout .= "    </correctfeedback>\n";
1268             $files = $fs->get_area_files($contextid, $component, 'partiallycorrectfeedback', $question->id);
1269             $expout .= "    <partiallycorrectfeedback>\n";
1270             $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3);
1271             $expout .= $this->writefiles($files);
1272             $expout .= "    </partiallycorrectfeedback>\n";
1274             $files = $fs->get_area_files($contextid, $component, 'incorrectfeedback', $question->id);
1275             $expout .= "    <incorrectfeedback>\n";
1276             $expout .= $this->writetext($question->options->incorrectfeedback, 3);
1277             $expout .= $this->writefiles($files);
1278             $expout .= "    </incorrectfeedback>\n";
1280             foreach ($question->options->answers as $answer) {
1281                 $tolerance = $answer->tolerance;
1282                 $tolerancetype = $answer->tolerancetype;
1283                 $correctanswerlength= $answer->correctanswerlength ;
1284                 $correctanswerformat= $answer->correctanswerformat;
1285                 $percent = 100 * $answer->fraction;
1286                 $expout .= "<answer fraction=\"$percent\">\n";
1287                 // "<text/>" tags are an added feature, old files won't have them
1288                 $expout .= "    <text>{$answer->answer}</text>\n";
1289                 $expout .= "    <tolerance>$tolerance</tolerance>\n";
1290                 $expout .= "    <tolerancetype>$tolerancetype</tolerancetype>\n";
1291                 $expout .= "    <correctanswerformat>$correctanswerformat</correctanswerformat>\n";
1292                 $expout .= "    <correctanswerlength>$correctanswerlength</correctanswerlength>\n";
1293                 $feedbackformat = $this->get_format($answer->feedbackformat);
1294                 $expout .= "    <feedback format=\"$feedbackformat\">\n";
1295                 $expout .= $this->writetext($answer->feedback);
1296                 $expout .= $this->writefiles($answer->feedbackfiles);
1297                 $expout .= "    </feedback>\n";
1298                 $expout .= "</answer>\n";
1299             }
1300             if (isset($question->options->unitgradingtype)) {
1301                 $expout .= "    <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n";
1302             }
1303             if (isset($question->options->unitpenalty)) {
1304                 $expout .= "    <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1305             }
1306             if (isset($question->options->showunits)) {
1307                 $expout .= "    <showunits>{$question->options->showunits}</showunits>\n";
1308             }
1309             if (isset($question->options->unitsleft)) {
1310                 $expout .= "    <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1311             }
1313             if (isset($question->options->instructionsformat)) {
1314                 $textformat = $this->get_format($question->options->instructionsformat);
1315                 $files = $fs->get_area_files($contextid, $component, 'instruction', $question->id);
1316                 $expout .= "    <instructions format=\"$textformat\">\n";
1317                 $expout .= $this->writetext($question->options->instructions, 3);
1318                 $expout .= $this->writefiles($files);
1319                 $expout .= "    </instructions>\n";
1320             }
1322             if (isset($question->options->units)) {
1323                 $units = $question->options->units;
1324                 if (count($units)) {
1325                     $expout .= "<units>\n";
1326                     foreach ($units as $unit) {
1327                         $expout .= "  <unit>\n";
1328                         $expout .= "    <multiplier>{$unit->multiplier}</multiplier>\n";
1329                         $expout .= "    <unit_name>{$unit->unit}</unit_name>\n";
1330                         $expout .= "  </unit>\n";
1331                     }
1332                     $expout .= "</units>\n";
1333                 }
1334             }
1335             //The tag $question->export_process has been set so we get all the data items in the database
1336             //   from the function $QTYPES['calculated']->get_question_options(&$question);
1337             //  calculatedsimple defaults to calculated
1338             if( isset($question->options->datasets)&&count($question->options->datasets)){// there should be
1339                 $expout .= "<dataset_definitions>\n";
1340                 foreach ($question->options->datasets as $def) {
1341                     $expout .= "<dataset_definition>\n";
1342                     $expout .= "    <status>".$this->writetext($def->status)."</status>\n";
1343                     $expout .= "    <name>".$this->writetext($def->name)."</name>\n";
1344                     if ( $question->qtype == CALCULATED){
1345                         $expout .= "    <type>calculated</type>\n";
1346                     }else {
1347                         $expout .= "    <type>calculatedsimple</type>\n";
1348                     }
1349                     $expout .= "    <distribution>".$this->writetext($def->distribution)."</distribution>\n";
1350                     $expout .= "    <minimum>".$this->writetext($def->minimum)."</minimum>\n";
1351                     $expout .= "    <maximum>".$this->writetext($def->maximum)."</maximum>\n";
1352                     $expout .= "    <decimals>".$this->writetext($def->decimals)."</decimals>\n";
1353                     $expout .= "    <itemcount>$def->itemcount</itemcount>\n";
1354                     if ($def->itemcount > 0 ) {
1355                         $expout .= "    <dataset_items>\n";
1356                         foreach ($def->items as $item ){
1357                               $expout .= "        <dataset_item>\n";
1358                               $expout .= "           <number>".$item->itemnumber."</number>\n";
1359                               $expout .= "           <value>".$item->value."</value>\n";
1360                               $expout .= "        </dataset_item>\n";
1361                         }
1362                         $expout .= "    </dataset_items>\n";
1363                         $expout .= "    <number_of_items>".$def-> number_of_items."</number_of_items>\n";
1364                      }
1365                     $expout .= "</dataset_definition>\n";
1366                 }
1367                 $expout .= "</dataset_definitions>\n";
1368             }
1369             break;
1370         default:
1371             // try support by optional plugin
1372             if (!$data = $this->try_exporting_using_qtypes( $question->qtype, $question )) {
1373                 echo $OUTPUT->notification( get_string( 'unsupportedexport','qformat_xml',$QTYPES[$question->qtype]->local_name() ) );
1374             }
1375             $expout .= $data;
1376         }
1378         // close the question tag
1379         $expout .= "</question>\n";
1381         // XXX: debuging
1382         //echo '<textarea cols=100 rows=20>';
1383         //echo $expout;
1384         //echo '</textarea>';
1386         return $expout;
1387     }