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