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