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