Merge branch 'MDL-70966-master' of git://github.com/marinaglancy/moodle
[moodle.git] / question / format / xml / tests / xmlformat_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for the Moodle XML format.
19  *
20  * @package    qformat_xml
21  * @copyright  2010 The Open University
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir . '/questionlib.php');
30 require_once($CFG->dirroot . '/question/format/xml/format.php');
31 require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
34 /**
35  * Unit tests for the matching question definition class.
36  *
37  * @copyright  2009 The Open University
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class qformat_xml_test extends question_testcase {
41     public function assert_same_xml($expectedxml, $xml) {
42         $this->assertEquals(str_replace("\r\n", "\n", $expectedxml),
43                 str_replace("\r\n", "\n", $xml));
44     }
46     public function make_test_question() {
47         global $USER;
48         $q = new stdClass();
49         $q->id = 0;
50         $q->contextid = 0;
51         $q->idnumber = null;
52         $q->category = 0;
53         $q->parent = 0;
54         $q->questiontextformat = FORMAT_HTML;
55         $q->generalfeedbackformat = FORMAT_HTML;
56         $q->defaultmark = 1;
57         $q->penalty = 0.3333333;
58         $q->length = 1;
59         $q->stamp = make_unique_id_code();
60         $q->version = make_unique_id_code();
61         $q->hidden = 0;
62         $q->timecreated = time();
63         $q->timemodified = time();
64         $q->createdby = $USER->id;
65         $q->modifiedby = $USER->id;
66         return $q;
67     }
69     /**
70      * The data the XML import format sends to save_question is not exactly
71      * the same as the data returned from the editing form, so this method
72      * makes necessary changes to the return value of
73      * test_question_maker::get_question_form_data so that the tests can work.
74      * @param object $expectedq as returned by get_question_form_data.
75      * @return object one more likely to match the return value of import_...().
76      */
77     public function remove_irrelevant_form_data_fields($expectedq) {
78         return $this->itemid_to_files($expectedq);
79     }
81     /**
82      * Becuase XML import uses a files array instead of an itemid integer to
83      * handle saving files with a question, we need to covert the output of
84      * test_question_maker::get_question_form_data to match. This method recursively
85      * replaces all array elements with key itemid with an array entry with
86      * key files and value an empty array.
87      *
88      * @param mixed $var any data structure.
89      * @return mixed an equivalent structure with the relacements made.
90      */
91     protected function itemid_to_files($var) {
92         if (is_object($var)) {
93             $newvar = new stdClass();
94             foreach (get_object_vars($var) as $field => $value) {
95                 $newvar->$field = $this->itemid_to_files($value);
96             }
98         } else if (is_array($var)) {
99             $newvar = array();
100             foreach ($var as $index => $value) {
101                 if ($index === 'itemid') {
102                     $newvar['files'] = array();
103                 } else {
104                     $newvar[$index] = $this->itemid_to_files($value);
105                 }
106             }
108         } else {
109             $newvar = $var;
110         }
112         return $newvar;
113     }
115     public function test_xml_escape_simple_input_not_escaped() {
116         $exporter = new qformat_xml();
117         $string = 'Nothing funny here. Even if we go to a café or to 日本.';
118         $this->assertEquals($string, $exporter->xml_escape($string));
119     }
121     public function test_xml_escape_html_wrapped_in_cdata() {
122         $exporter = new qformat_xml();
123         $string = '<p>Nothing <b>funny<b> here. Even if we go to a café or to 日本.</p>';
124         $this->assertEquals('<![CDATA[' . $string . ']]>', $exporter->xml_escape($string));
125     }
127     public function test_xml_escape_script_tag_handled_ok() {
128         $exporter = new qformat_xml();
129         $input = '<script><![CDATA[alert(1<2);]]></script>';
130         $expected = '<![CDATA[<script><![CDATA[alert(1<2);]]]]><![CDATA[></script>]]>';
131         $this->assertEquals($expected, $exporter->xml_escape($input));
133         // Check that parsing the expected result does give the input again.
134         $parsed = simplexml_load_string('<div>' . $expected . '</div>');
135         $this->assertEquals($input, $parsed->xpath('//div')[0]);
136     }
138     public function test_xml_escape_code_that_looks_like_cdata_end_ok() {
139         $exporter = new qformat_xml();
140         $input = "if (x[[0]]>a) print('hah');";
141         $expected = "<![CDATA[if (x[[0]]]]><![CDATA[>a) print('hah');]]>";
142         $this->assertEquals($expected, $exporter->xml_escape($input));
144         // Check that parsing the expected result does give the input again.
145         $parsed = simplexml_load_string('<div>' . $expected . '</div>');
146         $this->assertEquals($input, $parsed->xpath('//div')[0]);
147     }
149     public function test_write_hint_basic() {
150         $q = $this->make_test_question();
151         $q->contextid = \context_system::instance()->id;
152         $q->name = 'Short answer question';
153         $q->questiontext = 'Name an amphibian: __________';
154         $q->generalfeedback = 'Generalfeedback: frog or toad would have been OK.';
155         if (!isset($q->options)) {
156             $q->options = new stdClass();
157         }
158         $q->options->usecase = false;
159         $q->options->answers = array(
160             13 => new question_answer(13, 'frog', 1.0, 'Frog is a very good answer.', FORMAT_HTML),
161             14 => new question_answer(14, 'toad', 0.8, 'Toad is an OK good answer.', FORMAT_HTML),
162             15 => new question_answer(15, '*', 0.0, 'That is a bad answer.', FORMAT_HTML),
163         );
164         $q->qtype = 'shortanswer';
165         $q->hints = array(
166             new question_hint(0, 'This is the first hint.', FORMAT_MOODLE),
167         );
169         $exporter = new qformat_xml();
170         $xml = $exporter->writequestion($q);
172         $this->assertRegExp('|<hint format=\"moodle_auto_format\">\s*<text>\s*' .
173                 'This is the first hint\.\s*</text>\s*</hint>|', $xml);
174         $this->assertNotRegExp('|<shownumcorrect/>|', $xml);
175         $this->assertNotRegExp('|<clearwrong/>|', $xml);
176         $this->assertNotRegExp('|<options>|', $xml);
177     }
179     public function test_write_hint_with_parts() {
180         $q = $this->make_test_question();
181         $q->contextid = \context_system::instance()->id;
182         $q->name = 'Matching question';
183         $q->questiontext = 'Classify the animals.';
184         $q->generalfeedback = 'Frogs and toads are amphibians, the others are mammals.';
185         $q->qtype = 'match';
187         if (!isset($q->options)) {
188             $q->options = new stdClass();
189         }
190         $q->options->shuffleanswers = 1;
191         $q->options->correctfeedback = '';
192         $q->options->correctfeedbackformat = FORMAT_HTML;
193         $q->options->partiallycorrectfeedback = '';
194         $q->options->partiallycorrectfeedbackformat = FORMAT_HTML;
195         $q->options->incorrectfeedback = '';
196         $q->options->incorrectfeedbackformat = FORMAT_HTML;
198         $q->options->subquestions = array();
199         $q->hints = array(
200             new question_hint_with_parts(0, 'This is the first hint.', FORMAT_HTML, false, true),
201             new question_hint_with_parts(0, 'This is the second hint.', FORMAT_HTML, true, false),
202         );
204         $exporter = new qformat_xml();
205         $xml = $exporter->writequestion($q);
207         $this->assertRegExp(
208                 '|<hint format=\"html\">\s*<text>\s*This is the first hint\.\s*</text>|', $xml);
209         $this->assertRegExp(
210                 '|<hint format=\"html\">\s*<text>\s*This is the second hint\.\s*</text>|', $xml);
211         list($ignored, $hint1, $hint2) = explode('<hint', $xml);
212         $this->assertNotRegExp('|<shownumcorrect/>|', $hint1);
213         $this->assertRegExp('|<clearwrong/>|', $hint1);
214         $this->assertRegExp('|<shownumcorrect/>|', $hint2);
215         $this->assertNotRegExp('|<clearwrong/>|', $hint2);
216         $this->assertNotRegExp('|<options>|', $xml);
217     }
219     public function test_import_hints_no_parts() {
220         $xml = <<<END
221 <question>
222     <hint>
223         <text>This is the first hint</text>
224         <clearwrong/>
225     </hint>
226     <hint>
227         <text>This is the second hint</text>
228         <shownumcorrect/>
229     </hint>
230 </question>
231 END;
233         $questionxml = xmlize($xml);
234         $qo = new stdClass();
236         $importer = new qformat_xml();
237         $importer->import_hints($qo, $questionxml['question'], false, false, 'html');
239         $this->assertEquals(array(
240                 array('text' => 'This is the first hint',
241                         'format' => FORMAT_HTML),
242                 array('text' => 'This is the second hint',
243                         'format' => FORMAT_HTML),
244                 ), $qo->hint);
245         $this->assertFalse(isset($qo->hintclearwrong));
246         $this->assertFalse(isset($qo->hintshownumcorrect));
247     }
249     public function test_import_hints_with_parts() {
250         $xml = <<<END
251 <question>
252     <hint>
253         <text>This is the first hint</text>
254         <clearwrong/>
255     </hint>
256     <hint>
257         <text>This is the second hint</text>
258         <shownumcorrect/>
259     </hint>
260 </question>
261 END;
263         $questionxml = xmlize($xml);
264         $qo = new stdClass();
266         $importer = new qformat_xml();
267         $importer->import_hints($qo, $questionxml['question'], true, true, 'html');
269         $this->assertEquals(array(
270                 array('text' => 'This is the first hint',
271                         'format' => FORMAT_HTML),
272                 array('text' => 'This is the second hint',
273                         'format' => FORMAT_HTML),
274                 ), $qo->hint);
275         $this->assertEquals(array(1, 0), $qo->hintclearwrong);
276         $this->assertEquals(array(0, 1), $qo->hintshownumcorrect);
277     }
279     public function test_import_no_hints_no_error() {
280         $xml = <<<END
281 <question>
282 </question>
283 END;
285         $questionxml = xmlize($xml);
286         $qo = new stdClass();
288         $importer = new qformat_xml();
289         $importer->import_hints($qo, $questionxml['question'], 'html');
291         $this->assertFalse(isset($qo->hint));
292     }
294     public function test_import_description() {
295         $xml = '  <question type="description">
296     <name>
297       <text>A description</text>
298     </name>
299     <questiontext format="html">
300       <text>The question text.</text>
301     </questiontext>
302     <generalfeedback>
303       <text>Here is some general feedback.</text>
304     </generalfeedback>
305     <defaultgrade>0</defaultgrade>
306     <penalty>0</penalty>
307     <hidden>0</hidden>
308     <tags>
309       <tag><text>tagDescription</text></tag>
310       <tag><text>tagTest</text></tag>
311     </tags>
312   </question>';
313         $xmldata = xmlize($xml);
315         $importer = new qformat_xml();
316         $q = $importer->import_description($xmldata['question']);
318         $expectedq = new stdClass();
319         $expectedq->qtype = 'description';
320         $expectedq->name = 'A description';
321         $expectedq->questiontext = 'The question text.';
322         $expectedq->questiontextformat = FORMAT_HTML;
323         $expectedq->generalfeedback = 'Here is some general feedback.';
324         $expectedq->defaultmark = 0;
325         $expectedq->length = 0;
326         $expectedq->penalty = 0;
327         $expectedq->tags = array('tagDescription', 'tagTest');
329         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
330     }
332     public function test_export_description() {
333         $qdata = new stdClass();
334         $qdata->id = 123;
335         $qdata->contextid = \context_system::instance()->id;
336         $qdata->qtype = 'description';
337         $qdata->name = 'A description';
338         $qdata->questiontext = 'The question text.';
339         $qdata->questiontextformat = FORMAT_HTML;
340         $qdata->generalfeedback = 'Here is some general feedback.';
341         $qdata->generalfeedbackformat = FORMAT_HTML;
342         $qdata->defaultmark = 0;
343         $qdata->length = 0;
344         $qdata->penalty = 0;
345         $qdata->hidden = 0;
346         $qdata->idnumber = null;
348         $exporter = new qformat_xml();
349         $xml = $exporter->writequestion($qdata);
351         $expectedxml = '<!-- question: 123  -->
352   <question type="description">
353     <name>
354       <text>A description</text>
355     </name>
356     <questiontext format="html">
357       <text>The question text.</text>
358     </questiontext>
359     <generalfeedback format="html">
360       <text>Here is some general feedback.</text>
361     </generalfeedback>
362     <defaultgrade>0</defaultgrade>
363     <penalty>0</penalty>
364     <hidden>0</hidden>
365     <idnumber></idnumber>
366   </question>
367 ';
369         $this->assert_same_xml($expectedxml, $xml);
370     }
372     public function test_import_essay_20() {
373         $xml = '  <question type="essay">
374     <name>
375       <text>An essay</text>
376     </name>
377     <questiontext format="moodle_auto_format">
378       <text>Write something.</text>
379     </questiontext>
380     <generalfeedback>
381       <text>I hope you wrote something interesting.</text>
382     </generalfeedback>
383     <defaultgrade>1</defaultgrade>
384     <penalty>0</penalty>
385     <hidden>0</hidden>
386     <tags>
387       <tag><text>tagEssay</text></tag>
388       <tag><text>tagEssay20</text></tag>
389       <tag><text>tagTest</text></tag>
390     </tags>
391   </question>';
392         $xmldata = xmlize($xml);
394         $importer = new qformat_xml();
395         $q = $importer->import_essay($xmldata['question']);
397         $expectedq = new stdClass();
398         $expectedq->qtype = 'essay';
399         $expectedq->name = 'An essay';
400         $expectedq->questiontext = 'Write something.';
401         $expectedq->questiontextformat = FORMAT_MOODLE;
402         $expectedq->generalfeedback = 'I hope you wrote something interesting.';
403         $expectedq->defaultmark = 1;
404         $expectedq->length = 1;
405         $expectedq->penalty = 0;
406         $expectedq->responseformat = 'editor';
407         $expectedq->responserequired = 1;
408         $expectedq->responsefieldlines = 15;
409         $expectedq->minwordlimit = null;
410         $expectedq->minwordenabled = false;
411         $expectedq->maxwordlimit = null;
412         $expectedq->maxwordenabled = false;
413         $expectedq->attachments = 0;
414         $expectedq->attachmentsrequired = 0;
415         $expectedq->maxbytes = 0;
416         $expectedq->filetypeslist = null;
417         $expectedq->graderinfo['text'] = '';
418         $expectedq->graderinfo['format'] = FORMAT_MOODLE;
419         $expectedq->responsetemplate['text'] = '';
420         $expectedq->responsetemplate['format'] = FORMAT_MOODLE;
421         $expectedq->tags = array('tagEssay', 'tagEssay20', 'tagTest');
423         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
424     }
426     public function test_import_essay_21() {
427         $xml = '  <question type="essay">
428     <name>
429       <text>An essay</text>
430     </name>
431     <questiontext format="moodle_auto_format">
432       <text>Write something.</text>
433     </questiontext>
434     <generalfeedback>
435       <text>I hope you wrote something interesting.</text>
436     </generalfeedback>
437     <defaultgrade>1</defaultgrade>
438     <penalty>0</penalty>
439     <hidden>0</hidden>
440     <responseformat>monospaced</responseformat>
441     <responserequired>0</responserequired>
442     <responsefieldlines>42</responsefieldlines>
443     <attachments>-1</attachments>
444     <attachmentsrequired>1</attachmentsrequired>
445     <graderinfo format="html">
446         <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
447     </graderinfo>
448     <responsetemplate format="html">
449         <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
450     </responsetemplate>
451     <tags>
452       <tag><text>tagEssay</text></tag>
453       <tag><text>tagEssay21</text></tag>
454       <tag><text>tagTest</text></tag>
455     </tags>
456   </question>';
457         $xmldata = xmlize($xml);
459         $importer = new qformat_xml();
460         $q = $importer->import_essay($xmldata['question']);
462         $expectedq = new stdClass();
463         $expectedq->qtype = 'essay';
464         $expectedq->name = 'An essay';
465         $expectedq->questiontext = 'Write something.';
466         $expectedq->questiontextformat = FORMAT_MOODLE;
467         $expectedq->generalfeedback = 'I hope you wrote something interesting.';
468         $expectedq->defaultmark = 1;
469         $expectedq->length = 1;
470         $expectedq->penalty = 0;
471         $expectedq->responseformat = 'monospaced';
472         $expectedq->responserequired = 0;
473         $expectedq->responsefieldlines = 42;
474         $expectedq->minwordlimit = null;
475         $expectedq->minwordenabled = false;
476         $expectedq->maxwordlimit = null;
477         $expectedq->maxwordenabled = false;
478         $expectedq->attachments = -1;
479         $expectedq->attachmentsrequired = 1;
480         $expectedq->maxbytes = 0;
481         $expectedq->filetypeslist = null;
482         $expectedq->graderinfo['text'] = '<p>Grade <b>generously</b>!</p>';
483         $expectedq->graderinfo['format'] = FORMAT_HTML;
484         $expectedq->responsetemplate['text'] = '<p>Here is something <b>really</b> interesting.</p>';
485         $expectedq->responsetemplate['format'] = FORMAT_HTML;
486         $expectedq->tags = array('tagEssay', 'tagEssay21', 'tagTest');
488         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
489     }
491     public function test_import_essay_311() {
492         $xml = '  <question type="essay">
493     <name>
494       <text>An essay</text>
495     </name>
496     <questiontext format="moodle_auto_format">
497       <text>Write something.</text>
498     </questiontext>
499     <generalfeedback>
500       <text>I hope you wrote something interesting.</text>
501     </generalfeedback>
502     <defaultgrade>1</defaultgrade>
503     <penalty>0</penalty>
504     <hidden>0</hidden>
505     <responseformat>monospaced</responseformat>
506     <responserequired>0</responserequired>
507     <responsefieldlines>42</responsefieldlines>
508     <minwordlimit>10</minwordlimit>
509     <maxwordlimit>20</maxwordlimit>
510     <attachments>-1</attachments>
511     <attachmentsrequired>1</attachmentsrequired>
512     <maxbytes>52428800</maxbytes>
513     <filetypeslist>.pdf,.zip.,.docx</filetypeslist>
514     <graderinfo format="html">
515         <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
516     </graderinfo>
517     <responsetemplate format="html">
518         <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
519     </responsetemplate>
520     <tags>
521       <tag><text>tagEssay</text></tag>
522       <tag><text>tagEssay21</text></tag>
523       <tag><text>tagTest</text></tag>
524     </tags>
525   </question>';
526         $xmldata = xmlize($xml);
528         $importer = new qformat_xml();
529         $q = $importer->import_essay($xmldata['question']);
531         $expectedq = new stdClass();
532         $expectedq->qtype = 'essay';
533         $expectedq->name = 'An essay';
534         $expectedq->questiontext = 'Write something.';
535         $expectedq->questiontextformat = FORMAT_MOODLE;
536         $expectedq->generalfeedback = 'I hope you wrote something interesting.';
537         $expectedq->defaultmark = 1;
538         $expectedq->length = 1;
539         $expectedq->penalty = 0;
540         $expectedq->responseformat = 'monospaced';
541         $expectedq->responserequired = 0;
542         $expectedq->responsefieldlines = 42;
543         $expectedq->minwordlimit = 10;
544         $expectedq->minwordenabled = true;
545         $expectedq->maxwordlimit = 20;
546         $expectedq->maxwordenabled = true;
547         $expectedq->attachments = -1;
548         $expectedq->attachmentsrequired = 1;
549         $expectedq->maxbytes = 52428800; // 50MB.
550         $expectedq->filetypeslist = '.pdf,.zip.,.docx';
551         $expectedq->graderinfo['text'] = '<p>Grade <b>generously</b>!</p>';
552         $expectedq->graderinfo['format'] = FORMAT_HTML;
553         $expectedq->responsetemplate['text'] = '<p>Here is something <b>really</b> interesting.</p>';
554         $expectedq->responsetemplate['format'] = FORMAT_HTML;
555         $expectedq->tags = array('tagEssay', 'tagEssay21', 'tagTest');
557         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
558     }
560     public function test_export_essay() {
561         $qdata = new stdClass();
562         $qdata->id = 123;
563         $qdata->contextid = \context_system::instance()->id;
564         $qdata->qtype = 'essay';
565         $qdata->name = 'An essay';
566         $qdata->questiontext = 'Write something.';
567         $qdata->questiontextformat = FORMAT_MOODLE;
568         $qdata->generalfeedback = 'I hope you wrote something interesting.';
569         $qdata->generalfeedbackformat = FORMAT_MOODLE;
570         $qdata->defaultmark = 1;
571         $qdata->length = 1;
572         $qdata->penalty = 0;
573         $qdata->hidden = 0;
574         $qdata->idnumber = null;
575         $qdata->options = new stdClass();
576         $qdata->options->id = 456;
577         $qdata->options->questionid = 123;
578         $qdata->options->responseformat = 'monospaced';
579         $qdata->options->responserequired = 0;
580         $qdata->options->responsefieldlines = 42;
581         $qdata->options->minwordlimit = 10;
582         $qdata->options->maxwordlimit = 20;
583         $qdata->options->attachments = -1;
584         $qdata->options->attachmentsrequired = 1;
585         $qdata->options->graderinfo = '<p>Grade <b>generously</b>!</p>';
586         $qdata->options->graderinfoformat = FORMAT_HTML;
587         $qdata->options->responsetemplate = '<p>Here is something <b>really</b> interesting.</p>';
588         $qdata->options->responsetemplateformat = FORMAT_HTML;
589         $qdata->options->maxbytes = 52428800; // 50MB.
590         $qdata->options->filetypeslist = '.pdf,.zip.,.docx';
591         $exporter = new qformat_xml();
592         $xml = $exporter->writequestion($qdata);
594         $expectedxml = '<!-- question: 123  -->
595   <question type="essay">
596     <name>
597       <text>An essay</text>
598     </name>
599     <questiontext format="moodle_auto_format">
600       <text>Write something.</text>
601     </questiontext>
602     <generalfeedback format="moodle_auto_format">
603       <text>I hope you wrote something interesting.</text>
604     </generalfeedback>
605     <defaultgrade>1</defaultgrade>
606     <penalty>0</penalty>
607     <hidden>0</hidden>
608     <idnumber></idnumber>
609     <responseformat>monospaced</responseformat>
610     <responserequired>0</responserequired>
611     <responsefieldlines>42</responsefieldlines>
612     <minwordlimit>10</minwordlimit>
613     <maxwordlimit>20</maxwordlimit>
614     <attachments>-1</attachments>
615     <attachmentsrequired>1</attachmentsrequired>
616     <maxbytes>52428800</maxbytes>
617     <filetypeslist>.pdf,.zip.,.docx</filetypeslist>
618     <graderinfo format="html">
619       <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
620     </graderinfo>
621     <responsetemplate format="html">
622       <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
623     </responsetemplate>
624   </question>
625 ';
627         $this->assert_same_xml($expectedxml, $xml);
628     }
630     public function test_import_match_19() {
631         $xml = '  <question type="matching">
632     <name>
633       <text>Matching question</text>
634     </name>
635     <questiontext format="html">
636       <text>Match the upper and lower case letters.</text>
637     </questiontext>
638     <generalfeedback>
639       <text>The answer is A -> a, B -> b and C -> c.</text>
640     </generalfeedback>
641     <defaultgrade>1</defaultgrade>
642     <penalty>0.3333333</penalty>
643     <hidden>0</hidden>
644     <shuffleanswers>false</shuffleanswers>
645     <correctfeedback>
646       <text>Well done.</text>
647     </correctfeedback>
648     <partiallycorrectfeedback>
649       <text>Not entirely.</text>
650     </partiallycorrectfeedback>
651     <incorrectfeedback>
652       <text>Completely wrong!</text>
653     </incorrectfeedback>
654     <subquestion>
655       <text>A</text>
656       <answer>
657         <text>a</text>
658       </answer>
659     </subquestion>
660     <subquestion>
661       <text>B</text>
662       <answer>
663         <text>b</text>
664       </answer>
665     </subquestion>
666     <subquestion>
667       <text>C</text>
668       <answer>
669         <text>c</text>
670       </answer>
671     </subquestion>
672     <subquestion>
673       <text></text>
674       <answer>
675         <text>d</text>
676       </answer>
677     </subquestion>
678     <hint>
679       <text>Hint 1</text>
680       <shownumcorrect />
681     </hint>
682     <hint>
683       <text></text>
684       <shownumcorrect />
685       <clearwrong />
686     </hint>
687     <tags>
688       <tag><text>tagMatching</text></tag>
689       <tag><text>tagTest</text></tag>
690     </tags>
691   </question>';
692         $xmldata = xmlize($xml);
694         $importer = new qformat_xml();
695         $q = $importer->import_match($xmldata['question']);
697         $expectedq = new stdClass();
698         $expectedq->qtype = 'match';
699         $expectedq->name = 'Matching question';
700         $expectedq->questiontext = 'Match the upper and lower case letters.';
701         $expectedq->questiontextformat = FORMAT_HTML;
702         $expectedq->correctfeedback = array('text' => 'Well done.',
703                 'format' => FORMAT_HTML);
704         $expectedq->partiallycorrectfeedback = array('text' => 'Not entirely.',
705                 'format' => FORMAT_HTML);
706         $expectedq->shownumcorrect = false;
707         $expectedq->incorrectfeedback = array('text' => 'Completely wrong!',
708                 'format' => FORMAT_HTML);
709         $expectedq->generalfeedback = 'The answer is A -> a, B -> b and C -> c.';
710         $expectedq->generalfeedbackformat = FORMAT_HTML;
711         $expectedq->defaultmark = 1;
712         $expectedq->length = 1;
713         $expectedq->penalty = 0.3333333;
714         $expectedq->shuffleanswers = 0;
715         $expectedq->subquestions = array(
716             array('text' => 'A', 'format' => FORMAT_HTML),
717             array('text' => 'B', 'format' => FORMAT_HTML),
718             array('text' => 'C', 'format' => FORMAT_HTML),
719             array('text' => '', 'format' => FORMAT_HTML));
720         $expectedq->subanswers = array('a', 'b', 'c', 'd');
721         $expectedq->hint = array(
722             array('text' => 'Hint 1', 'format' => FORMAT_HTML),
723             array('text' => '', 'format' => FORMAT_HTML),
724         );
725         $expectedq->hintshownumcorrect = array(true, true);
726         $expectedq->hintclearwrong = array(false, true);
727         $expectedq->tags = array('tagMatching', 'tagTest');
729         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
730     }
732     public function test_export_match() {
733         $qdata = new stdClass();
734         $qdata->id = 123;
735         $qdata->contextid = \context_system::instance()->id;
736         $qdata->qtype = 'match';
737         $qdata->name = 'Matching question';
738         $qdata->questiontext = 'Match the upper and lower case letters.';
739         $qdata->questiontextformat = FORMAT_HTML;
740         $qdata->generalfeedback = 'The answer is A -> a, B -> b and C -> c.';
741         $qdata->generalfeedbackformat = FORMAT_HTML;
742         $qdata->defaultmark = 1;
743         $qdata->length = 1;
744         $qdata->penalty = 0.3333333;
745         $qdata->hidden = 0;
746         $qdata->idnumber = null;
748         $qdata->options = new stdClass();
749         $qdata->options->shuffleanswers = 1;
750         $qdata->options->correctfeedback = 'Well done.';
751         $qdata->options->correctfeedbackformat = FORMAT_HTML;
752         $qdata->options->partiallycorrectfeedback = 'Not entirely.';
753         $qdata->options->partiallycorrectfeedbackformat = FORMAT_HTML;
754         $qdata->options->shownumcorrect = false;
755         $qdata->options->incorrectfeedback = 'Completely wrong!';
756         $qdata->options->incorrectfeedbackformat = FORMAT_HTML;
758         $subq1 = new stdClass();
759         $subq1->id = -4;
760         $subq1->questiontext = 'A';
761         $subq1->questiontextformat = FORMAT_HTML;
762         $subq1->answertext = 'a';
764         $subq2 = new stdClass();
765         $subq2->id = -3;
766         $subq2->questiontext = 'B';
767         $subq2->questiontextformat = FORMAT_HTML;
768         $subq2->answertext = 'b';
770         $subq3 = new stdClass();
771         $subq3->id = -2;
772         $subq3->questiontext = 'C';
773         $subq3->questiontextformat = FORMAT_HTML;
774         $subq3->answertext = 'c';
776         $subq4 = new stdClass();
777         $subq4->id = -1;
778         $subq4->questiontext = '';
779         $subq4->questiontextformat = FORMAT_HTML;
780         $subq4->answertext = 'd';
782         $qdata->options->subquestions = array(
783                 $subq1, $subq2, $subq3, $subq4);
785         $qdata->hints = array(
786             new question_hint_with_parts(0, 'Hint 1', FORMAT_HTML, true, false),
787             new question_hint_with_parts(0, '', FORMAT_HTML, true, true),
788         );
790         $exporter = new qformat_xml();
791         $xml = $exporter->writequestion($qdata);
793         $expectedxml = '<!-- question: 123  -->
794   <question type="matching">
795     <name>
796       <text>Matching question</text>
797     </name>
798     <questiontext format="html">
799       <text>Match the upper and lower case letters.</text>
800     </questiontext>
801     <generalfeedback format="html">
802       <text><![CDATA[The answer is A -> a, B -> b and C -> c.]]></text>
803     </generalfeedback>
804     <defaultgrade>1</defaultgrade>
805     <penalty>0.3333333</penalty>
806     <hidden>0</hidden>
807     <idnumber></idnumber>
808     <shuffleanswers>true</shuffleanswers>
809     <correctfeedback format="html">
810       <text>Well done.</text>
811     </correctfeedback>
812     <partiallycorrectfeedback format="html">
813       <text>Not entirely.</text>
814     </partiallycorrectfeedback>
815     <incorrectfeedback format="html">
816       <text>Completely wrong!</text>
817     </incorrectfeedback>
818     <subquestion format="html">
819       <text>A</text>
820       <answer>
821         <text>a</text>
822       </answer>
823     </subquestion>
824     <subquestion format="html">
825       <text>B</text>
826       <answer>
827         <text>b</text>
828       </answer>
829     </subquestion>
830     <subquestion format="html">
831       <text>C</text>
832       <answer>
833         <text>c</text>
834       </answer>
835     </subquestion>
836     <subquestion format="html">
837       <text></text>
838       <answer>
839         <text>d</text>
840       </answer>
841     </subquestion>
842     <hint format="html">
843       <text>Hint 1</text>
844       <shownumcorrect/>
845     </hint>
846     <hint format="html">
847       <text></text>
848       <shownumcorrect/>
849       <clearwrong/>
850     </hint>
851   </question>
852 ';
854         $this->assert_same_xml($expectedxml, $xml);
855     }
857     public function test_import_multichoice_19() {
858         $xml = '  <question type="multichoice">
859     <name>
860       <text>Multiple choice question</text>
861     </name>
862     <questiontext format="html">
863       <text>Which are the even numbers?</text>
864     </questiontext>
865     <generalfeedback>
866       <text>The even numbers are 2 and 4.</text>
867     </generalfeedback>
868     <defaultgrade>2</defaultgrade>
869     <penalty>0.3333333</penalty>
870     <hidden>0</hidden>
871     <single>false</single>
872     <shuffleanswers>false</shuffleanswers>
873     <answernumbering>abc</answernumbering>
874     <correctfeedback>
875       <text><![CDATA[<p>Your answer is correct.</p>]]></text>
876     </correctfeedback>
877     <partiallycorrectfeedback>
878       <text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
879     </partiallycorrectfeedback>
880     <incorrectfeedback>
881       <text><![CDATA[<p>Your answer is incorrect.</p>]]></text>
882     </incorrectfeedback>
883     <shownumcorrect/>
884     <answer fraction="0">
885       <text>1</text>
886       <feedback>
887         <text></text>
888       </feedback>
889     </answer>
890     <answer fraction="100">
891       <text>2</text>
892       <feedback>
893         <text></text>
894       </feedback>
895     </answer>
896     <answer fraction="0">
897       <text>3</text>
898       <feedback>
899         <text></text>
900       </feedback>
901     </answer>
902     <answer fraction="100">
903       <text>4</text>
904       <feedback>
905         <text></text>
906       </feedback>
907     </answer>
908     <hint>
909       <text>Hint 1.</text>
910     </hint>
911     <hint>
912       <text>Hint 2.</text>
913     </hint>
914   </question>';
915         $xmldata = xmlize($xml);
917         $importer = new qformat_xml();
918         $q = $importer->import_multichoice($xmldata['question']);
920         $expectedq = new stdClass();
921         $expectedq->qtype = 'multichoice';
922         $expectedq->name = 'Multiple choice question';
923         $expectedq->questiontext = 'Which are the even numbers?';
924         $expectedq->questiontextformat = FORMAT_HTML;
925         $expectedq->correctfeedback = array(
926                 'text'   => '<p>Your answer is correct.</p>',
927                 'format' => FORMAT_HTML);
928         $expectedq->shownumcorrect = false;
929         $expectedq->partiallycorrectfeedback = array(
930                 'text'   => '<p>Your answer is partially correct.</p>',
931                 'format' => FORMAT_HTML);
932         $expectedq->shownumcorrect = true;
933         $expectedq->incorrectfeedback = array(
934                 'text'   => '<p>Your answer is incorrect.</p>',
935                 'format' => FORMAT_HTML);
936         $expectedq->generalfeedback = 'The even numbers are 2 and 4.';
937         $expectedq->defaultmark = 2;
938         $expectedq->length = 1;
939         $expectedq->penalty = 0.3333333;
940         $expectedq->shuffleanswers = 0;
941         $expectedq->single = false;
943         $expectedq->answer = array(
944             array('text' => '1', 'format' => FORMAT_HTML),
945             array('text' => '2', 'format' => FORMAT_HTML),
946             array('text' => '3', 'format' => FORMAT_HTML),
947             array('text' => '4', 'format' => FORMAT_HTML));
948         $expectedq->fraction = array(0, 1, 0, 1);
949         $expectedq->feedback = array(
950             array('text' => '', 'format' => FORMAT_HTML),
951             array('text' => '', 'format' => FORMAT_HTML),
952             array('text' => '', 'format' => FORMAT_HTML),
953             array('text' => '', 'format' => FORMAT_HTML));
955         $expectedq->hint = array(
956             array('text' => 'Hint 1.', 'format' => FORMAT_HTML),
957             array('text' => 'Hint 2.', 'format' => FORMAT_HTML),
958         );
959         $expectedq->hintshownumcorrect = array(false, false);
960         $expectedq->hintclearwrong = array(false, false);
962         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
963     }
965     public function test_export_multichoice() {
966         $qdata = new stdClass();
967         $qdata->id = 123;
968         $qdata->contextid = \context_system::instance()->id;
969         $qdata->qtype = 'multichoice';
970         $qdata->name = 'Multiple choice question';
971         $qdata->questiontext = 'Which are the even numbers?';
972         $qdata->questiontextformat = FORMAT_HTML;
973         $qdata->generalfeedback = 'The even numbers are 2 and 4.';
974         $qdata->generalfeedbackformat = FORMAT_HTML;
975         $qdata->defaultmark = 2;
976         $qdata->length = 1;
977         $qdata->penalty = 0.3333333;
978         $qdata->hidden = 0;
979         $qdata->idnumber = null;
981         $qdata->options = new stdClass();
982         $qdata->options->single = 0;
983         $qdata->options->shuffleanswers = 0;
984         $qdata->options->answernumbering = 'abc';
985         $qdata->options->showstandardinstruction = 0;
986         $qdata->options->correctfeedback = '<p>Your answer is correct.</p>';
987         $qdata->options->correctfeedbackformat = FORMAT_HTML;
988         $qdata->options->partiallycorrectfeedback = '<p>Your answer is partially correct.</p>';
989         $qdata->options->partiallycorrectfeedbackformat = FORMAT_HTML;
990         $qdata->options->shownumcorrect = 1;
991         $qdata->options->incorrectfeedback = '<p>Your answer is incorrect.</p>';
992         $qdata->options->incorrectfeedbackformat = FORMAT_HTML;
994         $qdata->options->answers = array(
995             13 => new question_answer(13, '1', 0, '', FORMAT_HTML),
996             14 => new question_answer(14, '2', 1, '', FORMAT_HTML),
997             15 => new question_answer(15, '3', 0, '', FORMAT_HTML),
998             16 => new question_answer(16, '4', 1, '', FORMAT_HTML),
999         );
1001         $qdata->hints = array(
1002             new question_hint_with_parts(0, 'Hint 1.', FORMAT_HTML, false, false),
1003             new question_hint_with_parts(0, 'Hint 2.', FORMAT_HTML, false, false),
1004         );
1006         $exporter = new qformat_xml();
1007         $xml = $exporter->writequestion($qdata);
1009         $expectedxml = '<!-- question: 123  -->
1010   <question type="multichoice">
1011     <name>
1012       <text>Multiple choice question</text>
1013     </name>
1014     <questiontext format="html">
1015       <text>Which are the even numbers?</text>
1016     </questiontext>
1017     <generalfeedback format="html">
1018       <text>The even numbers are 2 and 4.</text>
1019     </generalfeedback>
1020     <defaultgrade>2</defaultgrade>
1021     <penalty>0.3333333</penalty>
1022     <hidden>0</hidden>
1023     <idnumber></idnumber>
1024     <single>false</single>
1025     <shuffleanswers>false</shuffleanswers>
1026     <answernumbering>abc</answernumbering>
1027     <showstandardinstruction>0</showstandardinstruction>
1028     <correctfeedback format="html">
1029       <text><![CDATA[<p>Your answer is correct.</p>]]></text>
1030     </correctfeedback>
1031     <partiallycorrectfeedback format="html">
1032       <text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
1033     </partiallycorrectfeedback>
1034     <incorrectfeedback format="html">
1035       <text><![CDATA[<p>Your answer is incorrect.</p>]]></text>
1036     </incorrectfeedback>
1037     <shownumcorrect/>
1038     <answer fraction="0" format="plain_text">
1039       <text>1</text>
1040       <feedback format="html">
1041         <text></text>
1042       </feedback>
1043     </answer>
1044     <answer fraction="100" format="plain_text">
1045       <text>2</text>
1046       <feedback format="html">
1047         <text></text>
1048       </feedback>
1049     </answer>
1050     <answer fraction="0" format="plain_text">
1051       <text>3</text>
1052       <feedback format="html">
1053         <text></text>
1054       </feedback>
1055     </answer>
1056     <answer fraction="100" format="plain_text">
1057       <text>4</text>
1058       <feedback format="html">
1059         <text></text>
1060       </feedback>
1061     </answer>
1062     <hint format="html">
1063       <text>Hint 1.</text>
1064     </hint>
1065     <hint format="html">
1066       <text>Hint 2.</text>
1067     </hint>
1068   </question>
1069 ';
1071         $this->assert_same_xml($expectedxml, $xml);
1072     }
1074     public function test_import_numerical_19() {
1075         $xml = '  <question type="numerical">
1076     <name>
1077       <text>Numerical question</text>
1078     </name>
1079     <questiontext format="html">
1080       <text>What is the answer?</text>
1081     </questiontext>
1082     <generalfeedback>
1083       <text>General feedback: Think Hitch-hikers guide to the Galaxy.</text>
1084     </generalfeedback>
1085     <defaultgrade>1</defaultgrade>
1086     <penalty>0.1</penalty>
1087     <hidden>0</hidden>
1088     <answer fraction="100">
1089       <text>42</text>
1090       <feedback>
1091         <text>Well done!</text>
1092       </feedback>
1093       <tolerance>0.001</tolerance>
1094     </answer>
1095     <answer fraction="0">
1096       <text>13</text>
1097       <feedback>
1098         <text>What were you thinking?!</text>
1099       </feedback>
1100       <tolerance>1</tolerance>
1101     </answer>
1102     <answer fraction="0">
1103       <text>*</text>
1104       <feedback>
1105         <text>Completely wrong.</text>
1106       </feedback>
1107       <tolerance></tolerance>
1108     </answer>
1109   </question>';
1110         $xmldata = xmlize($xml);
1112         $importer = new qformat_xml();
1113         $q = $importer->import_numerical($xmldata['question']);
1115         $expectedq = new stdClass();
1116         $expectedq->qtype = 'numerical';
1117         $expectedq->name = 'Numerical question';
1118         $expectedq->questiontext = 'What is the answer?';
1119         $expectedq->questiontextformat = FORMAT_HTML;
1120         $expectedq->generalfeedback = 'General feedback: Think Hitch-hikers guide to the Galaxy.';
1121         $expectedq->generalfeedbackformat = FORMAT_HTML;
1122         $expectedq->defaultmark = 1;
1123         $expectedq->length = 1;
1124         $expectedq->penalty = 0.1;
1126         $expectedq->answer = array('42', '13', '*');
1127         $expectedq->fraction = array(1, 0, 0);
1128         $expectedq->feedback = array(
1129             array('text' => 'Well done!',
1130                     'format' => FORMAT_HTML),
1131             array('text' => 'What were you thinking?!',
1132                     'format' => FORMAT_HTML),
1133             array('text' => 'Completely wrong.',
1134                     'format' => FORMAT_HTML));
1135         $expectedq->tolerance = array(0.001, 1, '');
1137         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1138     }
1140     public function test_export_numerical() {
1141         question_bank::load_question_definition_classes('numerical');
1143         $qdata = new stdClass();
1144         $qdata->id = 123;
1145         $qdata->contextid = \context_system::instance()->id;
1146         $qdata->qtype = 'numerical';
1147         $qdata->name = 'Numerical question';
1148         $qdata->questiontext = 'What is the answer?';
1149         $qdata->questiontextformat = FORMAT_HTML;
1150         $qdata->generalfeedback = 'General feedback: Think Hitch-hikers guide to the Galaxy.';
1151         $qdata->generalfeedbackformat = FORMAT_HTML;
1152         $qdata->defaultmark = 1;
1153         $qdata->length = 1;
1154         $qdata->penalty = 0.1;
1155         $qdata->hidden = 0;
1156         $qdata->idnumber = null;
1158         $qdata->options = new stdClass();
1159         $qdata->options->answers = array(
1160             13 => new qtype_numerical_answer(13, '42', 1, 'Well done!',
1161                     FORMAT_HTML, 0.001),
1162             14 => new qtype_numerical_answer(14, '13', 0, 'What were you thinking?!',
1163                     FORMAT_HTML, 1),
1164             15 => new qtype_numerical_answer(15, '*', 0, 'Completely wrong.',
1165                     FORMAT_HTML, ''),
1166         );
1168         $qdata->options->units = array();
1170         $exporter = new qformat_xml();
1171         $xml = $exporter->writequestion($qdata);
1173         $expectedxml = '<!-- question: 123  -->
1174   <question type="numerical">
1175     <name>
1176       <text>Numerical question</text>
1177     </name>
1178     <questiontext format="html">
1179       <text>What is the answer?</text>
1180     </questiontext>
1181     <generalfeedback format="html">
1182       <text>General feedback: Think Hitch-hikers guide to the Galaxy.</text>
1183     </generalfeedback>
1184     <defaultgrade>1</defaultgrade>
1185     <penalty>0.1</penalty>
1186     <hidden>0</hidden>
1187     <idnumber></idnumber>
1188     <answer fraction="100" format="plain_text">
1189       <text>42</text>
1190       <feedback format="html">
1191         <text>Well done!</text>
1192       </feedback>
1193       <tolerance>0.001</tolerance>
1194     </answer>
1195     <answer fraction="0" format="plain_text">
1196       <text>13</text>
1197       <feedback format="html">
1198         <text>What were you thinking?!</text>
1199       </feedback>
1200       <tolerance>1</tolerance>
1201     </answer>
1202     <answer fraction="0" format="plain_text">
1203       <text>*</text>
1204       <feedback format="html">
1205         <text>Completely wrong.</text>
1206       </feedback>
1207       <tolerance>0</tolerance>
1208     </answer>
1209   </question>
1210 ';
1212         $this->assert_same_xml($expectedxml, $xml);
1213     }
1215     public function test_import_shortanswer_19() {
1216         $xml = '  <question type="shortanswer">
1217     <name>
1218       <text>Short answer question</text>
1219     </name>
1220     <questiontext format="html">
1221       <text>Fill in the gap in this sequence: Alpha, ________, Gamma.</text>
1222     </questiontext>
1223     <generalfeedback>
1224       <text>The answer is Beta.</text>
1225     </generalfeedback>
1226     <defaultgrade>1</defaultgrade>
1227     <penalty>0.3333333</penalty>
1228     <hidden>0</hidden>
1229     <usecase>0</usecase>
1230     <answer fraction="100" format="plain_text">
1231       <text>Beta</text>
1232       <feedback>
1233         <text>Well done!</text>
1234       </feedback>
1235     </answer>
1236     <answer fraction="0" format="plain_text">
1237       <text>*</text>
1238       <feedback>
1239         <text>Doh!</text>
1240       </feedback>
1241     </answer>
1242     <hint>
1243       <text>Hint 1</text>
1244     </hint>
1245     <hint>
1246       <text>Hint 2</text>
1247     </hint>
1248   </question>';
1249         $xmldata = xmlize($xml);
1251         $importer = new qformat_xml();
1252         $q = $importer->import_shortanswer($xmldata['question']);
1254         $expectedq = new stdClass();
1255         $expectedq->qtype = 'shortanswer';
1256         $expectedq->name = 'Short answer question';
1257         $expectedq->questiontext = 'Fill in the gap in this sequence: Alpha, ________, Gamma.';
1258         $expectedq->questiontextformat = FORMAT_HTML;
1259         $expectedq->generalfeedback = 'The answer is Beta.';
1260         $expectedq->usecase = false;
1261         $expectedq->defaultmark = 1;
1262         $expectedq->length = 1;
1263         $expectedq->penalty = 0.3333333;
1265         $expectedq->answer = array('Beta', '*');
1266         $expectedq->fraction = array(1, 0);
1267         $expectedq->feedback = array(
1268             array('text' => 'Well done!', 'format' => FORMAT_HTML),
1269             array('text' => 'Doh!', 'format' => FORMAT_HTML));
1271         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1272     }
1274     public function test_export_shortanswer() {
1275         $qdata = new stdClass();
1276         $qdata->id = 123;
1277         $qdata->contextid = \context_system::instance()->id;
1278         $qdata->qtype = 'shortanswer';
1279         $qdata->name = 'Short answer question';
1280         $qdata->questiontext = 'Fill in the gap in this sequence: Alpha, ________, Gamma.';
1281         $qdata->questiontextformat = FORMAT_HTML;
1282         $qdata->generalfeedback = 'The answer is Beta.';
1283         $qdata->generalfeedbackformat = FORMAT_HTML;
1284         $qdata->defaultmark = 1;
1285         $qdata->length = 1;
1286         $qdata->penalty = 0.3333333;
1287         $qdata->hidden = 0;
1288         $qdata->idnumber = null;
1290         $qdata->options = new stdClass();
1291         $qdata->options->usecase = 0;
1293         $qdata->options->answers = array(
1294             13 => new question_answer(13, 'Beta', 1, 'Well done!', FORMAT_HTML),
1295             14 => new question_answer(14, '*', 0, 'Doh!', FORMAT_HTML),
1296         );
1298         $qdata->hints = array(
1299             new question_hint(0, 'Hint 1', FORMAT_HTML),
1300             new question_hint(0, 'Hint 2', FORMAT_HTML),
1301         );
1303         $exporter = new qformat_xml();
1304         $xml = $exporter->writequestion($qdata);
1306         $expectedxml = '<!-- question: 123  -->
1307   <question type="shortanswer">
1308     <name>
1309       <text>Short answer question</text>
1310     </name>
1311     <questiontext format="html">
1312       <text>Fill in the gap in this sequence: Alpha, ________, Gamma.</text>
1313     </questiontext>
1314     <generalfeedback format="html">
1315       <text>The answer is Beta.</text>
1316     </generalfeedback>
1317     <defaultgrade>1</defaultgrade>
1318     <penalty>0.3333333</penalty>
1319     <hidden>0</hidden>
1320     <idnumber></idnumber>
1321     <usecase>0</usecase>
1322     <answer fraction="100" format="plain_text">
1323       <text>Beta</text>
1324       <feedback format="html">
1325         <text>Well done!</text>
1326       </feedback>
1327     </answer>
1328     <answer fraction="0" format="plain_text">
1329       <text>*</text>
1330       <feedback format="html">
1331         <text>Doh!</text>
1332       </feedback>
1333     </answer>
1334     <hint format="html">
1335       <text>Hint 1</text>
1336     </hint>
1337     <hint format="html">
1338       <text>Hint 2</text>
1339     </hint>
1340   </question>
1341 ';
1343         $this->assert_same_xml($expectedxml, $xml);
1344     }
1346     public function test_import_truefalse_19() {
1347         $xml = '  <question type="truefalse">
1348     <name>
1349       <text>True false question</text>
1350     </name>
1351     <questiontext format="html">
1352       <text>The answer is true.</text>
1353     </questiontext>
1354     <generalfeedback>
1355       <text>General feedback: You should have chosen true.</text>
1356     </generalfeedback>
1357     <defaultgrade>1</defaultgrade>
1358     <penalty>1</penalty>
1359     <hidden>0</hidden>
1360     <answer fraction="100">
1361       <text>true</text>
1362       <feedback>
1363         <text>Well done!</text>
1364       </feedback>
1365     </answer>
1366     <answer fraction="0">
1367       <text>false</text>
1368       <feedback>
1369         <text>Doh!</text>
1370       </feedback>
1371     </answer>
1372   </question>';
1373         $xmldata = xmlize($xml);
1375         $importer = new qformat_xml();
1376         $q = $importer->import_truefalse($xmldata['question']);
1378         $expectedq = new stdClass();
1379         $expectedq->qtype = 'truefalse';
1380         $expectedq->name = 'True false question';
1381         $expectedq->questiontext = 'The answer is true.';
1382         $expectedq->questiontextformat = FORMAT_HTML;
1383         $expectedq->generalfeedback = 'General feedback: You should have chosen true.';
1384         $expectedq->defaultmark = 1;
1385         $expectedq->length = 1;
1386         $expectedq->penalty = 1;
1388         $expectedq->feedbacktrue = array('text' => 'Well done!',
1389                 'format' => FORMAT_HTML);
1390         $expectedq->feedbackfalse = array('text' => 'Doh!',
1391                 'format' => FORMAT_HTML);
1392         $expectedq->correctanswer = true;
1394         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1395     }
1397     public function test_import_truefalse_with_idnumber() {
1398         $xml = '  <question type="truefalse">
1399     <name>
1400       <text>True false question</text>
1401     </name>
1402     <questiontext format="html">
1403       <text>The answer is true.</text>
1404     </questiontext>
1405     <generalfeedback>
1406       <text>General feedback: You should have chosen true.</text>
1407     </generalfeedback>
1408     <defaultgrade>1</defaultgrade>
1409     <penalty>1</penalty>
1410     <hidden>0</hidden>
1411     <idnumber>TestIdNum1</idnumber>
1412     <answer fraction="100">
1413       <text>true</text>
1414       <feedback>
1415         <text>Well done!</text>
1416       </feedback>
1417     </answer>
1418     <answer fraction="0">
1419       <text>false</text>
1420       <feedback>
1421         <text>Doh!</text>
1422       </feedback>
1423     </answer>
1424   </question>';
1425         $xmldata = xmlize($xml);
1427         $importer = new qformat_xml();
1428         $q = $importer->import_truefalse($xmldata['question']);
1430         $expectedq = new stdClass();
1431         $expectedq->qtype = 'truefalse';
1432         $expectedq->name = 'True false question';
1433         $expectedq->questiontext = 'The answer is true.';
1434         $expectedq->questiontextformat = FORMAT_HTML;
1435         $expectedq->generalfeedback = 'General feedback: You should have chosen true.';
1436         $expectedq->defaultmark = 1;
1437         $expectedq->length = 1;
1438         $expectedq->penalty = 1;
1439         $expectedq->idnumber = 'TestIdNum1';
1441         $expectedq->feedbacktrue = array('text' => 'Well done!',
1442                 'format' => FORMAT_HTML);
1443         $expectedq->feedbackfalse = array('text' => 'Doh!',
1444                 'format' => FORMAT_HTML);
1445         $expectedq->correctanswer = true;
1447         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1448     }
1450     public function test_export_truefalse() {
1451         $qdata = new stdClass();
1452         $qdata->id = 12;
1453         $qdata->contextid = \context_system::instance()->id;
1454         $qdata->qtype = 'truefalse';
1455         $qdata->name = 'True false question';
1456         $qdata->questiontext = 'The answer is true.';
1457         $qdata->questiontextformat = FORMAT_HTML;
1458         $qdata->generalfeedback = 'General feedback: You should have chosen true.';
1459         $qdata->generalfeedbackformat = FORMAT_HTML;
1460         $qdata->defaultmark = 1;
1461         $qdata->length = 1;
1462         $qdata->penalty = 1;
1463         $qdata->hidden = 0;
1464         $qdata->idnumber = null;
1466         $qdata->options = new stdClass();
1467         $qdata->options->answers = array(
1468             1 => new question_answer(1, 'True', 1, 'Well done!', FORMAT_HTML),
1469             2 => new question_answer(2, 'False', 0, 'Doh!', FORMAT_HTML),
1470         );
1471         $qdata->options->trueanswer = 1;
1472         $qdata->options->falseanswer = 2;
1474         $exporter = new qformat_xml();
1475         $xml = $exporter->writequestion($qdata);
1477         $expectedxml = '<!-- question: 12  -->
1478   <question type="truefalse">
1479     <name>
1480       <text>True false question</text>
1481     </name>
1482     <questiontext format="html">
1483       <text>The answer is true.</text>
1484     </questiontext>
1485     <generalfeedback format="html">
1486       <text>General feedback: You should have chosen true.</text>
1487     </generalfeedback>
1488     <defaultgrade>1</defaultgrade>
1489     <penalty>1</penalty>
1490     <hidden>0</hidden>
1491     <idnumber></idnumber>
1492     <answer fraction="100" format="plain_text">
1493       <text>true</text>
1494       <feedback format="html">
1495         <text>Well done!</text>
1496       </feedback>
1497     </answer>
1498     <answer fraction="0" format="plain_text">
1499       <text>false</text>
1500       <feedback format="html">
1501         <text>Doh!</text>
1502       </feedback>
1503     </answer>
1504   </question>
1505 ';
1507         $this->assert_same_xml($expectedxml, $xml);
1508     }
1510     public function test_export_truefalse_with_idnumber() {
1511         $qdata = new stdClass();
1512         $qdata->id = 12;
1513         $qdata->contextid = \context_system::instance()->id;
1514         $qdata->qtype = 'truefalse';
1515         $qdata->name = 'True false question';
1516         $qdata->questiontext = 'The answer is true.';
1517         $qdata->questiontextformat = FORMAT_HTML;
1518         $qdata->generalfeedback = 'General feedback: You should have chosen true.';
1519         $qdata->generalfeedbackformat = FORMAT_HTML;
1520         $qdata->defaultmark = 1;
1521         $qdata->length = 1;
1522         $qdata->penalty = 1;
1523         $qdata->hidden = 0;
1524         $qdata->idnumber = 'TestIDNum2';
1526         $qdata->options = new stdClass();
1527         $qdata->options->answers = array(
1528                 1 => new question_answer(1, 'True', 1, 'Well done!', FORMAT_HTML),
1529                 2 => new question_answer(2, 'False', 0, 'Doh!', FORMAT_HTML),
1530         );
1531         $qdata->options->trueanswer = 1;
1532         $qdata->options->falseanswer = 2;
1534         $exporter = new qformat_xml();
1535         $xml = $exporter->writequestion($qdata);
1537         $expectedxml = '<!-- question: 12  -->
1538   <question type="truefalse">
1539     <name>
1540       <text>True false question</text>
1541     </name>
1542     <questiontext format="html">
1543       <text>The answer is true.</text>
1544     </questiontext>
1545     <generalfeedback format="html">
1546       <text>General feedback: You should have chosen true.</text>
1547     </generalfeedback>
1548     <defaultgrade>1</defaultgrade>
1549     <penalty>1</penalty>
1550     <hidden>0</hidden>
1551     <idnumber>TestIDNum2</idnumber>
1552     <answer fraction="100" format="plain_text">
1553       <text>true</text>
1554       <feedback format="html">
1555         <text>Well done!</text>
1556       </feedback>
1557     </answer>
1558     <answer fraction="0" format="plain_text">
1559       <text>false</text>
1560       <feedback format="html">
1561         <text>Doh!</text>
1562       </feedback>
1563     </answer>
1564   </question>
1565 ';
1567         $this->assert_same_xml($expectedxml, $xml);
1568     }
1570     public function test_import_multianswer() {
1571         $xml = '  <question type="cloze">
1572     <name>
1573       <text>Simple multianswer</text>
1574     </name>
1575     <questiontext format="html">
1576       <text><![CDATA[Complete this opening line of verse: "The {1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer} and the {1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!} went to sea".]]></text>
1577     </questiontext>
1578     <generalfeedback format="html">
1579       <text><![CDATA[General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea".]]></text>
1580     </generalfeedback>
1581     <penalty>0.5</penalty>
1582     <hidden>0</hidden>
1583     <hint format="html">
1584       <text>Hint 1</text>
1585     </hint>
1586     <hint format="html">
1587       <text>Hint 2</text>
1588     </hint>
1589     <tags>
1590       <tag><text>tagCloze</text></tag>
1591       <tag><text>tagTest</text></tag>
1592     </tags>
1593   </question>
1594 ';
1595         $xmldata = xmlize($xml);
1597         $importer = new qformat_xml();
1598         $q = $importer->import_multianswer($xmldata['question']);
1600         // Annoyingly, import works in a weird way (it duplicates code, rather
1601         // than just calling save_question) so we cannot use
1602         // test_question_maker::get_question_form_data('multianswer', 'twosubq').
1603         $expectedqa = new stdClass();
1604         $expectedqa->name = 'Simple multianswer';
1605         $expectedqa->qtype = 'multianswer';
1606         $expectedqa->questiontext = 'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
1607         $expectedqa->generalfeedback =
1608                 'General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea".';
1609         $expectedqa->defaultmark = 2;
1610         $expectedqa->penalty = 0.5;
1612         $expectedqa->hint = array(
1613             array('text' => 'Hint 1', 'format' => FORMAT_HTML),
1614             array('text' => 'Hint 2', 'format' => FORMAT_HTML),
1615         );
1617         $sa = new stdClass();
1619         $sa->questiontext = array('text' => '{1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer}',
1620                 'format' => FORMAT_HTML, 'itemid' => null);
1621         $sa->generalfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1622         $sa->defaultmark = 1.0;
1623         $sa->qtype = 'shortanswer';
1624         $sa->usecase = 0;
1626         $sa->answer = array('Dog', 'Owl', '*');
1627         $sa->fraction = array(0, 1, 0);
1628         $sa->feedback = array(
1629             array('text' => 'Wrong, silly!', 'format' => FORMAT_HTML, 'itemid' => null),
1630             array('text' => 'Well done!',    'format' => FORMAT_HTML, 'itemid' => null),
1631             array('text' => 'Wrong answer',  'format' => FORMAT_HTML, 'itemid' => null),
1632         );
1634         $mc = new stdClass();
1636         $mc->generalfeedback = '';
1637         $mc->questiontext = array('text' => '{1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~' .
1638                 'Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!}',
1639                 'format' => FORMAT_HTML, 'itemid' => null);
1640         $mc->generalfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1641         $mc->defaultmark = 1.0;
1642         $mc->qtype = 'multichoice';
1644         $mc->layout = 0;
1645         $mc->single = 1;
1646         $mc->shuffleanswers = 0;
1647         $mc->correctfeedback =          array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1648         $mc->partiallycorrectfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1649         $mc->incorrectfeedback =        array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1650         $mc->answernumbering = 0;
1652         $mc->answer = array(
1653             array('text' => 'Bow-wow',     'format' => FORMAT_HTML, 'itemid' => null),
1654             array('text' => 'Wiggly worm', 'format' => FORMAT_HTML, 'itemid' => null),
1655             array('text' => 'Pussy-cat',   'format' => FORMAT_HTML, 'itemid' => null),
1656         );
1657         $mc->fraction = array(0, 0, 1);
1658         $mc->feedback = array(
1659             array('text' => 'You seem to have a dog obsessions!', 'format' => FORMAT_HTML, 'itemid' => null),
1660             array('text' => 'Now you are just being ridiculous!', 'format' => FORMAT_HTML, 'itemid' => null),
1661             array('text' => 'Well done!',                         'format' => FORMAT_HTML, 'itemid' => null),
1662         );
1664         $expectedqa->options = new stdClass();
1665         $expectedqa->options->questions = array(
1666             1 => $sa,
1667             2 => $mc,
1668         );
1669         $expectedqa->tags = array('tagCloze', 'tagTest');
1671         $this->assertEquals($expectedqa->hint, $q->hint);
1672         $this->assertEquals($expectedqa->options->questions[1], $q->options->questions[1]);
1673         $this->assertEquals($expectedqa->options->questions[2], $q->options->questions[2]);
1674         $this->assert(new question_check_specified_fields_expectation($expectedqa), $q);
1675     }
1677     public function test_export_multianswer() {
1678         $qdata = test_question_maker::get_question_data('multianswer', 'twosubq');
1679         $qdata->contextid = \context_system::instance()->id;
1680         $exporter = new qformat_xml();
1681         $xml = $exporter->writequestion($qdata);
1683         $expectedxml = '<!-- question: 0  -->
1684   <question type="cloze">
1685     <name>
1686       <text>Simple multianswer</text>
1687     </name>
1688     <questiontext format="html">
1689       <text><![CDATA[Complete this opening line of verse: "The {1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer} and the {1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!} went to sea".]]></text>
1690     </questiontext>
1691     <generalfeedback format="html">
1692       <text><![CDATA[General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea]]></text>
1693     </generalfeedback>
1694     <penalty>0.3333333</penalty>
1695     <hidden>0</hidden>
1696     <idnumber></idnumber>
1697     <hint format="html">
1698       <text>Hint 1</text>
1699     </hint>
1700     <hint format="html">
1701       <text>Hint 2</text>
1702     </hint>
1703   </question>
1704 ';
1706         $this->assert_same_xml($expectedxml, $xml);
1707     }
1709     public function test_export_multianswer_withdollars() {
1710         $qdata = test_question_maker::get_question_data('multianswer', 'dollarsigns');
1711         $qdata->contextid = \context_system::instance()->id;
1712         $exporter = new qformat_xml();
1713         $xml = $exporter->writequestion($qdata);
1715         $expectedxml = '<!-- question: 0  -->
1716   <question type="cloze">
1717     <name>
1718       <text>Multianswer with $s</text>
1719     </name>
1720     <questiontext format="html">
1721       <text>Which is the right order? {1:MULTICHOICE:=y,y,$3~$3,y,y}</text>
1722     </questiontext>
1723     <generalfeedback format="html">
1724       <text></text>
1725     </generalfeedback>
1726     <penalty>0.3333333</penalty>
1727     <hidden>0</hidden>
1728     <idnumber></idnumber>
1729   </question>
1730 ';
1732         $this->assert_same_xml($expectedxml, $xml);
1733     }
1735     public function test_import_files_as_draft() {
1736         $this->resetAfterTest();
1737         $this->setAdminUser();
1739         $xml = <<<END
1740 <questiontext format="html">
1741     <text><![CDATA[<p><a href="@@PLUGINFILE@@/moodle.txt">This text file</a> contains the word 'Moodle'.</p>]]></text>
1742     <file name="moodle.txt" encoding="base64">TW9vZGxl</file>
1743 </questiontext>
1744 END;
1746         $textxml = xmlize($xml);
1747         $qo = new stdClass();
1749         $importer = new qformat_xml();
1750         $draftitemid = $importer->import_files_as_draft($textxml['questiontext']['#']['file']);
1751         $files = file_get_drafarea_files($draftitemid);
1753         $this->assertEquals(1, count($files->list));
1755         $file = $files->list[0];
1756         $this->assertEquals('moodle.txt', $file->filename);
1757         $this->assertEquals('/',          $file->filepath);
1758         $this->assertEquals(6,            $file->size);
1759     }
1761     public function test_import_truefalse_wih_files() {
1762         $this->resetAfterTest();
1763         $this->setAdminUser();
1765         $xml = '<question type="truefalse">
1766     <name>
1767       <text>truefalse</text>
1768     </name>
1769     <questiontext format="html">
1770       <text><![CDATA[<p><a href="@@PLUGINFILE@@/myfolder/moodle.txt">This text file</a> contains the word Moodle.</p>]]></text>
1771 <file name="moodle.txt" path="/myfolder/" encoding="base64">TW9vZGxl</file>
1772     </questiontext>
1773     <generalfeedback format="html">
1774       <text><![CDATA[<p>For further information, see the documentation about Moodle.</p>]]></text>
1775 </generalfeedback>
1776     <defaultgrade>1.0000000</defaultgrade>
1777     <penalty>1.0000000</penalty>
1778     <hidden>0</hidden>
1779     <answer fraction="100" format="moodle_auto_format">
1780       <text>true</text>
1781       <feedback format="html">
1782         <text></text>
1783       </feedback>
1784     </answer>
1785     <answer fraction="0" format="moodle_auto_format">
1786       <text>false</text>
1787       <feedback format="html">
1788         <text></text>
1789       </feedback>
1790     </answer>
1791   </question>';
1792         $xmldata = xmlize($xml);
1794         $importer = new qformat_xml();
1795         $q = $importer->import_truefalse($xmldata['question']);
1797         $draftitemid = $q->questiontextitemid;
1798         $files = file_get_drafarea_files($draftitemid, '/myfolder/');
1800         $this->assertEquals(1, count($files->list));
1802         $file = $files->list[0];
1803         $this->assertEquals('moodle.txt', $file->filename);
1804         $this->assertEquals('/myfolder/', $file->filepath);
1805         $this->assertEquals(6,            $file->size);
1806     }
1808     public function test_create_dummy_question() {
1810         $testobject = new mock_qformat_xml();
1811         $categoryname = 'name1';
1812         $categoryinfo = new stdClass();
1813         $categoryinfo->info = 'info1';
1814         $categoryinfo->infoformat = 'infoformat1';
1815         $categoryinfo->idnumber = null;
1816         $dummyquestion = $testobject->mock_create_dummy_question_representing_category($categoryname, $categoryinfo);
1818         $this->assertEquals('category', $dummyquestion->qtype);
1819         $this->assertEquals($categoryname, $dummyquestion->category);
1820         $this->assertEquals($categoryinfo->info, $dummyquestion->info);
1821         $this->assertEquals($categoryinfo->infoformat, $dummyquestion->infoformat);
1822         $this->assertEquals('Switch category to ' . $categoryname, $dummyquestion->name);
1823         $this->assertEquals(0, $dummyquestion->id);
1824         $this->assertEquals('', $dummyquestion->questiontextformat);
1825         $this->assertEquals(0, $dummyquestion->contextid);
1826     }
1829 /**
1830  * Class mock_qformat_xml exists only to enable testing of the create dummy question category.
1831  * @package    qformat_xml
1832  * @copyright  2018 The Open University
1833  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1834  */
1835 class mock_qformat_xml extends qformat_xml {
1836     /**
1837      * Make public an otherwise protected function.
1838      * @param string $categoryname the name of the category
1839      * @param object $categoryinfo description of the category
1840      */
1841     public function mock_create_dummy_question_representing_category(string $categoryname, $categoryinfo) {
1842         return $this->create_dummy_question_representing_category($categoryname, $categoryinfo);
1843     }