2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Code for exporting questions as Moodle XML.
22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') ||
die();
29 require_once($CFG->dirroot
. '/question/format.php');
30 require_once($CFG->libdir
. '/xmlize.php');
34 * Importer for Moodle XML question format.
36 * See http://docs.moodle.org/en/Moodle_XML_format for a description of the format.
38 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 class qformat_xml
extends qformat_default
{
43 public function provide_import() {
47 public function provide_export() {
51 public function mime_type() {
52 return 'application/xml';
55 // IMPORT FUNCTIONS START HERE
58 * Translate human readable format name
59 * into internal Moodle code number
60 * @param string name format name from xml file
61 * @return int Moodle format code
63 protected function trans_format($name) {
66 if ($name == 'moodle_auto_format') {
68 } else if ($name == 'html') {
70 } else if ($name == 'plain_text') {
72 } else if ($name == 'wiki_like') {
74 } else if ($name == 'markdown') {
75 return FORMAT_MARKDOWN
;
77 return 0; // or maybe warning required
82 * Translate human readable single answer option
83 * to internal code number
84 * @param string name true/false
85 * @return int internal code number
87 public function trans_single($name) {
89 if ($name == "false" ||
!$name) {
97 * process text string from xml file
98 * @param array $text bit of xml tree after ['text']
99 * @return string processed text.
101 public function import_text($text) {
102 // quick sanity check
106 $data = $text[0]['#'];
111 * return the value of a node, given a path to the node
112 * if it doesn't exist return the default value
113 * @param array xml data to read
114 * @param array path path to node expressed as array
115 * @param mixed default
116 * @param bool istext process as text
117 * @param string error if set value must exist, return false and issue message if not
118 * @return mixed value
120 public function getpath($xml, $path, $default, $istext=false, $error='') {
121 foreach ($path as $index) {
122 if (!isset($xml[$index])) {
123 if (!empty($error)) {
124 $this->error($error);
135 if (!is_string($xml)) {
136 $this->error(get_string('invalidxml', 'qformat_xml'));
146 * import parts of question common to all types
147 * @param $question array question question array from xml tree
148 * @return object question object
150 public function import_headers($question) {
153 // get some error strings
154 $error_noname = get_string('xmlimportnoname', 'qformat_xml');
155 $error_noquestion = get_string('xmlimportnoquestion', 'qformat_xml');
157 // this routine initialises the question object
158 $qo = $this->defaultquestion();
161 $qo->name
= $this->getpath($question,
162 array('#', 'name', 0, '#', 'text', 0, '#'), '', true,
163 get_string('xmlimportnoname', 'qformat_xml'));
164 $qo->questiontext
= $this->getpath($question,
165 array('#', 'questiontext', 0, '#', 'text', 0, '#'), '', true);
166 $qo->questiontextformat
= $this->trans_format($this->getpath(
167 $question, array('#', 'questiontext', 0, '@', 'format'), ''));
169 $qo->questiontextfiles
= $this->import_files($this->getpath($question,
170 array('#', 'questiontext', 0, '#', 'file'), array(), false));
172 // Backwards compatibility, deal with the old image tag.
173 $filedata = $this->getpath($question, array('#', 'image_base64', '0', '#'), null, false);
174 $filename = $this->getpath($question, array('#', 'image', '0', '#'), null, false);
175 if ($filedata && $filename) {
176 $data = new stdClass();
177 $data->content
= $filedata;
178 $data->encoding
= 'base64';
179 $data->name
= $filename;
180 $qo->questiontextfiles
[] = $data;
181 $qo->questiontext
.= ' <img src="@@PLUGINFILE@@/' . $filename . '" />';
184 // restore files in generalfeedback
185 $qo->generalfeedback
= $this->getpath($question,
186 array('#', 'generalfeedback', 0, '#', 'text', 0, '#'), $qo->generalfeedback
, true);
187 $qo->generalfeedbackfiles
= array();
188 $qo->generalfeedbackformat
= $this->trans_format($this->getpath($question,
189 array('#', 'generalfeedback', 0, '@', 'format'), 'moodle_auto_format'));
190 $qo->generalfeedbackfiles
= $this->import_files($this->getpath($question,
191 array('#', 'generalfeedback', 0, '#', 'file'), array(), false));
193 $qo->defaultmark
= $this->getpath($question,
194 array('#', 'defaultgrade', 0, '#'), $qo->defaultmark
);
195 $qo->penalty
= $this->getpath($question,
196 array('#', 'penalty', 0, '#'), $qo->penalty
);
198 // Fix problematic rounding from old files:
199 if (abs($qo->penalty
- 0.3333333) < 0.005) {
200 $qo->penalty
= 0.3333333;
203 // Read the question tags.
204 if (!empty($CFG->usetags
) && array_key_exists('tags', $question['#'])
205 && !empty($question['#']['tags'][0]['#']['tag'])) {
206 require_once($CFG->dirroot
.'/tag/lib.php');
208 foreach ($question['#']['tags'][0]['#']['tag'] as $tagdata) {
209 $qo->tags
[] = $this->getpath($tagdata, array('#', 'text', 0, '#'), '', true);
217 * Import the common parts of a single answer
218 * @param array answer xml tree for single answer
219 * @return object answer object
221 public function import_answer($answer) {
222 $fraction = $this->getpath($answer, array('@', 'fraction'), 0);
223 $answertext = $this->getpath($answer, array('#', 'text', 0, '#'), '', true);
224 $answerformat = $this->trans_format($this->getpath($answer,
225 array('@', 'format'), 'moodle_auto_format'));
226 $answerfiles = $this->import_files($this->getpath($answer,
227 array('#', 'file'), array()));
229 $feedbacktext = $this->getpath($answer,
230 array('#', 'feedback', 0, '#', 'text', 0, '#'), '', true);
231 $feedbackformat = $this->trans_format($this->getpath($answer,
232 array('#', 'feedback', 0, '@', 'format'), 'moodle_auto_format'));
233 $feedbackfiles = $this->import_files($this->getpath($answer,
234 array('#', 'feedback', 0, '#', 'file'), array()));
236 $ans = new stdClass();
238 $ans->answer
= array();
239 $ans->answer
['text'] = $answertext;
240 $ans->answer
['format'] = $answerformat;
241 $ans->answer
['files'] = $answerfiles;
243 $ans->feedback
= array();
244 $ans->feedback
['text'] = $feedbacktext;
245 $ans->feedback
['format'] = $feedbackformat;
246 $ans->feedback
['files'] = $feedbackfiles;
248 $ans->fraction
= $fraction / 100;
253 * Import the common overall feedback fields.
254 * @param object $question the part of the XML relating to this question.
255 * @param object $qo the question data to add the fields to.
256 * @param bool $withshownumpartscorrect include the shownumcorrect field.
258 public function import_combined_feedback($qo, $questionxml, $withshownumpartscorrect = false) {
259 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback');
260 foreach ($fields as $field) {
262 $text['text'] = $this->getpath($questionxml,
263 array('#', $field, 0, '#', 'text', 0, '#'), '', true);
264 $text['format'] = $this->trans_format($this->getpath($questionxml,
265 array('#', $field, 0, '@', 'format'), 'moodle_auto_format'));
266 $text['files'] = $this->import_files($this->getpath($questionxml,
267 array('#', $field, 0, '#', 'file'), array(), false));
272 if ($withshownumpartscorrect) {
273 $qo->shownumcorrect
= array_key_exists('shownumcorrect', $questionxml['#']);
275 // Backwards compatibility:
276 if (array_key_exists('correctresponsesfeedback', $questionxml['#'])) {
277 $qo->shownumcorrect
= $this->trans_single($this->getpath($questionxml,
278 array('#', 'correctresponsesfeedback', 0, '#'), 1));
284 * Import a question hint
285 * @param array $hintxml hint xml fragment.
286 * @return object hint for storing in the database.
288 public function import_hint($hintxml) {
289 if (array_key_exists('hintcontent', $hintxml['#'])) {
290 // Backwards compatibility:
292 $hint = new stdClass();
293 $hint->hint
= array('format' => FORMAT_HTML
, 'files' => array());
294 $hint->hint
['text'] = $this->getpath($hintxml,
295 array('#', 'hintcontent', 0, '#', 'text', 0, '#'), '', true);
296 $hint->shownumcorrect
= $this->getpath($hintxml,
297 array('#', 'statenumberofcorrectresponses', 0, '#'), 0);
298 $hint->clearwrong
= $this->getpath($hintxml,
299 array('#', 'clearincorrectresponses', 0, '#'), 0);
300 $hint->options
= $this->getpath($hintxml,
301 array('#', 'showfeedbacktoresponses', 0, '#'), 0);
306 $hint->hint
= $this->getpath($hintxml,
307 array('#', 'text', 0, '#'), '', true);
309 $hinttext['text'] = $this->getpath($hintxml,
310 array('#', 'text', 0, '#'), '', true);
311 $hinttext['format'] = $this->trans_format($this->getpath($hintxml,
312 array('@', 'format'), 'moodle_auto_format'));
314 $hinttext['files'] = $this->import_files($this->getpath($hintxml,
315 array('#', 'file'), array(), false));
317 $hint = new stdClass();
318 $hint->hint
= $hinttext;
319 $hint->shownumcorrect
= array_key_exists('shownumcorrect', $hintxml['#']);
320 $hint->clearwrong
= array_key_exists('clearwrong', $hintxml['#']);
321 $hint->options
= $this->getpath($hintxml, array('#', 'options', 0, '#'), '', true);
327 * Import all the question hints
329 * @param object $qo the question data that is being constructed.
330 * @param array $hintsxml hints xml fragment.
332 public function import_hints($qo, $questionxml, $withparts = false, $withoptions = false) {
333 if (!isset($questionxml['#']['hint'])) {
337 foreach ($questionxml['#']['hint'] as $hintxml) {
338 $hint = $this->import_hint($hintxml);
339 $qo->hint
[] = $hint->hint
;
342 $qo->hintshownumcorrect
[] = $hint->shownumcorrect
;
343 $qo->hintclearwrong
[] = $hint->clearwrong
;
347 $qo->hintoptions
[] = $hint->options
;
353 * Import files from a node in the XML.
354 * @param array $xml an array of <file> nodes from the the parsed XML.
355 * @return array of things representing files - in the form that save_question expects.
357 public function import_files($xml) {
359 foreach ($xml as $file) {
360 $data = new stdClass();
361 $data->content
= $file['#'];
362 $data->encoding
= $file['@']['encoding'];
363 $data->name
= $file['@']['name'];
370 * import multiple choice question
371 * @param array question question array from xml tree
372 * @return object question object
374 public function import_multichoice($question) {
376 $qo = $this->import_headers($question);
378 // 'header' parts particular to multichoice
379 $qo->qtype
= MULTICHOICE
;
380 $single = $this->getpath($question, array('#', 'single', 0, '#'), 'true');
381 $qo->single
= $this->trans_single($single);
382 $shuffleanswers = $this->getpath($question,
383 array('#', 'shuffleanswers', 0, '#'), 'false');
384 $qo->answernumbering
= $this->getpath($question,
385 array('#', 'answernumbering', 0, '#'), 'abc');
386 $qo->shuffleanswers
= $this->trans_single($shuffleanswers);
388 // There was a time on the 1.8 branch when it could output an empty
389 // answernumbering tag, so fix up any found.
390 if (empty($qo->answernumbering
)) {
391 $qo->answernumbering
= 'abc';
394 // Run through the answers
395 $answers = $question['#']['answer'];
397 foreach ($answers as $answer) {
398 $ans = $this->import_answer($answer);
399 $qo->answer
[$acount] = $ans->answer
;
400 $qo->fraction
[$acount] = $ans->fraction
;
401 $qo->feedback
[$acount] = $ans->feedback
;
405 $this->import_combined_feedback($qo, $question, true);
406 $this->import_hints($qo, $question, true);
412 * Import cloze type question
413 * @param array question question array from xml tree
414 * @return object question object
416 public function import_multianswer($questions) {
417 $questiontext = array();
418 $questiontext['text'] = $this->import_text($questions['#']['questiontext'][0]['#']['text']);
419 $questiontext['format'] = '1';
420 $questiontext['itemid'] = '';
421 $qo = qtype_multianswer_extract_question($questiontext);
423 // 'header' parts particular to multianswer
424 $qo->qtype
= MULTIANSWER
;
425 $qo->course
= $this->course
;
426 $qo->generalfeedback
= '';
427 // restore files in generalfeedback
428 $qo->generalfeedback
= $this->getpath($questions,
429 array('#', 'generalfeedback', 0, '#', 'text', 0, '#'), $qo->generalfeedback
, true);
430 $qo->generalfeedbackformat
= $this->trans_format($this->getpath($questions,
431 array('#', 'generalfeedback', 0, '@', 'format'), 'moodle_auto_format'));
432 $qo->generalfeedbackfiles
= $this->import_files($this->getpath($questions,
433 array('#', 'generalfeedback', 0, '#', 'file'), array(), false));
435 if (!empty($questions)) {
436 $qo->name
= $this->import_text($questions['#']['name'][0]['#']['text']);
438 $qo->questiontext
= $qo->questiontext
['text'];
439 $qo->questiontextformat
= '';
441 $this->import_hints($qo, $question, true);
447 * Import true/false type question
448 * @param array question question array from xml tree
449 * @return object question object
451 public function import_truefalse($question) {
454 $qo = $this->import_headers($question);
456 // 'header' parts particular to true/false
457 $qo->qtype
= TRUEFALSE
;
459 // In the past, it used to be assumed that the two answers were in the file
460 // true first, then false. Howevever that was not always true. Now, we
461 // try to match on the answer text, but in old exports, this will be a localised
462 // string, so if we don't find true or false, we fall back to the old system.
465 foreach ($question['#']['answer'] as $answer) {
466 $answertext = $this->getpath($answer,
467 array('#', 'text', 0, '#'), '', true);
468 $feedback = $this->getpath($answer,
469 array('#', 'feedback', 0, '#', 'text', 0, '#'), '', true);
470 $feedbackformat = $this->getpath($answer,
471 array('#', 'feedback', 0, '@', 'format'), 'moodle_auto_format');
472 $feedbackfiles = $this->getpath($answer,
473 array('#', 'feedback', 0, '#', 'file'), array());
475 foreach ($feedbackfiles as $file) {
476 $data = new stdClass();
477 $data->content
= $file['#'];
478 $data->encoding
= $file['@']['encoding'];
479 $data->name
= $file['@']['name'];
482 if ($answertext != 'true' && $answertext != 'false') {
483 // Old style file, assume order is true/false.
486 $answertext = 'true';
488 $answertext = 'false';
492 if ($answertext == 'true') {
493 $qo->answer
= ($answer['@']['fraction'] == 100);
494 $qo->correctanswer
= $qo->answer
;
495 $qo->feedbacktrue
= array();
496 $qo->feedbacktrue
['text'] = $feedback;
497 $qo->feedbacktrue
['format'] = $this->trans_format($feedbackformat);
498 $qo->feedbacktrue
['files'] = $files;
500 $qo->answer
= ($answer['@']['fraction'] != 100);
501 $qo->correctanswer
= $qo->answer
;
502 $qo->feedbackfalse
= array();
503 $qo->feedbackfalse
['text'] = $feedback;
504 $qo->feedbackfalse
['format'] = $this->trans_format($feedbackformat);
505 $qo->feedbackfalse
['files'] = $files;
512 $a->questiontext
= $qo->questiontext
;
513 $a->answer
= get_string($qo->correctanswer ?
'true' : 'false', 'qtype_truefalse');
514 echo $OUTPUT->notification(get_string('truefalseimporterror', 'qformat_xml', $a));
517 $this->import_hints($qo, $question);
523 * Import short answer type question
524 * @param array question question array from xml tree
525 * @return object question object
527 public function import_shortanswer($question) {
529 $qo = $this->import_headers($question);
531 // header parts particular to shortanswer
532 $qo->qtype
= SHORTANSWER
;
535 $qo->usecase
= $this->getpath($question, array('#', 'usecase', 0, '#'), $qo->usecase
);
537 // Run through the answers
538 $answers = $question['#']['answer'];
540 foreach ($answers as $answer) {
541 $ans = $this->import_answer($answer);
542 $qo->answer
[$acount] = $ans->answer
['text'];
543 $qo->fraction
[$acount] = $ans->fraction
;
544 $qo->feedback
[$acount] = $ans->feedback
;
548 $this->import_hints($qo, $question);
554 * Import description type question
555 * @param array question question array from xml tree
556 * @return object question object
558 public function import_description($question) {
560 $qo = $this->import_headers($question);
561 // header parts particular to shortanswer
562 $qo->qtype
= DESCRIPTION
;
563 $qo->defaultmark
= 0;
569 * Import numerical type question
570 * @param array question question array from xml tree
571 * @return object question object
573 public function import_numerical($question) {
575 $qo = $this->import_headers($question);
577 // header parts particular to numerical
578 $qo->qtype
= NUMERICAL
;
581 $answers = $question['#']['answer'];
582 $qo->answer
= array();
583 $qo->feedback
= array();
584 $qo->fraction
= array();
585 $qo->tolerance
= array();
586 foreach ($answers as $answer) {
587 // answer outside of <text> is deprecated
588 $obj = $this->import_answer($answer);
589 $qo->answer
[] = $obj->answer
['text'];
590 if (empty($qo->answer
)) {
593 $qo->feedback
[] = $obj->feedback
;
594 $qo->tolerance
[] = $this->getpath($answer, array('#', 'tolerance', 0, '#'), 0);
596 // fraction as a tag is deprecated
597 $fraction = $this->getpath($answer, array('@', 'fraction'), 0) / 100;
598 $qo->fraction
[] = $this->getpath($answer,
599 array('#', 'fraction', 0, '#'), $fraction); // deprecated
602 // Get the units array
604 $units = $this->getpath($question, array('#', 'units', 0, '#', 'unit'), array());
605 if (!empty($units)) {
606 $qo->multiplier
= array();
607 foreach ($units as $unit) {
608 $qo->multiplier
[] = $this->getpath($unit, array('#', 'multiplier', 0, '#'), 1);
609 $qo->unit
[] = $this->getpath($unit, array('#', 'unit_name', 0, '#'), '', true);
612 $qo->unitgradingtype
= $this->getpath($question, array('#', 'unitgradingtype', 0, '#'), 0);
613 $qo->unitpenalty
= $this->getpath($question, array('#', 'unitpenalty', 0, '#'), 0);
614 $qo->showunits
= $this->getpath($question, array('#', 'showunits', 0, '#'), 0);
615 $qo->unitsleft
= $this->getpath($question, array('#', 'unitsleft', 0, '#'), 0);
616 $qo->instructions
['text'] = '';
617 $qo->instructions
['format'] = FORMAT_HTML
;
618 $instructions = $this->getpath($question, array('#', 'instructions'), array());
619 if (!empty($instructions)) {
620 $qo->instructions
= array();
621 $qo->instructions
['text'] = $this->getpath($instructions,
622 array('0', '#', 'text', '0', '#'), '', true);
623 $qo->instructions
['format'] = $this->trans_format($this->getpath($instructions,
624 array('0', '@', 'format'), 'moodle_auto_format'));
625 $qo->instructions
['files'] = $this->import_files($this->getpath(
626 $instructions, array('0', '#', 'file'), array()));
629 $this->import_hints($qo, $question);
635 * Import matching type question
636 * @param array question question array from xml tree
637 * @return object question object
639 public function import_matching($question) {
641 $qo = $this->import_headers($question);
643 // header parts particular to matching
645 $qo->shuffleanswers
= $this->trans_single($this->getpath($question,
646 array('#', 'shuffleanswers', 0, '#'), 1));
648 // run through subquestions
649 $qo->subquestions
= array();
650 $qo->subanswers
= array();
651 foreach ($question['#']['subquestion'] as $subqxml) {
652 $subquestion = array();
653 $subquestion['text'] = $this->getpath($subqxml, array('#', 'text', 0, '#'), '', true);
654 $subquestion['format'] = $this->trans_format($this->getpath($subqxml,
655 array('@', 'format'), 'moodle_auto_format'));
656 $subquestion['files'] = $this->import_files($this->getpath($subqxml,
657 array('#', 'file'), array()));
659 $qo->subquestions
[] = $subquestion;
660 $answers = $this->getpath($subqxml, array('#', 'answer'), array());
661 $qo->subanswers
[] = $this->getpath($subqxml,
662 array('#', 'answer', 0, '#', 'text', 0, '#'), '', true);
665 $this->import_combined_feedback($qo, $question, true);
666 $this->import_hints($qo, $question, true);
672 * Import essay type question
673 * @param array question question array from xml tree
674 * @return object question object
676 public function import_essay($question) {
678 $qo = $this->import_headers($question);
680 // header parts particular to essay
683 $answers = $this->getpath($question, array('#', 'answer'), null);
685 $answer = array_pop($answers);
686 $answer = $this->import_answer($answer);
688 $qo->feedback
= $answer->feedback
;
690 $qo->feedback
= array('text' => '', 'format' => FORMAT_MOODLE
, 'files' => array());
693 // get fraction - <fraction> tag is deprecated
694 $qo->fraction
= $this->getpath($question, array('@', 'fraction'), 0) / 100;
695 $qo->fraction
= $this->getpath($question, array('#', 'fraction', 0, '#'), $qo->fraction
);
701 * Import a calculated question
702 * @param object $question the imported XML data.
704 public function import_calculated($question) {
707 $qo = $this->import_headers($question);
709 // header parts particular to calculated
710 $qo->qtype
= CALCULATED
;
711 $qo->synchronize
= $this->getpath($question, array('#', 'synchronize', 0, '#'), 0);
712 $single = $this->getpath($question, array('#', 'single', 0, '#'), 'true');
713 $qo->single
= $this->trans_single($single);
714 $shuffleanswers = $this->getpath($question, array('#', 'shuffleanswers', 0, '#'), 'false');
715 $qo->answernumbering
= $this->getpath($question,
716 array('#', 'answernumbering', 0, '#'), 'abc');
717 $qo->shuffleanswers
= $this->trans_single($shuffleanswers);
719 $qo->correctfeedback
= array();
720 $qo->correctfeedback
['text'] = $this->getpath(
721 $question, array('#', 'correctfeedback', 0, '#', 'text', 0, '#'), '', true);
722 $qo->correctfeedback
['format'] = $this->trans_format($this->getpath(
723 $question, array('#', 'correctfeedback', 0, '@', 'formath'), 'moodle_auto_format'));
724 $qo->correctfeedback
['files'] = $this->import_files($this->getpath(
725 $question, array('#', 'correctfeedback', '0', '#', 'file'), array()));
727 $qo->partiallycorrectfeedback
= array();
728 $qo->partiallycorrectfeedback
['text'] = $this->getpath($question,
729 array('#', 'partiallycorrectfeedback', 0, '#', 'text', 0, '#'), '', true);
730 $qo->partiallycorrectfeedback
['format'] = $this->trans_format(
731 $this->getpath($question, array('#', 'partiallycorrectfeedback', 0, '@', 'format'),
732 'moodle_auto_format'));
733 $qo->partiallycorrectfeedback
['files'] = $this->import_files($this->getpath(
734 $question, array('#', 'partiallycorrectfeedback', '0', '#', 'file'), array()));
736 $qo->incorrectfeedback
= array();
737 $qo->incorrectfeedback
['text'] = $this->getpath($question,
738 array('#', 'incorrectfeedback', 0, '#', 'text', 0, '#'), '', true);
739 $qo->incorrectfeedback
['format'] = $this->trans_format($this->getpath($question,
740 array('#', 'incorrectfeedback', 0, '@', 'format'), 'moodle_auto_format'));
741 $qo->incorrectfeedback
['files'] = $this->import_files($this->getpath($question,
742 array('#', 'incorrectfeedback', '0', '#', 'file'), array()));
744 $qo->unitgradingtype
= $this->getpath($question,
745 array('#', 'unitgradingtype', 0, '#'), 0);
746 $qo->unitpenalty
= $this->getpath($question, array('#', 'unitpenalty', 0, '#'), 0);
747 $qo->showunits
= $this->getpath($question, array('#', 'showunits', 0, '#'), 0);
748 $qo->unitsleft
= $this->getpath($question, array('#', 'unitsleft', 0, '#'), 0);
749 $qo->instructions
= $this->getpath($question,
750 array('#', 'instructions', 0, '#', 'text', 0, '#'), '', true);
751 if (!empty($instructions)) {
752 $qo->instructions
= array();
753 $qo->instructions
['text'] = $this->getpath($instructions,
754 array('0', '#', 'text', '0', '#'), '', true);
755 $qo->instructions
['format'] = $this->trans_format($this->getpath($instructions,
756 array('0', '@', 'format'), 'moodle_auto_format'));
757 $qo->instructions
['files'] = $this->import_files($this->getpath($instructions,
758 array('0', '#', 'file'), array()));
762 $answers = $question['#']['answer'];
763 $qo->answers
= array();
764 $qo->feedback
= array();
765 $qo->fraction
= array();
766 $qo->tolerance
= array();
767 $qo->tolerancetype
= array();
768 $qo->correctanswerformat
= array();
769 $qo->correctanswerlength
= array();
770 $qo->feedback
= array();
771 foreach ($answers as $answer) {
772 $ans = $this->import_answer($answer);
773 // answer outside of <text> is deprecated
774 if (empty($ans->answer
['text'])) {
775 $ans->answer
['text'] = '*';
777 $qo->answers
[] = $ans->answer
;
778 $qo->feedback
[] = $ans->feedback
;
779 $qo->tolerance
[] = $answer['#']['tolerance'][0]['#'];
780 // fraction as a tag is deprecated
781 if (!empty($answer['#']['fraction'][0]['#'])) {
782 $qo->fraction
[] = $answer['#']['fraction'][0]['#'];
784 $qo->fraction
[] = $answer['@']['fraction'] / 100;
786 $qo->tolerancetype
[] = $answer['#']['tolerancetype'][0]['#'];
787 $qo->correctanswerformat
[] = $answer['#']['correctanswerformat'][0]['#'];
788 $qo->correctanswerlength
[] = $answer['#']['correctanswerlength'][0]['#'];
792 if (isset($question['#']['units'][0]['#']['unit'])) {
793 $units = $question['#']['units'][0]['#']['unit'];
794 $qo->multiplier
= array();
795 foreach ($units as $unit) {
796 $qo->multiplier
[] = $unit['#']['multiplier'][0]['#'];
797 $qo->unit
[] = $unit['#']['unit_name'][0]['#'];
800 $instructions = $this->getpath($question, array('#', 'instructions'), array());
801 if (!empty($instructions)) {
802 $qo->instructions
= array();
803 $qo->instructions
['text'] = $this->getpath($instructions,
804 array('0', '#', 'text', '0', '#'), '', true);
805 $qo->instructions
['format'] = $this->trans_format($this->getpath($instructions,
806 array('0', '@', 'format'), 'moodle_auto_format'));
807 $qo->instructions
['files'] = $this->import_files($this->getpath($instructions,
808 array('0', '#', 'file'), array()));
810 $datasets = $question['#']['dataset_definitions'][0]['#']['dataset_definition'];
811 $qo->dataset
= array();
812 $qo->datasetindex
= 0;
813 foreach ($datasets as $dataset) {
815 $qo->dataset
[$qo->datasetindex
] = new stdClass();
816 $qo->dataset
[$qo->datasetindex
]->status
=
817 $this->import_text($dataset['#']['status'][0]['#']['text']);
818 $qo->dataset
[$qo->datasetindex
]->name
=
819 $this->import_text($dataset['#']['name'][0]['#']['text']);
820 $qo->dataset
[$qo->datasetindex
]->type
=
821 $dataset['#']['type'][0]['#'];
822 $qo->dataset
[$qo->datasetindex
]->distribution
=
823 $this->import_text($dataset['#']['distribution'][0]['#']['text']);
824 $qo->dataset
[$qo->datasetindex
]->max
=
825 $this->import_text($dataset['#']['maximum'][0]['#']['text']);
826 $qo->dataset
[$qo->datasetindex
]->min
=
827 $this->import_text($dataset['#']['minimum'][0]['#']['text']);
828 $qo->dataset
[$qo->datasetindex
]->length
=
829 $this->import_text($dataset['#']['decimals'][0]['#']['text']);
830 $qo->dataset
[$qo->datasetindex
]->distribution
=
831 $this->import_text($dataset['#']['distribution'][0]['#']['text']);
832 $qo->dataset
[$qo->datasetindex
]->itemcount
= $dataset['#']['itemcount'][0]['#'];
833 $qo->dataset
[$qo->datasetindex
]->datasetitem
= array();
834 $qo->dataset
[$qo->datasetindex
]->itemindex
= 0;
835 $qo->dataset
[$qo->datasetindex
]->number_of_items
=
836 $dataset['#']['number_of_items'][0]['#'];
837 $datasetitems = $dataset['#']['dataset_items'][0]['#']['dataset_item'];
838 foreach ($datasetitems as $datasetitem) {
839 $qo->dataset
[$qo->datasetindex
]->itemindex++
;
840 $qo->dataset
[$qo->datasetindex
]->datasetitem
[
841 $qo->dataset
[$qo->datasetindex
]->itemindex
] = new stdClass();
842 $qo->dataset
[$qo->datasetindex
]->datasetitem
[
843 $qo->dataset
[$qo->datasetindex
]->itemindex
]->itemnumber
=
844 $datasetitem['#']['number'][0]['#'];
845 $qo->dataset
[$qo->datasetindex
]->datasetitem
[
846 $qo->dataset
[$qo->datasetindex
]->itemindex
]->value
=
847 $datasetitem['#']['value'][0]['#'];
851 $this->import_hints($qo, $question);
857 * This is not a real question type. It's a dummy type used to specify the
858 * import category. The format is:
859 * <question type="category">
860 * <category>tom/dick/harry</category>
863 protected function import_category($question) {
864 $qo = new stdClass();
865 $qo->qtype
= 'category';
866 $qo->category
= $this->import_text($question['#']['category'][0]['#']['text']);
871 * Parse the array of lines into an array of questions
872 * this *could* burn memory - but it won't happen that much
873 * so fingers crossed!
874 * @param array of lines from the input file.
875 * @return array (of objects) question objects.
877 protected function readquestions($lines) {
878 // We just need it as one big string
879 $text = implode($lines, ' ');
882 // This converts xml to big nasty data structure
883 // the 0 means keep white space as it is (important for markdown format)
885 $xml = xmlize($text, 0, 'UTF-8', true);
886 } catch (xml_format_exception
$e) {
887 $this->error($e->getMessage(), '');
890 // Set up array to hold all our questions
891 $questions = array();
893 // Iterate through questions
894 foreach ($xml['quiz']['#']['question'] as $question) {
895 $questiontype = $question['@']['type'];
897 if ($questiontype == 'multichoice') {
898 $qo = $this->import_multichoice($question);
899 } else if ($questiontype == 'truefalse') {
900 $qo = $this->import_truefalse($question);
901 } else if ($questiontype == 'shortanswer') {
902 $qo = $this->import_shortanswer($question);
903 } else if ($questiontype == 'numerical') {
904 $qo = $this->import_numerical($question);
905 } else if ($questiontype == 'description') {
906 $qo = $this->import_description($question);
907 } else if ($questiontype == 'matching') {
908 $qo = $this->import_matching($question);
909 } else if ($questiontype == 'cloze') {
910 $qo = $this->import_multianswer($question);
911 } else if ($questiontype == 'essay') {
912 $qo = $this->import_essay($question);
913 } else if ($questiontype == 'calculated') {
914 $qo = $this->import_calculated($question);
915 } else if ($questiontype == 'category') {
916 $qo = $this->import_category($question);
919 // Not a type we handle ourselves. See if the question type wants
921 if (!$qo = $this->try_importing_using_qtypes(
922 $question, null, null, $questiontype)) {
923 $this->error(get_string('xmltypeunsupported', 'qformat_xml', $questiontype));
928 // Stick the result in the $questions array
936 // EXPORT FUNCTIONS START HERE
938 public function export_file_extension() {
943 * Turn the internal question code into a human readable form
944 * (The code used to be numeric, but this remains as some of
945 * the names don't match the new internal format)
946 * @param mixed $typeid Internal code
947 * @return string question type string
949 protected function get_qtype($typeid) {
954 return 'multichoice';
956 return 'shortanswer';
962 return 'description';
975 * Convert internal Moodle text format code into
976 * human readable form
977 * @param int id internal code
978 * @return string format text
980 protected function get_format($id) {
983 return 'moodle_auto_format';
990 case FORMAT_MARKDOWN
:
998 * Convert internal single question code into
999 * human readable form
1000 * @param int id single question code
1001 * @return string single question string
1003 public function get_single($id) {
1015 * Generates <text></text> tags, processing raw text therein
1016 * @param string $raw the content to output.
1017 * @param int $indent the current indent level.
1018 * @param bool $short stick it on one line.
1019 * @return string formatted text.
1021 public function writetext($raw, $indent = 0, $short = true) {
1022 $indent = str_repeat(' ', $indent);
1024 // if required add CDATA tags
1025 if (!empty($raw) && htmlspecialchars($raw) != $raw) {
1026 $raw = "<![CDATA[$raw]]>";
1030 $xml = "$indent<text>$raw</text>\n";
1032 $xml = "$indent<text>\n$raw\n$indent</text>\n";
1038 protected function presave_process($content) {
1039 // Override to allow us to add xml headers and footers
1040 return '<?xml version="1.0" encoding="UTF-8"?>
1042 ' . $content . '</quiz>';
1046 * Turns question into an xml segment
1047 * @param object $question the question data.
1048 * @return string xml segment
1050 public function writequestion($question) {
1051 global $CFG, $OUTPUT;
1053 $fs = get_file_storage();
1054 $contextid = $question->contextid
;
1055 // Get files used by the questiontext.
1056 $question->questiontextfiles
= $fs->get_area_files(
1057 $contextid, 'question', 'questiontext', $question->id
);
1058 // Get files used by the generalfeedback.
1059 $question->generalfeedbackfiles
= $fs->get_area_files(
1060 $contextid, 'question', 'generalfeedback', $question->id
);
1061 if (!empty($question->options
->answers
)) {
1062 foreach ($question->options
->answers
as $answer) {
1063 $answer->feedbackfiles
= $fs->get_area_files(
1064 $contextid, 'question', 'answerfeedback', $answer->id
);
1070 // Add a comment linking this to the original question id.
1071 $expout .= "<!-- question: $question->id -->\n";
1073 // Check question type
1074 if (!$questiontype = $this->get_qtype($question->qtype
)) {
1075 // must be a plugin then, so just accept the name supplied
1076 $questiontype = $question->qtype
;
1080 // generates specific header for Cloze and category type question
1081 if ($question->qtype
== 'category') {
1082 $categorypath = $this->writetext($question->category
);
1083 $expout .= " <question type=\"category\">\n";
1084 $expout .= " <category>\n";
1085 $expout .= " $categorypath\n";
1086 $expout .= " </category>\n";
1087 $expout .= " </question>\n";
1090 } else if ($question->qtype
!= MULTIANSWER
) {
1091 // for all question types except Close
1092 $name_text = $this->writetext($question->name
, 3);
1094 $expout .= " <question type=\"$questiontype\">\n";
1095 $expout .= " <name>\n";
1096 $expout .= $name_text;
1097 $expout .= " </name>\n";
1098 $expout .= " <questiontext {$this->format($question->questiontextformat)}>\n";
1099 $expout .= $this->writetext($question->questiontext
, 3);
1100 $expout .= $this->writefiles($question->questiontextfiles
);
1101 $expout .= " </questiontext>\n";
1102 $expout .= " <generalfeedback {$this->format($question->generalfeedbackformat)}>\n";
1103 $expout .= $this->writetext($question->generalfeedback
, 3);
1104 $expout .= $this->writefiles($question->generalfeedbackfiles
);
1105 $expout .= " </generalfeedback>\n";
1106 $expout .= " <defaultgrade>{$question->defaultmark}</defaultgrade>\n";
1107 $expout .= " <penalty>{$question->penalty}</penalty>\n";
1108 $expout .= " <hidden>{$question->hidden}</hidden>\n";
1111 // for Cloze type only
1112 $name_text = $this->writetext($question->name
);
1113 $question_text = $this->writetext($question->questiontext
);
1114 $generalfeedback = $this->writetext($question->generalfeedback
);
1115 $expout .= " <question type=\"$questiontype\">\n";
1116 $expout .= " <name>\n";
1117 $expout .= $name_text;
1118 $expout .= " </name>\n";
1119 $expout .= " <questiontext>\n";
1120 $expout .= $this->writetext($question->questiontext
, 3);
1121 $expout .= $this->writefiles($question->questiontextfiles
);
1122 $expout .= " </questiontext>\n";
1123 $expout .= " <generalfeedback>\n";
1124 $expout .= $this->writetext($question->generalfeedback
, 3);
1125 $expout .= $this->writefiles($question->generalfeedbackfiles
);
1126 $expout .= " </generalfeedback>\n";
1129 // output depends on question type
1130 switch($question->qtype
) {
1132 // not a qtype really - dummy used for category switching
1136 $trueanswer = $question->options
->answers
[$question->options
->trueanswer
];
1137 $trueanswer->answer
= 'true';
1138 $expout .= $this->write_answer($trueanswer);
1140 $falseanswer = $question->options
->answers
[$question->options
->falseanswer
];
1141 $falseanswer->answer
= 'false';
1142 $expout .= $this->write_answer($falseanswer);
1146 $expout .= " <single>" . $this->get_single($question->options
->single
) .
1148 $expout .= " <shuffleanswers>" .
1149 $this->get_single($question->options
->shuffleanswers
) .
1150 "</shuffleanswers>\n";
1151 $expout .= " <answernumbering>" . $question->options
->answernumbering
.
1152 "</answernumbering>\n";
1153 $expout .= $this->write_combined_feedback($question->options
);
1154 $expout .= $this->write_answers($question->options
->answers
);
1158 $expout .= " <usecase>{$question->options->usecase}</usecase>\n";
1159 $expout .= $this->write_answers($question->options
->answers
);
1163 foreach ($question->options
->answers
as $answer) {
1164 $expout .= $this->write_answer($answer,
1165 " <tolerance>$answer->tolerance</tolerance>\n");
1168 $units = $question->options
->units
;
1169 if (count($units)) {
1170 $expout .= "<units>\n";
1171 foreach ($units as $unit) {
1172 $expout .= " <unit>\n";
1173 $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n";
1174 $expout .= " <unit_name>{$unit->unit}</unit_name>\n";
1175 $expout .= " </unit>\n";
1177 $expout .= "</units>\n";
1179 if (isset($question->options
->unitgradingtype
)) {
1180 $expout .= " <unitgradingtype>" . $question->options
->unitgradingtype
.
1181 "</unitgradingtype>\n";
1183 if (isset($question->options
->unitpenalty
)) {
1184 $expout .= " <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n";
1186 if (isset($question->options
->showunits
)) {
1187 $expout .= " <showunits>{$question->options->showunits}</showunits>\n";
1189 if (isset($question->options
->unitsleft
)) {
1190 $expout .= " <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1192 if (!empty($question->options
->instructionsformat
)) {
1193 $files = $fs->get_area_files($contextid, 'qtype_numerical',
1194 'instruction', $question->id
);
1195 $expout .= " <instructions " .
1196 $this->format($question->options
->instructionsformat
) . ">\n";
1197 $expout .= $this->writetext($question->options
->instructions
, 3);
1198 $expout .= $this->writefiles($files);
1199 $expout .= " </instructions>\n";
1204 $expout .= " <shuffleanswers>" .
1205 $this->get_single($question->options
->shuffleanswers
) .
1206 "</shuffleanswers>\n";
1207 $expout .= $this->write_combined_feedback($question->options
);
1208 foreach ($question->options
->subquestions
as $subquestion) {
1209 $files = $fs->get_area_files($contextid, 'qtype_match',
1210 'subquestion', $subquestion->id
);
1211 $expout .= " <subquestion " .
1212 $this->format($subquestion->questiontextformat
) . ">\n";
1213 $expout .= $this->writetext($subquestion->questiontext
, 3);
1214 $expout .= $this->writefiles($files);
1215 $expout .= " <answer>\n";
1216 $expout .= $this->writetext($subquestion->answertext
, 4);
1217 $expout .= " </answer>\n";
1218 $expout .= " </subquestion>\n";
1223 // Nothing else to do.
1228 foreach ($question->options
->questions
as $question) {
1229 $thispattern = "{#".$acount."}";
1230 $thisreplace = $question->questiontext
;
1231 $expout = preg_replace("~$thispattern~", $thisreplace, $expout);
1237 // Nothing else to do.
1241 case 'calculatedsimple':
1242 case 'calculatedmulti':
1243 $expout .= " <synchronize>{$question->options->synchronize}</synchronize>\n";
1244 $expout .= " <single>{$question->options->single}</single>\n";
1245 $expout .= " <answernumbering>" . $question->options
->answernumbering
.
1246 "</answernumbering>\n";
1247 $expout .= " <shuffleanswers>" .
1248 $this->writetext($question->options
->shuffleanswers
, 3) .
1249 "</shuffleanswers>\n";
1251 $component = 'qtype_' . $question->qtype
;
1252 $files = $fs->get_area_files($contextid, $component,
1253 'correctfeedback', $question->id
);
1254 $expout .= " <correctfeedback>\n";
1255 $expout .= $this->writetext($question->options
->correctfeedback
, 3);
1256 $expout .= $this->writefiles($files);
1257 $expout .= " </correctfeedback>\n";
1259 $files = $fs->get_area_files($contextid, $component,
1260 'partiallycorrectfeedback', $question->id
);
1261 $expout .= " <partiallycorrectfeedback>\n";
1262 $expout .= $this->writetext($question->options
->partiallycorrectfeedback
, 3);
1263 $expout .= $this->writefiles($files);
1264 $expout .= " </partiallycorrectfeedback>\n";
1266 $files = $fs->get_area_files($contextid, $component,
1267 'incorrectfeedback', $question->id
);
1268 $expout .= " <incorrectfeedback>\n";
1269 $expout .= $this->writetext($question->options
->incorrectfeedback
, 3);
1270 $expout .= $this->writefiles($files);
1271 $expout .= " </incorrectfeedback>\n";
1273 foreach ($question->options
->answers
as $answer) {
1274 $percent = 100 * $answer->fraction
;
1275 $expout .= "<answer fraction=\"$percent\">\n";
1276 // "<text/>" tags are an added feature, old files won't have them
1277 $expout .= " <text>{$answer->answer}</text>\n";
1278 $expout .= " <tolerance>{$answer->tolerance}</tolerance>\n";
1279 $expout .= " <tolerancetype>{$answer->tolerancetype}</tolerancetype>\n";
1280 $expout .= " <correctanswerformat>" .
1281 $answer->correctanswerformat
. "</correctanswerformat>\n";
1282 $expout .= " <correctanswerlength>" .
1283 $answer->correctanswerlength
. "</correctanswerlength>\n";
1284 $expout .= " <feedback {$this->format($answer->feedbackformat)}>\n";
1285 $files = $fs->get_area_files($contextid, $component,
1286 'instruction', $question->id
);
1287 $expout .= $this->writetext($answer->feedback
);
1288 $expout .= $this->writefiles($answer->feedbackfiles
);
1289 $expout .= " </feedback>\n";
1290 $expout .= "</answer>\n";
1292 if (isset($question->options
->unitgradingtype
)) {
1293 $expout .= " <unitgradingtype>" .
1294 $question->options
->unitgradingtype
. "</unitgradingtype>\n";
1296 if (isset($question->options
->unitpenalty
)) {
1297 $expout .= " <unitpenalty>" .
1298 $question->options
->unitpenalty
. "</unitpenalty>\n";
1300 if (isset($question->options
->showunits
)) {
1301 $expout .= " <showunits>{$question->options->showunits}</showunits>\n";
1303 if (isset($question->options
->unitsleft
)) {
1304 $expout .= " <unitsleft>{$question->options->unitsleft}</unitsleft>\n";
1307 if (isset($question->options
->instructionsformat
)) {
1308 $files = $fs->get_area_files($contextid, $component,
1309 'instruction', $question->id
);
1310 $expout .= " <instructions " .
1311 $this->format($question->options
->instructionsformat
) . ">\n";
1312 $expout .= $this->writetext($question->options
->instructions
, 3);
1313 $expout .= $this->writefiles($files);
1314 $expout .= " </instructions>\n";
1317 if (isset($question->options
->units
)) {
1318 $units = $question->options
->units
;
1319 if (count($units)) {
1320 $expout .= "<units>\n";
1321 foreach ($units as $unit) {
1322 $expout .= " <unit>\n";
1323 $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n";
1324 $expout .= " <unit_name>{$unit->unit}</unit_name>\n";
1325 $expout .= " </unit>\n";
1327 $expout .= "</units>\n";
1331 // The tag $question->export_process has been set so we get all the
1332 // data items in the database from the function
1333 // qtype_calculated::get_question_options calculatedsimple defaults
1335 if (isset($question->options
->datasets
) && count($question->options
->datasets
)) {
1336 $expout .= "<dataset_definitions>\n";
1337 foreach ($question->options
->datasets
as $def) {
1338 $expout .= "<dataset_definition>\n";
1339 $expout .= " <status>".$this->writetext($def->status
)."</status>\n";
1340 $expout .= " <name>".$this->writetext($def->name
)."</name>\n";
1341 if ($question->qtype
== CALCULATED
) {
1342 $expout .= " <type>calculated</type>\n";
1344 $expout .= " <type>calculatedsimple</type>\n";
1346 $expout .= " <distribution>" . $this->writetext($def->distribution
) .
1347 "</distribution>\n";
1348 $expout .= " <minimum>" . $this->writetext($def->minimum
) .
1350 $expout .= " <maximum>" . $this->writetext($def->maximum
) .
1352 $expout .= " <decimals>" . $this->writetext($def->decimals
) .
1354 $expout .= " <itemcount>$def->itemcount</itemcount>\n";
1355 if ($def->itemcount
> 0) {
1356 $expout .= " <dataset_items>\n";
1357 foreach ($def->items
as $item) {
1358 $expout .= " <dataset_item>\n";
1359 $expout .= " <number>".$item->itemnumber
."</number>\n";
1360 $expout .= " <value>".$item->value
."</value>\n";
1361 $expout .= " </dataset_item>\n";
1363 $expout .= " </dataset_items>\n";
1364 $expout .= " <number_of_items>" . $def->number_of_items
.
1365 "</number_of_items>\n";
1367 $expout .= "</dataset_definition>\n";
1369 $expout .= "</dataset_definitions>\n";
1374 // try support by optional plugin
1375 if (!$data = $this->try_exporting_using_qtypes($question->qtype
, $question)) {
1376 notify(get_string('unsupportedexport', 'qformat_xml', $question->qtype
));
1381 // Output any hints.
1382 $expout .= $this->write_hints($question);
1384 // Write the question tags.
1385 if (!empty($CFG->usetags
)) {
1386 require_once($CFG->dirroot
.'/tag/lib.php');
1387 $tags = tag_get_tags_array('question', $question->id
);
1388 if (!empty($tags)) {
1389 $expout .= " <tags>\n";
1390 foreach ($tags as $tag) {
1391 $expout .= " <tag>" . $this->writetext($tag, 0, true) . "</tag>\n";
1393 $expout .= " </tags>\n";
1397 // close the question tag
1398 $expout .= " </question>\n";
1403 public function write_answers($answers) {
1404 if (empty($answers)) {
1408 foreach ($answers as $answer) {
1409 $output .= $this->write_answer($answer);
1414 public function write_answer($answer, $extra = '') {
1415 $percent = $answer->fraction
* 100;
1417 $output .= " <answer fraction=\"$percent\" {$this->format($answer->answerformat)}>\n";
1418 $output .= $this->writetext($answer->answer
, 3);
1419 $output .= " <feedback {$this->format($answer->feedbackformat)}>\n";
1420 $output .= $this->writetext($answer->feedback
, 4);
1421 $output .= $this->writefiles($answer->feedbackfiles
);
1422 $output .= " </feedback>\n";
1424 $output .= " </answer>\n";
1428 public function write_hints($question) {
1429 if (empty($question->hints
)) {
1434 foreach ($question->hints
as $hint) {
1435 $output .= $this->write_hint($hint);
1441 * @param unknown_type $format a FORMAT_... constant.
1442 * @return string the attribute to add to an XML tag.
1444 protected function format($format) {
1445 return 'format="' . $this->get_format($format) . '"';
1448 public function write_hint($hint) {
1450 $output .= " <hint {$this->format($hint->hintformat)}>\n";
1451 $output .= ' ' . $this->writetext($hint->hint
);
1452 if (!empty($hint->shownumcorrect
)) {
1453 $output .= " <shownumcorrect/>\n";
1455 if (!empty($hint->clearwrong
)) {
1456 $output .= " <clearwrong/>\n";
1458 if (!empty($hint->options
)) {
1459 $output .= ' <options>' . htmlspecialchars($hint->options
) . "</options>\n";
1461 $output .= " </hint>\n";
1465 public function write_combined_feedback($questionoptions) {
1466 $output = " <correctfeedback {$this->format($questionoptions->correctfeedbackformat)}>
1467 {$this->writetext($questionoptions->correctfeedback)} </correctfeedback>
1468 <partiallycorrectfeedback {$this->format($questionoptions->partiallycorrectfeedbackformat)}>
1469 {$this->writetext($questionoptions->partiallycorrectfeedback)} </partiallycorrectfeedback>
1470 <incorrectfeedback {$this->format($questionoptions->incorrectfeedbackformat)}>
1471 {$this->writetext($questionoptions->incorrectfeedback)} </incorrectfeedback>\n";
1472 if (!empty($questionoptions->shownumcorrect
)) {
1473 $output .= " <shownumcorrect/>\n";