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