Commit | Line | Data |
---|---|---|
aeb15530 | 1 | <?php |
84769fd8 | 2 | // |
3 | /////////////////////////////////////////////////////////////// | |
4 | // XML import/export | |
5 | // | |
6 | ////////////////////////////////////////////////////////////////////////// | |
7 | // Based on default.php, included by ../import.php | |
41a89a07 | 8 | /** |
9 | * @package questionbank | |
10 | * @subpackage importexport | |
11 | */ | |
84769fd8 | 12 | require_once( "$CFG->libdir/xmlize.php" ); |
13 | ||
f5565b69 | 14 | class qformat_xml extends qformat_default { |
84769fd8 | 15 | |
16 | function provide_import() { | |
17 | return true; | |
18 | } | |
19 | ||
20 | function provide_export() { | |
21 | return true; | |
22 | } | |
23 | ||
24 | // IMPORT FUNCTIONS START HERE | |
25 | ||
88bc20c3 | 26 | /** |
c81415c7 | 27 | * Translate human readable format name |
28 | * into internal Moodle code number | |
6e557c08 | 29 | * @param string name format name from xml file |
30 | * @return int Moodle format code | |
c81415c7 | 31 | */ |
84769fd8 | 32 | function trans_format( $name ) { |
88bc20c3 | 33 | $name = trim($name); |
34 | ||
84769fd8 | 35 | if ($name=='moodle_auto_format') { |
36 | $id = 0; | |
37 | } | |
38 | elseif ($name=='html') { | |
39 | $id = 1; | |
40 | } | |
41 | elseif ($name=='plain_text') { | |
42 | $id = 2; | |
43 | } | |
44 | elseif ($name=='wiki_like') { | |
45 | $id = 3; | |
46 | } | |
47 | elseif ($name=='markdown') { | |
48 | $id = 4; | |
49 | } | |
50 | else { | |
51 | $id = 0; // or maybe warning required | |
52 | } | |
53 | return $id; | |
54 | } | |
55 | ||
6e557c08 | 56 | /** |
c81415c7 | 57 | * Translate human readable single answer option |
58 | * to internal code number | |
6e557c08 | 59 | * @param string name true/false |
60 | * @return int internal code number | |
c81415c7 | 61 | */ |
84769fd8 | 62 | function trans_single( $name ) { |
2da44816 | 63 | $name = trim($name); |
64 | if ($name == "false" || !$name) { | |
65 | return 0; | |
66 | } else { | |
67 | return 1; | |
68 | } | |
84769fd8 | 69 | } |
70 | ||
6e557c08 | 71 | /** |
c81415c7 | 72 | * process text string from xml file |
6e557c08 | 73 | * @param array $text bit of xml tree after ['text'] |
74 | * @return string processed text | |
c81415c7 | 75 | */ |
84769fd8 | 76 | function import_text( $text ) { |
17102269 | 77 | // quick sanity check |
78 | if (empty($text)) { | |
79 | return ''; | |
80 | } | |
84769fd8 | 81 | $data = $text[0]['#']; |
294ce987 | 82 | return trim($data); |
84769fd8 | 83 | } |
84 | ||
46013523 | 85 | /** |
86 | * return the value of a node, given a path to the node | |
87 | * if it doesn't exist return the default value | |
88 | * @param array xml data to read | |
88bc20c3 | 89 | * @param array path path to node expressed as array |
90 | * @param mixed default | |
46013523 | 91 | * @param bool istext process as text |
92 | * @param string error if set value must exist, return false and issue message if not | |
93 | * @return mixed value | |
94 | */ | |
95 | function getpath( $xml, $path, $default, $istext=false, $error='' ) { | |
96 | foreach ($path as $index) { | |
228b6f6b | 97 | if (!isset($xml[$index])) { |
46013523 | 98 | if (!empty($error)) { |
99 | $this->error( $error ); | |
100 | return false; | |
101 | } else { | |
102 | return $default; | |
103 | } | |
104 | } | |
105 | else $xml = $xml[$index]; | |
106 | } | |
107 | if ($istext) { | |
fc22da99 | 108 | if (!is_string($xml)) { |
c95defa9 | 109 | $this->error( get_string('invalidxml','qformat_xml') ); |
fc22da99 | 110 | } |
294ce987 | 111 | $xml = trim( $xml ); |
46013523 | 112 | } |
113 | ||
114 | return $xml; | |
115 | } | |
116 | ||
117 | ||
6e557c08 | 118 | /** |
c81415c7 | 119 | * import parts of question common to all types |
e7ef42f5 | 120 | * @param $question array question question array from xml tree |
6e557c08 | 121 | * @return object question object |
c81415c7 | 122 | */ |
84769fd8 | 123 | function import_headers( $question ) { |
46013523 | 124 | // get some error strings |
125 | $error_noname = get_string( 'xmlimportnoname','quiz' ); | |
126 | $error_noquestion = get_string( 'xmlimportnoquestion','quiz' ); | |
127 | ||
84769fd8 | 128 | // this routine initialises the question object |
5bed54e1 | 129 | $qo = $this->defaultquestion(); |
84769fd8 | 130 | |
46013523 | 131 | // question name |
132 | $qo->name = $this->getpath( $question, array('#','name',0,'#','text',0,'#'), '', true, $error_noname ); | |
6af98025 | 133 | $qo->questiontext = $this->getpath( $question, array('#','questiontext',0,'#','text',0,'#'), '', true ); |
46013523 | 134 | $qo->questiontextformat = $this->getpath( $question, array('#','questiontext',0,'@','format'), '' ); |
04d8268d PP |
135 | if (!is_numeric($qo->questiontextformat)) { |
136 | $qo->questiontextformat = text_format_name($qo->questiontextformat); | |
137 | } | |
e7ef42f5 | 138 | $qo->image = $this->getpath( $question, array('#','image',0,'#'), $qo->image ); |
46013523 | 139 | $image_base64 = $this->getpath( $question, array('#','image_base64','0','#'),'' ); |
140 | if (!empty($image_base64)) { | |
cdfaa838 | 141 | $qo->image = $this->importimagefile( $qo->image, $image_base64); |
46013523 | 142 | } |
143 | $qo->generalfeedback = $this->getpath( $question, array('#','generalfeedback',0,'#','text',0,'#'), $qo->generalfeedback, true ); | |
144 | $qo->defaultgrade = $this->getpath( $question, array('#','defaultgrade',0,'#'), $qo->defaultgrade ); | |
145 | $qo->penalty = $this->getpath( $question, array('#','penalty',0,'#'), $qo->penalty ); | |
84769fd8 | 146 | |
147 | return $qo; | |
148 | } | |
149 | ||
6e557c08 | 150 | /** |
c81415c7 | 151 | * import the common parts of a single answer |
6e557c08 | 152 | * @param array answer xml tree for single answer |
153 | * @return object answer object | |
88bc20c3 | 154 | */ |
84769fd8 | 155 | function import_answer( $answer ) { |
81a7a02b | 156 | $fraction = $this->getpath( $answer, array('@','fraction'),0 ); |
157 | $text = $this->getpath( $answer, array('#','text',0,'#'), '', true ); | |
158 | $feedback = $this->getpath( $answer, array('#','feedback',0,'#','text',0,'#'), '', true ); | |
84769fd8 | 159 | |
160 | $ans = null; | |
161 | $ans->answer = $text; | |
162 | $ans->fraction = $fraction / 100; | |
163 | $ans->feedback = $feedback; | |
84769fd8 | 164 | return $ans; |
165 | } | |
166 | ||
6e557c08 | 167 | /** |
88bc20c3 | 168 | * import multiple choice question |
6e557c08 | 169 | * @param array question question array from xml tree |
170 | * @return object question object | |
c81415c7 | 171 | */ |
84769fd8 | 172 | function import_multichoice( $question ) { |
84769fd8 | 173 | // get common parts |
174 | $qo = $this->import_headers( $question ); | |
175 | ||
176 | // 'header' parts particular to multichoice | |
177 | $qo->qtype = MULTICHOICE; | |
81a7a02b | 178 | $single = $this->getpath( $question, array('#','single',0,'#'), 'true' ); |
84769fd8 | 179 | $qo->single = $this->trans_single( $single ); |
81a7a02b | 180 | $shuffleanswers = $this->getpath( $question, array('#','shuffleanswers',0,'#'), 'false' ); |
2377f8dc | 181 | $qo->answernumbering = $this->getpath( $question, array('#','answernumbering',0,'#'), 'abc' ); |
2da44816 | 182 | $qo->shuffleanswers = $this->trans_single($shuffleanswers); |
81a7a02b | 183 | $qo->correctfeedback = $this->getpath( $question, array('#','correctfeedback',0,'#','text',0,'#'), '', true ); |
184 | $qo->partiallycorrectfeedback = $this->getpath( $question, array('#','partiallycorrectfeedback',0,'#','text',0,'#'), '', true ); | |
185 | $qo->incorrectfeedback = $this->getpath( $question, array('#','incorrectfeedback',0,'#','text',0,'#'), '', true ); | |
c0ffeb39 | 186 | |
187 | // There was a time on the 1.8 branch when it could output an empty answernumbering tag, so fix up any found. | |
188 | if (empty($qo->answernumbering)) { | |
189 | $qo->answernumbering = 'abc'; | |
190 | } | |
191 | ||
84769fd8 | 192 | // run through the answers |
88bc20c3 | 193 | $answers = $question['#']['answer']; |
84769fd8 | 194 | $a_count = 0; |
195 | foreach ($answers as $answer) { | |
196 | $ans = $this->import_answer( $answer ); | |
197 | $qo->answer[$a_count] = $ans->answer; | |
198 | $qo->fraction[$a_count] = $ans->fraction; | |
199 | $qo->feedback[$a_count] = $ans->feedback; | |
200 | ++$a_count; | |
201 | } | |
84769fd8 | 202 | return $qo; |
203 | } | |
204 | ||
6e557c08 | 205 | /** |
c81415c7 | 206 | * import cloze type question |
6e557c08 | 207 | * @param array question question array from xml tree |
208 | * @return object question object | |
c81415c7 | 209 | */ |
7b8bc256 | 210 | function import_multianswer( $questions ) { |
0c6b4d2e | 211 | $questiontext = $questions['#']['questiontext'][0]['#']['text']; |
212 | $qo = qtype_multianswer_extract_question($this->import_text($questiontext)); | |
7b8bc256 | 213 | |
214 | // 'header' parts particular to multianswer | |
215 | $qo->qtype = MULTIANSWER; | |
216 | $qo->course = $this->course; | |
4ab4dcb2 | 217 | $qo->generalfeedback = $this->getpath( $questions, array('#','generalfeedback',0,'#','text',0,'#'), '', true ); |
7b8bc256 | 218 | |
71ffbac2 | 219 | if (!empty($questions)) { |
7b8bc256 | 220 | $qo->name = $this->import_text( $questions['#']['name'][0]['#']['text'] ); |
221 | } | |
222 | ||
223 | return $qo; | |
224 | } | |
225 | ||
6e557c08 | 226 | /** |
c81415c7 | 227 | * import true/false type question |
6e557c08 | 228 | * @param array question question array from xml tree |
229 | * @return object question object | |
c81415c7 | 230 | */ |
84769fd8 | 231 | function import_truefalse( $question ) { |
84769fd8 | 232 | // get common parts |
fef8f84e | 233 | global $OUTPUT; |
84769fd8 | 234 | $qo = $this->import_headers( $question ); |
235 | ||
236 | // 'header' parts particular to true/false | |
237 | $qo->qtype = TRUEFALSE; | |
238 | ||
239 | // get answer info | |
3246ed33 | 240 | // |
241 | // In the past, it used to be assumed that the two answers were in the file | |
242 | // true first, then false. Howevever that was not always true. Now, we | |
243 | // try to match on the answer text, but in old exports, this will be a localised | |
244 | // string, so if we don't find true or false, we fall back to the old system. | |
245 | $first = true; | |
246 | $warning = false; | |
247 | foreach ($question['#']['answer'] as $answer) { | |
81a7a02b | 248 | $answertext = $this->getpath( $answer, array('#','text',0,'#'), '', true ); |
249 | $feedback = $this->getpath($answer, array('#','feedback',0,'#','text',0,'#'), '', true ); | |
3246ed33 | 250 | if ($answertext != 'true' && $answertext != 'false') { |
251 | $warning = true; | |
252 | $answertext = $first ? 'true' : 'false'; // Old style file, assume order is true/false. | |
88bc20c3 | 253 | } |
3246ed33 | 254 | if ($answertext == 'true') { |
255 | $qo->answer = ($answer['@']['fraction'] == 100); | |
7939a4a0 | 256 | $qo->correctanswer = $qo->answer; |
3246ed33 | 257 | $qo->feedbacktrue = $feedback; |
258 | } else { | |
259 | $qo->answer = ($answer['@']['fraction'] != 100); | |
7939a4a0 | 260 | $qo->correctanswer = $qo->answer; |
3246ed33 | 261 | $qo->feedbackfalse = $feedback; |
262 | } | |
263 | $first = false; | |
84769fd8 | 264 | } |
3246ed33 | 265 | |
266 | if ($warning) { | |
267 | $a = new stdClass; | |
55c54868 | 268 | $a->questiontext = $qo->questiontext; |
3246ed33 | 269 | $a->answer = get_string($qo->answer ? 'true' : 'false', 'quiz'); |
fef8f84e | 270 | echo $OUTPUT->notification(get_string('truefalseimporterror', 'quiz', $a)); |
84769fd8 | 271 | } |
84769fd8 | 272 | return $qo; |
273 | } | |
274 | ||
6e557c08 | 275 | /** |
c81415c7 | 276 | * import short answer type question |
6e557c08 | 277 | * @param array question question array from xml tree |
278 | * @return object question object | |
c81415c7 | 279 | */ |
84769fd8 | 280 | function import_shortanswer( $question ) { |
84769fd8 | 281 | // get common parts |
282 | $qo = $this->import_headers( $question ); | |
283 | ||
284 | // header parts particular to shortanswer | |
285 | $qo->qtype = SHORTANSWER; | |
286 | ||
287 | // get usecase | |
46013523 | 288 | $qo->usecase = $this->getpath($question, array('#','usecase',0,'#'), $qo->usecase ); |
84769fd8 | 289 | |
290 | // run through the answers | |
88bc20c3 | 291 | $answers = $question['#']['answer']; |
84769fd8 | 292 | $a_count = 0; |
293 | foreach ($answers as $answer) { | |
294 | $ans = $this->import_answer( $answer ); | |
295 | $qo->answer[$a_count] = $ans->answer; | |
296 | $qo->fraction[$a_count] = $ans->fraction; | |
297 | $qo->feedback[$a_count] = $ans->feedback; | |
298 | ++$a_count; | |
299 | } | |
300 | ||
301 | return $qo; | |
302 | } | |
88bc20c3 | 303 | |
6e557c08 | 304 | /** |
c81415c7 | 305 | * import description type question |
6e557c08 | 306 | * @param array question question array from xml tree |
307 | * @return object question object | |
c81415c7 | 308 | */ |
7b8bc256 | 309 | function import_description( $question ) { |
310 | // get common parts | |
311 | $qo = $this->import_headers( $question ); | |
312 | // header parts particular to shortanswer | |
313 | $qo->qtype = DESCRIPTION; | |
3f5633df | 314 | $qo->defaultgrade = 0; |
315 | $qo->length = 0; | |
7b8bc256 | 316 | return $qo; |
317 | } | |
84769fd8 | 318 | |
6e557c08 | 319 | /** |
c81415c7 | 320 | * import numerical type question |
6e557c08 | 321 | * @param array question question array from xml tree |
322 | * @return object question object | |
c81415c7 | 323 | */ |
324 | function import_numerical( $question ) { | |
84769fd8 | 325 | // get common parts |
326 | $qo = $this->import_headers( $question ); | |
327 | ||
328 | // header parts particular to numerical | |
329 | $qo->qtype = NUMERICAL; | |
330 | ||
331 | // get answers array | |
332 | $answers = $question['#']['answer']; | |
333 | $qo->answer = array(); | |
334 | $qo->feedback = array(); | |
335 | $qo->fraction = array(); | |
336 | $qo->tolerance = array(); | |
337 | foreach ($answers as $answer) { | |
55c54868 | 338 | // answer outside of <text> is deprecated |
81a7a02b | 339 | $answertext = trim( $this->getpath( $answer, array('#',0), '' ) ); |
340 | $qo->answer[] = $this->getpath( $answer, array('#','text',0,'#'), $answertext, true ); | |
341 | if (empty($qo->answer)) { | |
342 | $qo->answer = '*'; | |
55c54868 | 343 | } |
81a7a02b | 344 | $qo->feedback[] = $this->getpath( $answer, array('#','feedback',0,'#','text',0,'#'), '', true ); |
345 | $qo->tolerance[] = $this->getpath( $answer, array('#','tolerance',0,'#'), 0 ); | |
55c54868 | 346 | |
347 | // fraction as a tag is deprecated | |
81a7a02b | 348 | $fraction = $this->getpath( $answer, array('@','fraction'), 0 ) / 100; |
349 | $qo->fraction[] = $this->getpath( $answer, array('#','fraction',0,'#'), $fraction ); // deprecated | |
84769fd8 | 350 | } |
351 | ||
352 | // get units array | |
84769fd8 | 353 | $qo->unit = array(); |
81a7a02b | 354 | $units = $this->getpath( $question, array('#','units',0,'#','unit'), array() ); |
355 | if (!empty($units)) { | |
a0d187bf | 356 | $qo->multiplier = array(); |
357 | foreach ($units as $unit) { | |
81a7a02b | 358 | $qo->multiplier[] = $this->getpath( $unit, array('#','multiplier',0,'#'), 1 ); |
359 | $qo->unit[] = $this->getpath( $unit, array('#','unit_name',0,'#'), '', true ); | |
a0d187bf | 360 | } |
84769fd8 | 361 | } |
84769fd8 | 362 | return $qo; |
363 | } | |
364 | ||
6e557c08 | 365 | /** |
c81415c7 | 366 | * import matching type question |
6e557c08 | 367 | * @param array question question array from xml tree |
368 | * @return object question object | |
c81415c7 | 369 | */ |
51bcdf28 | 370 | function import_matching( $question ) { |
51bcdf28 | 371 | // get common parts |
372 | $qo = $this->import_headers( $question ); | |
373 | ||
374 | // header parts particular to matching | |
375 | $qo->qtype = MATCH; | |
81a7a02b | 376 | $qo->shuffleanswers = $this->getpath( $question, array( '#','shuffleanswers',0,'#' ), 1 ); |
51bcdf28 | 377 | |
378 | // get subquestions | |
379 | $subquestions = $question['#']['subquestion']; | |
380 | $qo->subquestions = array(); | |
381 | $qo->subanswers = array(); | |
382 | ||
383 | // run through subquestions | |
384 | foreach ($subquestions as $subquestion) { | |
81a7a02b | 385 | $qo->subquestions[] = $this->getpath( $subquestion, array('#','text',0,'#'), '', true ); |
386 | $qo->subanswers[] = $this->getpath( $subquestion, array('#','answer',0,'#','text',0,'#'), '', true); | |
51bcdf28 | 387 | } |
51bcdf28 | 388 | return $qo; |
389 | } | |
390 | ||
6e557c08 | 391 | /** |
c81415c7 | 392 | * import essay type question |
6e557c08 | 393 | * @param array question question array from xml tree |
394 | * @return object question object | |
c81415c7 | 395 | */ |
396 | function import_essay( $question ) { | |
397 | // get common parts | |
398 | $qo = $this->import_headers( $question ); | |
399 | ||
400 | // header parts particular to essay | |
401 | $qo->qtype = ESSAY; | |
402 | ||
403 | // get feedback | |
88bc20c3 | 404 | $qo->feedback = $this->getpath( $question, array('#','answer',0,'#','feedback',0,'#','text',0,'#'), '', true ); |
55c54868 | 405 | |
406 | // get fraction - <fraction> tag is deprecated | |
6af98025 | 407 | $qo->fraction = $this->getpath( $question, array('@','fraction'), 0 ) / 100; |
408 | $q0->fraction = $this->getpath( $question, array('#','fraction',0,'#'), $qo->fraction ); | |
c81415c7 | 409 | |
410 | return $qo; | |
411 | } | |
84769fd8 | 412 | |
725ba2a0 | 413 | function import_calculated( $question ) { |
04d8268d | 414 | // import calculated question |
725ba2a0 | 415 | |
416 | // get common parts | |
417 | $qo = $this->import_headers( $question ); | |
418 | ||
04d8268d | 419 | // header parts particular to calculated |
725ba2a0 | 420 | $qo->qtype = CALCULATED ;//CALCULATED; |
505f7342 | 421 | $qo->synchronize = $this->getpath( $question, array( '#','synchronize',0,'#' ), 0 ); |
04d8268d PP |
422 | $single = $this->getpath( $question, array('#','single',0,'#'), 'true' ); |
423 | $qo->single = $this->trans_single( $single ); | |
424 | $shuffleanswers = $this->getpath( $question, array('#','shuffleanswers',0,'#'), 'false' ); | |
425 | $qo->answernumbering = $this->getpath( $question, array('#','answernumbering',0,'#'), 'abc' ); | |
426 | $qo->shuffleanswers = $this->trans_single($shuffleanswers); | |
427 | $qo->correctfeedback = $this->getpath( $question, array('#','correctfeedback',0,'#','text',0,'#'), '', true ); | |
428 | $qo->partiallycorrectfeedback = $this->getpath( $question, array('#','partiallycorrectfeedback',0,'#','text',0,'#'), '', true ); | |
429 | $qo->incorrectfeedback = $this->getpath( $question, array('#','incorrectfeedback',0,'#','text',0,'#'), '', true ); | |
430 | $qo->unitgradingtype = $this->getpath( $question, array('#','unitgradingtype',0,'#'), 0 ); | |
431 | $qo->unitpenalty = $this->getpath( $question, array('#','unitpenalty',0,'#'), 0 ); | |
432 | $qo->showunits = $this->getpath( $question, array('#','showunits',0,'#'), 0 ); | |
433 | $qo->unitsleft = $this->getpath( $question, array('#','unitsleft',0,'#'), 0 ); | |
434 | $qo->instructions = $this->getpath( $question, array('#','instructions',0,'#','text',0,'#'), '', true ); | |
725ba2a0 | 435 | // get answers array |
436 | // echo "<pre> question";print_r($question);echo "</pre>"; | |
437 | $answers = $question['#']['answer']; | |
438 | $qo->answers = array(); | |
439 | $qo->feedback = array(); | |
440 | $qo->fraction = array(); | |
441 | $qo->tolerance = array(); | |
442 | $qo->tolerancetype = array(); | |
443 | $qo->correctanswerformat = array(); | |
444 | $qo->correctanswerlength = array(); | |
445 | $qo->feedback = array(); | |
446 | foreach ($answers as $answer) { | |
447 | // answer outside of <text> is deprecated | |
448 | if (!empty( $answer['#']['text'] )) { | |
449 | $answertext = $this->import_text( $answer['#']['text'] ); | |
450 | } | |
451 | else { | |
452 | $answertext = trim($answer['#'][0]); | |
453 | } | |
454 | if ($answertext == '') { | |
455 | $qo->answers[] = '*'; | |
456 | } else { | |
457 | $qo->answers[] = $answertext; | |
458 | } | |
459 | $qo->feedback[] = $this->import_text( $answer['#']['feedback'][0]['#']['text'] ); | |
460 | $qo->tolerance[] = $answer['#']['tolerance'][0]['#']; | |
461 | // fraction as a tag is deprecated | |
462 | if (!empty($answer['#']['fraction'][0]['#'])) { | |
463 | $qo->fraction[] = $answer['#']['fraction'][0]['#']; | |
464 | } | |
465 | else { | |
466 | $qo->fraction[] = $answer['@']['fraction'] / 100; | |
467 | } | |
468 | $qo->tolerancetype[] = $answer['#']['tolerancetype'][0]['#']; | |
469 | $qo->correctanswerformat[] = $answer['#']['correctanswerformat'][0]['#']; | |
470 | $qo->correctanswerlength[] = $answer['#']['correctanswerlength'][0]['#']; | |
471 | } | |
472 | // get units array | |
473 | $qo->unit = array(); | |
474 | if (isset($question['#']['units'][0]['#']['unit'])) { | |
475 | $units = $question['#']['units'][0]['#']['unit']; | |
476 | $qo->multiplier = array(); | |
477 | foreach ($units as $unit) { | |
478 | $qo->multiplier[] = $unit['#']['multiplier'][0]['#']; | |
479 | $qo->unit[] = $unit['#']['unit_name'][0]['#']; | |
480 | } | |
481 | } | |
482 | $datasets = $question['#']['dataset_definitions'][0]['#']['dataset_definition']; | |
483 | $qo->dataset = array(); | |
484 | $qo->datasetindex= 0 ; | |
485 | foreach ($datasets as $dataset) { | |
486 | $qo->datasetindex++; | |
487 | $qo->dataset[$qo->datasetindex] = new stdClass(); | |
488 | $qo->dataset[$qo->datasetindex]->status = $this->import_text( $dataset['#']['status'][0]['#']['text']); | |
489 | $qo->dataset[$qo->datasetindex]->name = $this->import_text( $dataset['#']['name'][0]['#']['text']); | |
490 | $qo->dataset[$qo->datasetindex]->type = $dataset['#']['type'][0]['#']; | |
491 | $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']); | |
492 | $qo->dataset[$qo->datasetindex]->max = $this->import_text( $dataset['#']['maximum'][0]['#']['text']); | |
493 | $qo->dataset[$qo->datasetindex]->min = $this->import_text( $dataset['#']['minimum'][0]['#']['text']); | |
494 | $qo->dataset[$qo->datasetindex]->length = $this->import_text( $dataset['#']['decimals'][0]['#']['text']); | |
495 | $qo->dataset[$qo->datasetindex]->distribution = $this->import_text( $dataset['#']['distribution'][0]['#']['text']); | |
496 | $qo->dataset[$qo->datasetindex]->itemcount = $dataset['#']['itemcount'][0]['#']; | |
497 | $qo->dataset[$qo->datasetindex]->datasetitem = array(); | |
498 | $qo->dataset[$qo->datasetindex]->itemindex = 0; | |
499 | $qo->dataset[$qo->datasetindex]->number_of_items=$dataset['#']['number_of_items'][0]['#']; | |
500 | $datasetitems = $dataset['#']['dataset_items'][0]['#']['dataset_item']; | |
501 | foreach ($datasetitems as $datasetitem) { | |
502 | $qo->dataset[$qo->datasetindex]->itemindex++; | |
503 | $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex] = new stdClass(); | |
504 | $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->itemnumber = $datasetitem['#']['number'][0]['#']; //[0]['#']['number'][0]['#'] ; // [0]['numberitems'] ;//['#']['number'][0]['#'];// $datasetitems['#']['number'][0]['#']; | |
505 | $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->value = $datasetitem['#']['value'][0]['#'] ;//$datasetitem['#']['value'][0]['#']; | |
88bc20c3 | 506 | } |
725ba2a0 | 507 | } |
88bc20c3 | 508 | |
5fd8f999 | 509 | // echo "<pre>loaded qo";print_r($qo);echo "</pre>"; |
725ba2a0 | 510 | |
511 | return $qo; | |
512 | } | |
513 | ||
ee259d0c | 514 | /** |
515 | * this is not a real question type. It's a dummy type used | |
516 | * to specify the import category | |
517 | * format is: | |
518 | * <question type="category"> | |
519 | * <category>tom/dick/harry</category> | |
520 | * </question> | |
521 | */ | |
522 | function import_category( $question ) { | |
0c6b4d2e | 523 | $qo = new stdClass; |
ee259d0c | 524 | $qo->qtype = 'category'; |
86b68520 | 525 | $qo->category = $this->import_text($question['#']['category'][0]['#']['text']); |
ee259d0c | 526 | return $qo; |
527 | } | |
528 | ||
c81415c7 | 529 | /** |
530 | * parse the array of lines into an array of questions | |
531 | * this *could* burn memory - but it won't happen that much | |
532 | * so fingers crossed! | |
6e557c08 | 533 | * @param array lines array of lines from the input file |
534 | * @return array (of objects) question objects | |
c81415c7 | 535 | */ |
536 | function readquestions($lines) { | |
84769fd8 | 537 | // we just need it as one big string |
538 | $text = implode($lines, " "); | |
539 | unset( $lines ); | |
540 | ||
541 | // this converts xml to big nasty data structure | |
542 | // the 0 means keep white space as it is (important for markdown format) | |
543 | // print_r it if you want to see what it looks like! | |
88bc20c3 | 544 | $xml = xmlize( $text, 0 ); |
84769fd8 | 545 | |
546 | // set up array to hold all our questions | |
547 | $questions = array(); | |
548 | ||
549 | // iterate through questions | |
550 | foreach ($xml['quiz']['#']['question'] as $question) { | |
551 | $question_type = $question['@']['type']; | |
552 | $questiontype = get_string( 'questiontype','quiz',$question_type ); | |
84769fd8 | 553 | |
554 | if ($question_type=='multichoice') { | |
555 | $qo = $this->import_multichoice( $question ); | |
88bc20c3 | 556 | } |
84769fd8 | 557 | elseif ($question_type=='truefalse') { |
558 | $qo = $this->import_truefalse( $question ); | |
559 | } | |
560 | elseif ($question_type=='shortanswer') { | |
561 | $qo = $this->import_shortanswer( $question ); | |
562 | } | |
563 | elseif ($question_type=='numerical') { | |
564 | $qo = $this->import_numerical( $question ); | |
565 | } | |
7b8bc256 | 566 | elseif ($question_type=='description') { |
567 | $qo = $this->import_description( $question ); | |
568 | } | |
51bcdf28 | 569 | elseif ($question_type=='matching') { |
570 | $qo = $this->import_matching( $question ); | |
571 | } | |
7b8bc256 | 572 | elseif ($question_type=='cloze') { |
c81415c7 | 573 | $qo = $this->import_multianswer( $question ); |
574 | } | |
575 | elseif ($question_type=='essay') { | |
576 | $qo = $this->import_essay( $question ); | |
7b8bc256 | 577 | } |
725ba2a0 | 578 | elseif ($question_type=='calculated') { |
579 | $qo = $this->import_calculated( $question ); | |
580 | } | |
9c1c6c7f | 581 | elseif ($question_type=='calculatedsimple') { |
582 | $qo = $this->import_calculated( $question ); | |
583 | $qo->qtype = CALCULATEDSIMPLE ; | |
584 | } | |
04d8268d PP |
585 | elseif ($question_type=='calculatedmulti') { |
586 | $qo = $this->import_calculated( $question ); | |
587 | $qo->qtype = CALCULATEDMULTI ; | |
588 | } | |
ee259d0c | 589 | elseif ($question_type=='category') { |
590 | $qo = $this->import_category( $question ); | |
591 | } | |
84769fd8 | 592 | else { |
a41e3287 | 593 | // try for plugin support |
88bc20c3 | 594 | // no default question, as the plugin can call |
a41e3287 | 595 | // import_headers() itself if it wants to |
88bc20c3 | 596 | if (!$qo = $this->try_importing_using_qtypes( $question, null, null, $question_type)) { |
a41e3287 | 597 | $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type ); |
598 | $this->error( $notsupported ); | |
599 | $qo = null; | |
600 | } | |
84769fd8 | 601 | } |
602 | ||
603 | // stick the result in the $questions array | |
604 | if ($qo) { | |
605 | $questions[] = $qo; | |
606 | } | |
607 | } | |
84769fd8 | 608 | return $questions; |
609 | } | |
610 | ||
611 | // EXPORT FUNCTIONS START HERE | |
612 | ||
84769fd8 | 613 | function export_file_extension() { |
614 | // override default type so extension is .xml | |
88bc20c3 | 615 | |
84769fd8 | 616 | return ".xml"; |
617 | } | |
618 | ||
c81415c7 | 619 | |
620 | /** | |
621 | * Turn the internal question code into a human readable form | |
622 | * (The code used to be numeric, but this remains as some of | |
623 | * the names don't match the new internal format) | |
6e557c08 | 624 | * @param mixed type_id Internal code |
625 | * @return string question type string | |
c81415c7 | 626 | */ |
84769fd8 | 627 | function get_qtype( $type_id ) { |
84769fd8 | 628 | switch( $type_id ) { |
629 | case TRUEFALSE: | |
630 | $name = 'truefalse'; | |
631 | break; | |
632 | case MULTICHOICE: | |
633 | $name = 'multichoice'; | |
634 | break; | |
635 | case SHORTANSWER: | |
636 | $name = 'shortanswer'; | |
637 | break; | |
638 | case NUMERICAL: | |
639 | $name = 'numerical'; | |
640 | break; | |
641 | case MATCH: | |
642 | $name = 'matching'; | |
643 | break; | |
644 | case DESCRIPTION: | |
645 | $name = 'description'; | |
646 | break; | |
647 | case MULTIANSWER: | |
648 | $name = 'cloze'; | |
649 | break; | |
c81415c7 | 650 | case ESSAY: |
651 | $name = 'essay'; | |
652 | break; | |
725ba2a0 | 653 | case CALCULATED: |
654 | $name = 'calculated'; | |
655 | break; | |
9c1c6c7f | 656 | case CALCULATEDSIMPLE: |
657 | $name = 'calculatedsimple'; | |
658 | break; | |
04d8268d PP |
659 | case CALCULATEDMULTI: |
660 | $name = 'calculatedmulti'; | |
661 | break; | |
84769fd8 | 662 | default: |
a41e3287 | 663 | $name = false; |
84769fd8 | 664 | } |
665 | return $name; | |
666 | } | |
667 | ||
6e557c08 | 668 | /** |
c81415c7 | 669 | * Convert internal Moodle text format code into |
670 | * human readable form | |
6e557c08 | 671 | * @param int id internal code |
672 | * @return string format text | |
c81415c7 | 673 | */ |
84769fd8 | 674 | function get_format( $id ) { |
84769fd8 | 675 | switch( $id ) { |
676 | case 0: | |
677 | $name = "moodle_auto_format"; | |
678 | break; | |
679 | case 1: | |
680 | $name = "html"; | |
681 | break; | |
682 | case 2: | |
683 | $name = "plain_text"; | |
684 | break; | |
685 | case 3: | |
686 | $name = "wiki_like"; | |
687 | break; | |
688 | case 4: | |
689 | $name = "markdown"; | |
690 | break; | |
691 | default: | |
692 | $name = "unknown"; | |
693 | } | |
694 | return $name; | |
695 | } | |
696 | ||
6e557c08 | 697 | /** |
88bc20c3 | 698 | * Convert internal single question code into |
c81415c7 | 699 | * human readable form |
6e557c08 | 700 | * @param int id single question code |
701 | * @return string single question string | |
c81415c7 | 702 | */ |
84769fd8 | 703 | function get_single( $id ) { |
84769fd8 | 704 | switch( $id ) { |
705 | case 0: | |
706 | $name = "false"; | |
707 | break; | |
708 | case 1: | |
709 | $name = "true"; | |
710 | break; | |
711 | default: | |
712 | $name = "unknown"; | |
713 | } | |
714 | return $name; | |
715 | } | |
716 | ||
6e557c08 | 717 | /** |
88bc20c3 | 718 | * generates <text></text> tags, processing raw text therein |
6e557c08 | 719 | * @param int ilev the current indent level |
720 | * @param boolean short stick it on one line | |
721 | * @return string formatted text | |
c81415c7 | 722 | */ |
84769fd8 | 723 | function writetext( $raw, $ilev=0, $short=true) { |
84769fd8 | 724 | $indent = str_repeat( " ",$ilev ); |
725 | ||
3695cfa9 | 726 | // if required add CDATA tags |
727 | if (!empty($raw) and (htmlspecialchars($raw)!=$raw)) { | |
728 | $raw = "<![CDATA[$raw]]>"; | |
729 | } | |
84769fd8 | 730 | |
731 | if ($short) { | |
732 | $xml = "$indent<text>$raw</text>\n"; | |
733 | } | |
734 | else { | |
735 | $xml = "$indent<text>\n$raw\n$indent</text>\n"; | |
736 | } | |
737 | ||
738 | return $xml; | |
739 | } | |
88bc20c3 | 740 | |
81a7a02b | 741 | function xmltidy( $content ) { |
742 | // can only do this if tidy is installed | |
743 | if (extension_loaded('tidy')) { | |
744 | $config = array( 'input-xml'=>true, 'output-xml'=>true, 'indent'=>true, 'wrap'=>0 ); | |
745 | $tidy = new tidy; | |
746 | $tidy->parseString($content, $config, 'utf8'); | |
88bc20c3 | 747 | $tidy->cleanRepair(); |
81a7a02b | 748 | return $tidy->value; |
749 | } | |
750 | else { | |
751 | return $content; | |
752 | } | |
753 | } | |
754 | ||
84769fd8 | 755 | |
756 | function presave_process( $content ) { | |
757 | // override method to allow us to add xml headers and footers | |
758 | ||
81a7a02b | 759 | // add the xml headers and footers |
46013523 | 760 | $content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . |
84769fd8 | 761 | "<quiz>\n" . |
762 | $content . "\n" . | |
763 | "</quiz>"; | |
764 | ||
81a7a02b | 765 | // make the xml look nice |
766 | $content = $this->xmltidy( $content ); | |
767 | ||
84769fd8 | 768 | return $content; |
769 | } | |
770 | ||
6e557c08 | 771 | /** |
c81415c7 | 772 | * Include an image encoded in base 64 |
6e557c08 | 773 | * @param string imagepath The location of the image file |
88bc20c3 | 774 | * @return string xml code segment |
c81415c7 | 775 | */ |
d08e16b2 | 776 | function writeimage( $imagepath ) { |
d08e16b2 | 777 | global $CFG; |
88bc20c3 | 778 | |
d08e16b2 | 779 | if (empty($imagepath)) { |
780 | return ''; | |
781 | } | |
782 | ||
783 | $courseid = $this->course->id; | |
784 | if (!$binary = file_get_contents( "{$CFG->dataroot}/$courseid/$imagepath" )) { | |
785 | return ''; | |
786 | } | |
787 | ||
294ce987 | 788 | $content = " <image_base64>\n".base64_encode( $binary )."\n". |
d08e16b2 | 789 | "\n </image_base64>\n"; |
790 | return $content; | |
791 | } | |
792 | ||
6e557c08 | 793 | /** |
c81415c7 | 794 | * Turns question into an xml segment |
6e557c08 | 795 | * @param array question question array |
796 | * @return string xml segment | |
c81415c7 | 797 | */ |
84769fd8 | 798 | function writequestion( $question ) { |
fef8f84e | 799 | global $CFG,$QTYPES, $OUTPUT; |
84769fd8 | 800 | // initial string; |
801 | $expout = ""; | |
802 | ||
803 | // add comment | |
804 | $expout .= "\n\n<!-- question: $question->id -->\n"; | |
805 | ||
a41e3287 | 806 | // check question type |
807 | if (!$question_type = $this->get_qtype( $question->qtype )) { | |
808 | // must be a plugin then, so just accept the name supplied | |
809 | $question_type = $question->qtype; | |
46013523 | 810 | } |
811 | ||
84769fd8 | 812 | // add opening tag |
f1abd39f | 813 | // generates specific header for Cloze and category type question |
814 | if ($question->qtype == 'category') { | |
6f8481ec | 815 | $categorypath = $this->writetext( $question->category ); |
f1abd39f | 816 | $expout .= " <question type=\"category\">\n"; |
817 | $expout .= " <category>\n"; | |
6f8481ec | 818 | $expout .= " $categorypath\n"; |
f1abd39f | 819 | $expout .= " </category>\n"; |
820 | $expout .= " </question>\n"; | |
821 | return $expout; | |
88bc20c3 | 822 | } |
f1abd39f | 823 | elseif ($question->qtype != MULTIANSWER) { |
7b8bc256 | 824 | // for all question types except Close |
7b8bc256 | 825 | $name_text = $this->writetext( $question->name ); |
826 | $qtformat = $this->get_format($question->questiontextformat); | |
827 | $question_text = $this->writetext( $question->questiontext ); | |
a4514d91 | 828 | $generalfeedback = $this->writetext( $question->generalfeedback ); |
88bc20c3 | 829 | $expout .= " <question type=\"$question_type\">\n"; |
7b8bc256 | 830 | $expout .= " <name>$name_text</name>\n"; |
831 | $expout .= " <questiontext format=\"$qtformat\">\n"; | |
832 | $expout .= $question_text; | |
88bc20c3 | 833 | $expout .= " </questiontext>\n"; |
7b8bc256 | 834 | $expout .= " <image>{$question->image}</image>\n"; |
835 | $expout .= $this->writeimage($question->image); | |
a4514d91 | 836 | $expout .= " <generalfeedback>\n"; |
837 | $expout .= $generalfeedback; | |
838 | $expout .= " </generalfeedback>\n"; | |
c81415c7 | 839 | $expout .= " <defaultgrade>{$question->defaultgrade}</defaultgrade>\n"; |
7b8bc256 | 840 | $expout .= " <penalty>{$question->penalty}</penalty>\n"; |
841 | $expout .= " <hidden>{$question->hidden}</hidden>\n"; | |
842 | } | |
843 | else { | |
844 | // for Cloze type only | |
7b8bc256 | 845 | $name_text = $this->writetext( $question->name ); |
846 | $question_text = $this->writetext( $question->questiontext ); | |
4ab4dcb2 | 847 | $generalfeedback = $this->writetext( $question->generalfeedback ); |
7b8bc256 | 848 | $expout .= " <question type=\"$question_type\">\n"; |
849 | $expout .= " <name>$name_text</name>\n"; | |
850 | $expout .= " <questiontext>\n"; | |
851 | $expout .= $question_text; | |
852 | $expout .= " </questiontext>\n"; | |
4ab4dcb2 | 853 | $expout .= " <generalfeedback>\n"; |
854 | $expout .= $generalfeedback; | |
855 | $expout .= " </generalfeedback>\n"; | |
7b8bc256 | 856 | } |
857 | ||
d08e16b2 | 858 | if (!empty($question->options->shuffleanswers)) { |
859 | $expout .= " <shuffleanswers>{$question->options->shuffleanswers}</shuffleanswers>\n"; | |
860 | } | |
861 | else { | |
862 | $expout .= " <shuffleanswers>0</shuffleanswers>\n"; | |
863 | } | |
84769fd8 | 864 | |
865 | // output depends on question type | |
866 | switch($question->qtype) { | |
f1abd39f | 867 | case 'category': |
868 | // not a qtype really - dummy used for category switching | |
88bc20c3 | 869 | break; |
84769fd8 | 870 | case TRUEFALSE: |
36e2232e | 871 | foreach ($question->options->answers as $answer) { |
872 | $fraction_pc = round( $answer->fraction * 100 ); | |
3246ed33 | 873 | if ($answer->id == $question->options->trueanswer) { |
874 | $answertext = 'true'; | |
875 | } else { | |
876 | $answertext = 'false'; | |
88bc20c3 | 877 | } |
36e2232e | 878 | $expout .= " <answer fraction=\"$fraction_pc\">\n"; |
3246ed33 | 879 | $expout .= $this->writetext($answertext, 3) . "\n"; |
36e2232e | 880 | $expout .= " <feedback>\n"; |
881 | $expout .= $this->writetext( $answer->feedback,4,false ); | |
882 | $expout .= " </feedback>\n"; | |
883 | $expout .= " </answer>\n"; | |
884 | } | |
84769fd8 | 885 | break; |
886 | case MULTICHOICE: | |
887 | $expout .= " <single>".$this->get_single($question->options->single)."</single>\n"; | |
307f045f | 888 | $expout .= " <shuffleanswers>".$this->get_single($question->options->shuffleanswers)."</shuffleanswers>\n"; |
889 | $expout .= " <correctfeedback>".$this->writetext($question->options->correctfeedback, 3)."</correctfeedback>\n"; | |
890 | $expout .= " <partiallycorrectfeedback>".$this->writetext($question->options->partiallycorrectfeedback, 3)."</partiallycorrectfeedback>\n"; | |
891 | $expout .= " <incorrectfeedback>".$this->writetext($question->options->incorrectfeedback, 3)."</incorrectfeedback>\n"; | |
04d8268d | 892 | $expout .= " <answernumbering>".$this->writetext($question->options->answernumbering, 3)."</answernumbering>\n"; |
84769fd8 | 893 | foreach($question->options->answers as $answer) { |
894 | $percent = $answer->fraction * 100; | |
895 | $expout .= " <answer fraction=\"$percent\">\n"; | |
896 | $expout .= $this->writetext( $answer->answer,4,false ); | |
897 | $expout .= " <feedback>\n"; | |
6e557c08 | 898 | $expout .= $this->writetext( $answer->feedback,5,false ); |
84769fd8 | 899 | $expout .= " </feedback>\n"; |
900 | $expout .= " </answer>\n"; | |
901 | } | |
902 | break; | |
903 | case SHORTANSWER: | |
904 | $expout .= " <usecase>{$question->options->usecase}</usecase>\n "; | |
905 | foreach($question->options->answers as $answer) { | |
906 | $percent = 100 * $answer->fraction; | |
907 | $expout .= " <answer fraction=\"$percent\">\n"; | |
908 | $expout .= $this->writetext( $answer->answer,3,false ); | |
909 | $expout .= " <feedback>\n"; | |
910 | $expout .= $this->writetext( $answer->feedback,4,false ); | |
911 | $expout .= " </feedback>\n"; | |
912 | $expout .= " </answer>\n"; | |
913 | } | |
914 | break; | |
915 | case NUMERICAL: | |
916 | foreach ($question->options->answers as $answer) { | |
917 | $tolerance = $answer->tolerance; | |
55c54868 | 918 | $percent = 100 * $answer->fraction; |
919 | $expout .= "<answer fraction=\"$percent\">\n"; | |
920 | // <text> tags are an added feature, old filed won't have them | |
921 | $expout .= " <text>{$answer->answer}</text>\n"; | |
84769fd8 | 922 | $expout .= " <tolerance>$tolerance</tolerance>\n"; |
923 | $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n"; | |
55c54868 | 924 | // fraction tag is deprecated |
925 | // $expout .= " <fraction>{$answer->fraction}</fraction>\n"; | |
84769fd8 | 926 | $expout .= "</answer>\n"; |
927 | } | |
928 | ||
929 | $units = $question->options->units; | |
930 | if (count($units)) { | |
931 | $expout .= "<units>\n"; | |
932 | foreach ($units as $unit) { | |
933 | $expout .= " <unit>\n"; | |
934 | $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n"; | |
935 | $expout .= " <unit_name>{$unit->unit}</unit_name>\n"; | |
936 | $expout .= " </unit>\n"; | |
937 | } | |
938 | $expout .= "</units>\n"; | |
939 | } | |
940 | break; | |
941 | case MATCH: | |
942 | foreach($question->options->subquestions as $subquestion) { | |
943 | $expout .= "<subquestion>\n"; | |
944 | $expout .= $this->writetext( $subquestion->questiontext ); | |
945 | $expout .= "<answer>".$this->writetext( $subquestion->answertext )."</answer>\n"; | |
946 | $expout .= "</subquestion>\n"; | |
947 | } | |
948 | break; | |
949 | case DESCRIPTION: | |
c81415c7 | 950 | // nothing more to do for this type |
84769fd8 | 951 | break; |
952 | case MULTIANSWER: | |
7b8bc256 | 953 | $a_count=1; |
954 | foreach($question->options->questions as $question) { | |
294ce987 | 955 | $thispattern = addslashes("{#".$a_count."}"); // TODO: fix this addslashes |
7b8bc256 | 956 | $thisreplace = $question->questiontext; |
6dbcacee | 957 | $expout=preg_replace("~$thispattern~", $thisreplace, $expout ); |
7b8bc256 | 958 | $a_count++; |
959 | } | |
960 | break; | |
c81415c7 | 961 | case ESSAY: |
33ed59a2 | 962 | if (!empty($question->options->answers)) { |
963 | foreach ($question->options->answers as $answer) { | |
964 | $percent = 100 * $answer->fraction; | |
965 | $expout .= "<answer fraction=\"$percent\">\n"; | |
966 | $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n"; | |
967 | // fraction tag is deprecated | |
968 | // $expout .= " <fraction>{$answer->fraction}</fraction>\n"; | |
969 | $expout .= "</answer>\n"; | |
970 | } | |
c81415c7 | 971 | } |
725ba2a0 | 972 | break; |
973 | case CALCULATED: | |
9c1c6c7f | 974 | case CALCULATEDSIMPLE: |
04d8268d PP |
975 | case CALCULATEDMULTI: |
976 | $expout .= " <synchronize>{$question->options->synchronize}</synchronize>\n"; | |
977 | $expout .= " <single>{$question->options->single}</single>\n"; | |
978 | $expout .= " <answernumbering>{$question->options->answernumbering}</answernumbering>\n"; | |
979 | $expout .= " <shuffleanswers>".$this->writetext($question->options->shuffleanswers, 3)."</shuffleanswers>\n"; | |
980 | $expout .= " <correctfeedback>".$this->writetext($question->options->correctfeedback, 3)."</correctfeedback>\n"; | |
981 | $expout .= " <partiallycorrectfeedback>".$this->writetext($question->options->partiallycorrectfeedback, 3)."</partiallycorrectfeedback>\n"; | |
982 | $expout .= " <incorrectfeedback>".$this->writetext($question->options->incorrectfeedback, 3)."</incorrectfeedback>\n"; | |
725ba2a0 | 983 | foreach ($question->options->answers as $answer) { |
984 | $tolerance = $answer->tolerance; | |
985 | $tolerancetype = $answer->tolerancetype; | |
986 | $correctanswerlength= $answer->correctanswerlength ; | |
987 | $correctanswerformat= $answer->correctanswerformat; | |
988 | $percent = 100 * $answer->fraction; | |
989 | $expout .= "<answer fraction=\"$percent\">\n"; | |
990 | // "<text/>" tags are an added feature, old files won't have them | |
991 | $expout .= " <text>{$answer->answer}</text>\n"; | |
992 | $expout .= " <tolerance>$tolerance</tolerance>\n"; | |
993 | $expout .= " <tolerancetype>$tolerancetype</tolerancetype>\n"; | |
994 | $expout .= " <correctanswerformat>$correctanswerformat</correctanswerformat>\n"; | |
a800639b | 995 | $expout .= " <correctanswerlength>$correctanswerlength</correctanswerlength>\n"; |
725ba2a0 | 996 | $expout .= " <feedback>".$this->writetext( $answer->feedback )."</feedback>\n"; |
997 | $expout .= "</answer>\n"; | |
998 | } | |
04d8268d PP |
999 | $expout .= " <unitgradingtype>{$question->options->unitgradingtype}</unitgradingtype>\n"; |
1000 | $expout .= " <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n"; | |
1001 | $expout .= " <showunits>{$question->options->showunits}</showunits>\n"; | |
1002 | $expout .= " <unitsleft>{$question->options->unitsleft}</unitsleft>\n"; | |
1003 | $expout .= " <instructions>".$this->writetext($question->options->instructions, 3)."</instructions>\n"; | |
725ba2a0 | 1004 | $units = $question->options->units; |
1005 | if (count($units)) { | |
1006 | $expout .= "<units>\n"; | |
1007 | foreach ($units as $unit) { | |
1008 | $expout .= " <unit>\n"; | |
1009 | $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n"; | |
1010 | $expout .= " <unit_name>{$unit->unit}</unit_name>\n"; | |
1011 | $expout .= " </unit>\n"; | |
1012 | } | |
1013 | $expout .= "</units>\n"; | |
88bc20c3 | 1014 | } |
9c1c6c7f | 1015 | //The tag $question->export_process has been set so we get all the data items in the database |
1016 | // from the function $QTYPES['calculated']->get_question_options(&$question); | |
1017 | // calculatedsimple defaults to calculated | |
c9e4ba36 | 1018 | if( isset($question->options->datasets)&&count($question->options->datasets)){// there should be |
725ba2a0 | 1019 | $expout .= "<dataset_definitions>\n"; |
c9e4ba36 | 1020 | foreach ($question->options->datasets as $def) { |
725ba2a0 | 1021 | $expout .= "<dataset_definition>\n"; |
1022 | $expout .= " <status>".$this->writetext($def->status)."</status>\n"; | |
1023 | $expout .= " <name>".$this->writetext($def->name)."</name>\n"; | |
9c1c6c7f | 1024 | if ( $question->qtype == CALCULATED){ |
1025 | $expout .= " <type>calculated</type>\n"; | |
1026 | }else { | |
1027 | $expout .= " <type>calculatedsimple</type>\n"; | |
1028 | } | |
725ba2a0 | 1029 | $expout .= " <distribution>".$this->writetext($def->distribution)."</distribution>\n"; |
1030 | $expout .= " <minimum>".$this->writetext($def->minimum)."</minimum>\n"; | |
1031 | $expout .= " <maximum>".$this->writetext($def->maximum)."</maximum>\n"; | |
88bc20c3 | 1032 | $expout .= " <decimals>".$this->writetext($def->decimals)."</decimals>\n"; |
725ba2a0 | 1033 | $expout .= " <itemcount>$def->itemcount</itemcount>\n"; |
1034 | if ($def->itemcount > 0 ) { | |
1035 | $expout .= " <dataset_items>\n"; | |
1036 | foreach ($def->items as $item ){ | |
1037 | $expout .= " <dataset_item>\n"; | |
1038 | $expout .= " <number>".$item->itemnumber."</number>\n"; | |
1039 | $expout .= " <value>".$item->value."</value>\n"; | |
1040 | $expout .= " </dataset_item>\n"; | |
88bc20c3 | 1041 | } |
725ba2a0 | 1042 | $expout .= " </dataset_items>\n"; |
1043 | $expout .= " <number_of_items>".$def-> number_of_items."</number_of_items>\n"; | |
1044 | } | |
1045 | $expout .= "</dataset_definition>\n"; | |
88bc20c3 | 1046 | } |
1047 | $expout .= "</dataset_definitions>\n"; | |
1048 | } | |
c81415c7 | 1049 | break; |
84769fd8 | 1050 | default: |
a41e3287 | 1051 | // try support by optional plugin |
88bc20c3 | 1052 | if (!$data = $this->try_exporting_using_qtypes( $question->qtype, $question )) { |
fef8f84e | 1053 | echo $OUTPUT->notification( get_string( 'unsupportedexport','qformat_xml',$QTYPES[$question->qtype]->local_name() ) ); |
a41e3287 | 1054 | } |
392238d6 | 1055 | $expout .= $data; |
84769fd8 | 1056 | } |
1057 | ||
1058 | // close the question tag | |
1059 | $expout .= "</question>\n"; | |
1060 | ||
84769fd8 | 1061 | return $expout; |
1062 | } | |
1063 | } | |
1064 | ||
1065 | ?> |