Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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/>.
  16  
  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   */
  24  
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  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');
  32  
  33  
  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      }
  45  
  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      }
  68  
  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      }
  80  
  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              }
  97  
  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              }
 107  
 108          } else {
 109              $newvar = $var;
 110          }
 111  
 112          return $newvar;
 113      }
 114  
 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      }
 120  
 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      }
 126  
 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));
 132  
 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      }
 137  
 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));
 143  
 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      }
 148  
 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          );
 168  
 169          $exporter = new qformat_xml();
 170          $xml = $exporter->writequestion($q);
 171  
 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      }
 178  
 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';
 186  
 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;
 197  
 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          );
 203  
 204          $exporter = new qformat_xml();
 205          $xml = $exporter->writequestion($q);
 206  
 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      }
 218  
 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;
 232  
 233          $questionxml = xmlize($xml);
 234          $qo = new stdClass();
 235  
 236          $importer = new qformat_xml();
 237          $importer->import_hints($qo, $questionxml['question'], false, false, 'html');
 238  
 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      }
 248  
 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;
 262  
 263          $questionxml = xmlize($xml);
 264          $qo = new stdClass();
 265  
 266          $importer = new qformat_xml();
 267          $importer->import_hints($qo, $questionxml['question'], true, true, 'html');
 268  
 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      }
 278  
 279      public function test_import_no_hints_no_error() {
 280          $xml = <<<END
 281  <question>
 282  </question>
 283  END;
 284  
 285          $questionxml = xmlize($xml);
 286          $qo = new stdClass();
 287  
 288          $importer = new qformat_xml();
 289          $importer->import_hints($qo, $questionxml['question'], 'html');
 290  
 291          $this->assertFalse(isset($qo->hint));
 292      }
 293  
 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);
 314  
 315          $importer = new qformat_xml();
 316          $q = $importer->import_description($xmldata['question']);
 317  
 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');
 328  
 329          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 330      }
 331  
 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;
 347  
 348          $exporter = new qformat_xml();
 349          $xml = $exporter->writequestion($qdata);
 350  
 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  ';
 368  
 369          $this->assert_same_xml($expectedxml, $xml);
 370      }
 371  
 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);
 393  
 394          $importer = new qformat_xml();
 395          $q = $importer->import_essay($xmldata['question']);
 396  
 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->attachments = 0;
 410          $expectedq->attachmentsrequired = 0;
 411          $expectedq->maxbytes = 0;
 412          $expectedq->filetypeslist = null;
 413          $expectedq->graderinfo['text'] = '';
 414          $expectedq->graderinfo['format'] = FORMAT_MOODLE;
 415          $expectedq->responsetemplate['text'] = '';
 416          $expectedq->responsetemplate['format'] = FORMAT_MOODLE;
 417          $expectedq->tags = array('tagEssay', 'tagEssay20', 'tagTest');
 418  
 419          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 420      }
 421  
 422      public function test_import_essay_21() {
 423          $xml = '  <question type="essay">
 424      <name>
 425        <text>An essay</text>
 426      </name>
 427      <questiontext format="moodle_auto_format">
 428        <text>Write something.</text>
 429      </questiontext>
 430      <generalfeedback>
 431        <text>I hope you wrote something interesting.</text>
 432      </generalfeedback>
 433      <defaultgrade>1</defaultgrade>
 434      <penalty>0</penalty>
 435      <hidden>0</hidden>
 436      <responseformat>monospaced</responseformat>
 437      <responserequired>0</responserequired>
 438      <responsefieldlines>42</responsefieldlines>
 439      <attachments>-1</attachments>
 440      <attachmentsrequired>1</attachmentsrequired>
 441      <graderinfo format="html">
 442          <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
 443      </graderinfo>
 444      <responsetemplate format="html">
 445          <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
 446      </responsetemplate>
 447      <tags>
 448        <tag><text>tagEssay</text></tag>
 449        <tag><text>tagEssay21</text></tag>
 450        <tag><text>tagTest</text></tag>
 451      </tags>
 452    </question>';
 453          $xmldata = xmlize($xml);
 454  
 455          $importer = new qformat_xml();
 456          $q = $importer->import_essay($xmldata['question']);
 457  
 458          $expectedq = new stdClass();
 459          $expectedq->qtype = 'essay';
 460          $expectedq->name = 'An essay';
 461          $expectedq->questiontext = 'Write something.';
 462          $expectedq->questiontextformat = FORMAT_MOODLE;
 463          $expectedq->generalfeedback = 'I hope you wrote something interesting.';
 464          $expectedq->defaultmark = 1;
 465          $expectedq->length = 1;
 466          $expectedq->penalty = 0;
 467          $expectedq->responseformat = 'monospaced';
 468          $expectedq->responserequired = 0;
 469          $expectedq->responsefieldlines = 42;
 470          $expectedq->attachments = -1;
 471          $expectedq->attachmentsrequired = 1;
 472          $expectedq->maxbytes = 0;
 473          $expectedq->filetypeslist = null;
 474          $expectedq->graderinfo['text'] = '<p>Grade <b>generously</b>!</p>';
 475          $expectedq->graderinfo['format'] = FORMAT_HTML;
 476          $expectedq->responsetemplate['text'] = '<p>Here is something <b>really</b> interesting.</p>';
 477          $expectedq->responsetemplate['format'] = FORMAT_HTML;
 478          $expectedq->tags = array('tagEssay', 'tagEssay21', 'tagTest');
 479  
 480          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 481      }
 482  
 483      public function test_export_essay() {
 484          $qdata = new stdClass();
 485          $qdata->id = 123;
 486          $qdata->contextid = \context_system::instance()->id;
 487          $qdata->qtype = 'essay';
 488          $qdata->name = 'An essay';
 489          $qdata->questiontext = 'Write something.';
 490          $qdata->questiontextformat = FORMAT_MOODLE;
 491          $qdata->generalfeedback = 'I hope you wrote something interesting.';
 492          $qdata->generalfeedbackformat = FORMAT_MOODLE;
 493          $qdata->defaultmark = 1;
 494          $qdata->length = 1;
 495          $qdata->penalty = 0;
 496          $qdata->hidden = 0;
 497          $qdata->idnumber = null;
 498          $qdata->options = new stdClass();
 499          $qdata->options->id = 456;
 500          $qdata->options->questionid = 123;
 501          $qdata->options->responseformat = 'monospaced';
 502          $qdata->options->responserequired = 0;
 503          $qdata->options->responsefieldlines = 42;
 504          $qdata->options->attachments = -1;
 505          $qdata->options->attachmentsrequired = 1;
 506          $qdata->options->graderinfo = '<p>Grade <b>generously</b>!</p>';
 507          $qdata->options->graderinfoformat = FORMAT_HTML;
 508          $qdata->options->responsetemplate = '<p>Here is something <b>really</b> interesting.</p>';
 509          $qdata->options->responsetemplateformat = FORMAT_HTML;
 510          $qdata->options->maxbytes = 52428800; // 50MB.
 511          $qdata->options->filetypeslist = '.pdf,.zip.,.docx';
 512          $exporter = new qformat_xml();
 513          $xml = $exporter->writequestion($qdata);
 514  
 515          $expectedxml = '<!-- question: 123  -->
 516    <question type="essay">
 517      <name>
 518        <text>An essay</text>
 519      </name>
 520      <questiontext format="moodle_auto_format">
 521        <text>Write something.</text>
 522      </questiontext>
 523      <generalfeedback format="moodle_auto_format">
 524        <text>I hope you wrote something interesting.</text>
 525      </generalfeedback>
 526      <defaultgrade>1</defaultgrade>
 527      <penalty>0</penalty>
 528      <hidden>0</hidden>
 529      <idnumber></idnumber>
 530      <responseformat>monospaced</responseformat>
 531      <responserequired>0</responserequired>
 532      <responsefieldlines>42</responsefieldlines>
 533      <attachments>-1</attachments>
 534      <attachmentsrequired>1</attachmentsrequired>
 535      <maxbytes>52428800</maxbytes>
 536      <filetypeslist>.pdf,.zip.,.docx</filetypeslist>
 537      <graderinfo format="html">
 538        <text><![CDATA[<p>Grade <b>generously</b>!</p>]]></text>
 539      </graderinfo>
 540      <responsetemplate format="html">
 541        <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
 542      </responsetemplate>
 543    </question>
 544  ';
 545  
 546          $this->assert_same_xml($expectedxml, $xml);
 547      }
 548  
 549      public function test_import_match_19() {
 550          $xml = '  <question type="matching">
 551      <name>
 552        <text>Matching question</text>
 553      </name>
 554      <questiontext format="html">
 555        <text>Match the upper and lower case letters.</text>
 556      </questiontext>
 557      <generalfeedback>
 558        <text>The answer is A -> a, B -> b and C -> c.</text>
 559      </generalfeedback>
 560      <defaultgrade>1</defaultgrade>
 561      <penalty>0.3333333</penalty>
 562      <hidden>0</hidden>
 563      <shuffleanswers>false</shuffleanswers>
 564      <correctfeedback>
 565        <text>Well done.</text>
 566      </correctfeedback>
 567      <partiallycorrectfeedback>
 568        <text>Not entirely.</text>
 569      </partiallycorrectfeedback>
 570      <incorrectfeedback>
 571        <text>Completely wrong!</text>
 572      </incorrectfeedback>
 573      <subquestion>
 574        <text>A</text>
 575        <answer>
 576          <text>a</text>
 577        </answer>
 578      </subquestion>
 579      <subquestion>
 580        <text>B</text>
 581        <answer>
 582          <text>b</text>
 583        </answer>
 584      </subquestion>
 585      <subquestion>
 586        <text>C</text>
 587        <answer>
 588          <text>c</text>
 589        </answer>
 590      </subquestion>
 591      <subquestion>
 592        <text></text>
 593        <answer>
 594          <text>d</text>
 595        </answer>
 596      </subquestion>
 597      <hint>
 598        <text>Hint 1</text>
 599        <shownumcorrect />
 600      </hint>
 601      <hint>
 602        <text></text>
 603        <shownumcorrect />
 604        <clearwrong />
 605      </hint>
 606      <tags>
 607        <tag><text>tagMatching</text></tag>
 608        <tag><text>tagTest</text></tag>
 609      </tags>
 610    </question>';
 611          $xmldata = xmlize($xml);
 612  
 613          $importer = new qformat_xml();
 614          $q = $importer->import_match($xmldata['question']);
 615  
 616          $expectedq = new stdClass();
 617          $expectedq->qtype = 'match';
 618          $expectedq->name = 'Matching question';
 619          $expectedq->questiontext = 'Match the upper and lower case letters.';
 620          $expectedq->questiontextformat = FORMAT_HTML;
 621          $expectedq->correctfeedback = array('text' => 'Well done.',
 622                  'format' => FORMAT_HTML);
 623          $expectedq->partiallycorrectfeedback = array('text' => 'Not entirely.',
 624                  'format' => FORMAT_HTML);
 625          $expectedq->shownumcorrect = false;
 626          $expectedq->incorrectfeedback = array('text' => 'Completely wrong!',
 627                  'format' => FORMAT_HTML);
 628          $expectedq->generalfeedback = 'The answer is A -> a, B -> b and C -> c.';
 629          $expectedq->generalfeedbackformat = FORMAT_HTML;
 630          $expectedq->defaultmark = 1;
 631          $expectedq->length = 1;
 632          $expectedq->penalty = 0.3333333;
 633          $expectedq->shuffleanswers = 0;
 634          $expectedq->subquestions = array(
 635              array('text' => 'A', 'format' => FORMAT_HTML),
 636              array('text' => 'B', 'format' => FORMAT_HTML),
 637              array('text' => 'C', 'format' => FORMAT_HTML),
 638              array('text' => '', 'format' => FORMAT_HTML));
 639          $expectedq->subanswers = array('a', 'b', 'c', 'd');
 640          $expectedq->hint = array(
 641              array('text' => 'Hint 1', 'format' => FORMAT_HTML),
 642              array('text' => '', 'format' => FORMAT_HTML),
 643          );
 644          $expectedq->hintshownumcorrect = array(true, true);
 645          $expectedq->hintclearwrong = array(false, true);
 646          $expectedq->tags = array('tagMatching', 'tagTest');
 647  
 648          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 649      }
 650  
 651      public function test_export_match() {
 652          $qdata = new stdClass();
 653          $qdata->id = 123;
 654          $qdata->contextid = \context_system::instance()->id;
 655          $qdata->qtype = 'match';
 656          $qdata->name = 'Matching question';
 657          $qdata->questiontext = 'Match the upper and lower case letters.';
 658          $qdata->questiontextformat = FORMAT_HTML;
 659          $qdata->generalfeedback = 'The answer is A -> a, B -> b and C -> c.';
 660          $qdata->generalfeedbackformat = FORMAT_HTML;
 661          $qdata->defaultmark = 1;
 662          $qdata->length = 1;
 663          $qdata->penalty = 0.3333333;
 664          $qdata->hidden = 0;
 665          $qdata->idnumber = null;
 666  
 667          $qdata->options = new stdClass();
 668          $qdata->options->shuffleanswers = 1;
 669          $qdata->options->correctfeedback = 'Well done.';
 670          $qdata->options->correctfeedbackformat = FORMAT_HTML;
 671          $qdata->options->partiallycorrectfeedback = 'Not entirely.';
 672          $qdata->options->partiallycorrectfeedbackformat = FORMAT_HTML;
 673          $qdata->options->shownumcorrect = false;
 674          $qdata->options->incorrectfeedback = 'Completely wrong!';
 675          $qdata->options->incorrectfeedbackformat = FORMAT_HTML;
 676  
 677          $subq1 = new stdClass();
 678          $subq1->id = -4;
 679          $subq1->questiontext = 'A';
 680          $subq1->questiontextformat = FORMAT_HTML;
 681          $subq1->answertext = 'a';
 682  
 683          $subq2 = new stdClass();
 684          $subq2->id = -3;
 685          $subq2->questiontext = 'B';
 686          $subq2->questiontextformat = FORMAT_HTML;
 687          $subq2->answertext = 'b';
 688  
 689          $subq3 = new stdClass();
 690          $subq3->id = -2;
 691          $subq3->questiontext = 'C';
 692          $subq3->questiontextformat = FORMAT_HTML;
 693          $subq3->answertext = 'c';
 694  
 695          $subq4 = new stdClass();
 696          $subq4->id = -1;
 697          $subq4->questiontext = '';
 698          $subq4->questiontextformat = FORMAT_HTML;
 699          $subq4->answertext = 'd';
 700  
 701          $qdata->options->subquestions = array(
 702                  $subq1, $subq2, $subq3, $subq4);
 703  
 704          $qdata->hints = array(
 705              new question_hint_with_parts(0, 'Hint 1', FORMAT_HTML, true, false),
 706              new question_hint_with_parts(0, '', FORMAT_HTML, true, true),
 707          );
 708  
 709          $exporter = new qformat_xml();
 710          $xml = $exporter->writequestion($qdata);
 711  
 712          $expectedxml = '<!-- question: 123  -->
 713    <question type="matching">
 714      <name>
 715        <text>Matching question</text>
 716      </name>
 717      <questiontext format="html">
 718        <text>Match the upper and lower case letters.</text>
 719      </questiontext>
 720      <generalfeedback format="html">
 721        <text><![CDATA[The answer is A -> a, B -> b and C -> c.]]></text>
 722      </generalfeedback>
 723      <defaultgrade>1</defaultgrade>
 724      <penalty>0.3333333</penalty>
 725      <hidden>0</hidden>
 726      <idnumber></idnumber>
 727      <shuffleanswers>true</shuffleanswers>
 728      <correctfeedback format="html">
 729        <text>Well done.</text>
 730      </correctfeedback>
 731      <partiallycorrectfeedback format="html">
 732        <text>Not entirely.</text>
 733      </partiallycorrectfeedback>
 734      <incorrectfeedback format="html">
 735        <text>Completely wrong!</text>
 736      </incorrectfeedback>
 737      <subquestion format="html">
 738        <text>A</text>
 739        <answer>
 740          <text>a</text>
 741        </answer>
 742      </subquestion>
 743      <subquestion format="html">
 744        <text>B</text>
 745        <answer>
 746          <text>b</text>
 747        </answer>
 748      </subquestion>
 749      <subquestion format="html">
 750        <text>C</text>
 751        <answer>
 752          <text>c</text>
 753        </answer>
 754      </subquestion>
 755      <subquestion format="html">
 756        <text></text>
 757        <answer>
 758          <text>d</text>
 759        </answer>
 760      </subquestion>
 761      <hint format="html">
 762        <text>Hint 1</text>
 763        <shownumcorrect/>
 764      </hint>
 765      <hint format="html">
 766        <text></text>
 767        <shownumcorrect/>
 768        <clearwrong/>
 769      </hint>
 770    </question>
 771  ';
 772  
 773          $this->assert_same_xml($expectedxml, $xml);
 774      }
 775  
 776      public function test_import_multichoice_19() {
 777          $xml = '  <question type="multichoice">
 778      <name>
 779        <text>Multiple choice question</text>
 780      </name>
 781      <questiontext format="html">
 782        <text>Which are the even numbers?</text>
 783      </questiontext>
 784      <generalfeedback>
 785        <text>The even numbers are 2 and 4.</text>
 786      </generalfeedback>
 787      <defaultgrade>2</defaultgrade>
 788      <penalty>0.3333333</penalty>
 789      <hidden>0</hidden>
 790      <single>false</single>
 791      <shuffleanswers>false</shuffleanswers>
 792      <answernumbering>abc</answernumbering>
 793      <correctfeedback>
 794        <text><![CDATA[<p>Your answer is correct.</p>]]></text>
 795      </correctfeedback>
 796      <partiallycorrectfeedback>
 797        <text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
 798      </partiallycorrectfeedback>
 799      <incorrectfeedback>
 800        <text><![CDATA[<p>Your answer is incorrect.</p>]]></text>
 801      </incorrectfeedback>
 802      <shownumcorrect/>
 803      <answer fraction="0">
 804        <text>1</text>
 805        <feedback>
 806          <text></text>
 807        </feedback>
 808      </answer>
 809      <answer fraction="100">
 810        <text>2</text>
 811        <feedback>
 812          <text></text>
 813        </feedback>
 814      </answer>
 815      <answer fraction="0">
 816        <text>3</text>
 817        <feedback>
 818          <text></text>
 819        </feedback>
 820      </answer>
 821      <answer fraction="100">
 822        <text>4</text>
 823        <feedback>
 824          <text></text>
 825        </feedback>
 826      </answer>
 827      <hint>
 828        <text>Hint 1.</text>
 829      </hint>
 830      <hint>
 831        <text>Hint 2.</text>
 832      </hint>
 833    </question>';
 834          $xmldata = xmlize($xml);
 835  
 836          $importer = new qformat_xml();
 837          $q = $importer->import_multichoice($xmldata['question']);
 838  
 839          $expectedq = new stdClass();
 840          $expectedq->qtype = 'multichoice';
 841          $expectedq->name = 'Multiple choice question';
 842          $expectedq->questiontext = 'Which are the even numbers?';
 843          $expectedq->questiontextformat = FORMAT_HTML;
 844          $expectedq->correctfeedback = array(
 845                  'text'   => '<p>Your answer is correct.</p>',
 846                  'format' => FORMAT_HTML);
 847          $expectedq->shownumcorrect = false;
 848          $expectedq->partiallycorrectfeedback = array(
 849                  'text'   => '<p>Your answer is partially correct.</p>',
 850                  'format' => FORMAT_HTML);
 851          $expectedq->shownumcorrect = true;
 852          $expectedq->incorrectfeedback = array(
 853                  'text'   => '<p>Your answer is incorrect.</p>',
 854                  'format' => FORMAT_HTML);
 855          $expectedq->generalfeedback = 'The even numbers are 2 and 4.';
 856          $expectedq->defaultmark = 2;
 857          $expectedq->length = 1;
 858          $expectedq->penalty = 0.3333333;
 859          $expectedq->shuffleanswers = 0;
 860          $expectedq->single = false;
 861  
 862          $expectedq->answer = array(
 863              array('text' => '1', 'format' => FORMAT_HTML),
 864              array('text' => '2', 'format' => FORMAT_HTML),
 865              array('text' => '3', 'format' => FORMAT_HTML),
 866              array('text' => '4', 'format' => FORMAT_HTML));
 867          $expectedq->fraction = array(0, 1, 0, 1);
 868          $expectedq->feedback = array(
 869              array('text' => '', 'format' => FORMAT_HTML),
 870              array('text' => '', 'format' => FORMAT_HTML),
 871              array('text' => '', 'format' => FORMAT_HTML),
 872              array('text' => '', 'format' => FORMAT_HTML));
 873  
 874          $expectedq->hint = array(
 875              array('text' => 'Hint 1.', 'format' => FORMAT_HTML),
 876              array('text' => 'Hint 2.', 'format' => FORMAT_HTML),
 877          );
 878          $expectedq->hintshownumcorrect = array(false, false);
 879          $expectedq->hintclearwrong = array(false, false);
 880  
 881          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
 882      }
 883  
 884      public function test_export_multichoice() {
 885          $qdata = new stdClass();
 886          $qdata->id = 123;
 887          $qdata->contextid = \context_system::instance()->id;
 888          $qdata->qtype = 'multichoice';
 889          $qdata->name = 'Multiple choice question';
 890          $qdata->questiontext = 'Which are the even numbers?';
 891          $qdata->questiontextformat = FORMAT_HTML;
 892          $qdata->generalfeedback = 'The even numbers are 2 and 4.';
 893          $qdata->generalfeedbackformat = FORMAT_HTML;
 894          $qdata->defaultmark = 2;
 895          $qdata->length = 1;
 896          $qdata->penalty = 0.3333333;
 897          $qdata->hidden = 0;
 898          $qdata->idnumber = null;
 899  
 900          $qdata->options = new stdClass();
 901          $qdata->options->single = 0;
 902          $qdata->options->shuffleanswers = 0;
 903          $qdata->options->answernumbering = 'abc';
 904          $qdata->options->showstandardinstruction = 0;
 905          $qdata->options->correctfeedback = '<p>Your answer is correct.</p>';
 906          $qdata->options->correctfeedbackformat = FORMAT_HTML;
 907          $qdata->options->partiallycorrectfeedback = '<p>Your answer is partially correct.</p>';
 908          $qdata->options->partiallycorrectfeedbackformat = FORMAT_HTML;
 909          $qdata->options->shownumcorrect = 1;
 910          $qdata->options->incorrectfeedback = '<p>Your answer is incorrect.</p>';
 911          $qdata->options->incorrectfeedbackformat = FORMAT_HTML;
 912  
 913          $qdata->options->answers = array(
 914              13 => new question_answer(13, '1', 0, '', FORMAT_HTML),
 915              14 => new question_answer(14, '2', 1, '', FORMAT_HTML),
 916              15 => new question_answer(15, '3', 0, '', FORMAT_HTML),
 917              16 => new question_answer(16, '4', 1, '', FORMAT_HTML),
 918          );
 919  
 920          $qdata->hints = array(
 921              new question_hint_with_parts(0, 'Hint 1.', FORMAT_HTML, false, false),
 922              new question_hint_with_parts(0, 'Hint 2.', FORMAT_HTML, false, false),
 923          );
 924  
 925          $exporter = new qformat_xml();
 926          $xml = $exporter->writequestion($qdata);
 927  
 928          $expectedxml = '<!-- question: 123  -->
 929    <question type="multichoice">
 930      <name>
 931        <text>Multiple choice question</text>
 932      </name>
 933      <questiontext format="html">
 934        <text>Which are the even numbers?</text>
 935      </questiontext>
 936      <generalfeedback format="html">
 937        <text>The even numbers are 2 and 4.</text>
 938      </generalfeedback>
 939      <defaultgrade>2</defaultgrade>
 940      <penalty>0.3333333</penalty>
 941      <hidden>0</hidden>
 942      <idnumber></idnumber>
 943      <single>false</single>
 944      <shuffleanswers>false</shuffleanswers>
 945      <answernumbering>abc</answernumbering>
 946      <showstandardinstruction>0</showstandardinstruction>
 947      <correctfeedback format="html">
 948        <text><![CDATA[<p>Your answer is correct.</p>]]></text>
 949      </correctfeedback>
 950      <partiallycorrectfeedback format="html">
 951        <text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
 952      </partiallycorrectfeedback>
 953      <incorrectfeedback format="html">
 954        <text><![CDATA[<p>Your answer is incorrect.</p>]]></text>
 955      </incorrectfeedback>
 956      <shownumcorrect/>
 957      <answer fraction="0" format="plain_text">
 958        <text>1</text>
 959        <feedback format="html">
 960          <text></text>
 961        </feedback>
 962      </answer>
 963      <answer fraction="100" format="plain_text">
 964        <text>2</text>
 965        <feedback format="html">
 966          <text></text>
 967        </feedback>
 968      </answer>
 969      <answer fraction="0" format="plain_text">
 970        <text>3</text>
 971        <feedback format="html">
 972          <text></text>
 973        </feedback>
 974      </answer>
 975      <answer fraction="100" format="plain_text">
 976        <text>4</text>
 977        <feedback format="html">
 978          <text></text>
 979        </feedback>
 980      </answer>
 981      <hint format="html">
 982        <text>Hint 1.</text>
 983      </hint>
 984      <hint format="html">
 985        <text>Hint 2.</text>
 986      </hint>
 987    </question>
 988  ';
 989  
 990          $this->assert_same_xml($expectedxml, $xml);
 991      }
 992  
 993      public function test_import_numerical_19() {
 994          $xml = '  <question type="numerical">
 995      <name>
 996        <text>Numerical question</text>
 997      </name>
 998      <questiontext format="html">
 999        <text>What is the answer?</text>
1000      </questiontext>
1001      <generalfeedback>
1002        <text>General feedback: Think Hitch-hikers guide to the Galaxy.</text>
1003      </generalfeedback>
1004      <defaultgrade>1</defaultgrade>
1005      <penalty>0.1</penalty>
1006      <hidden>0</hidden>
1007      <answer fraction="100">
1008        <text>42</text>
1009        <feedback>
1010          <text>Well done!</text>
1011        </feedback>
1012        <tolerance>0.001</tolerance>
1013      </answer>
1014      <answer fraction="0">
1015        <text>13</text>
1016        <feedback>
1017          <text>What were you thinking?!</text>
1018        </feedback>
1019        <tolerance>1</tolerance>
1020      </answer>
1021      <answer fraction="0">
1022        <text>*</text>
1023        <feedback>
1024          <text>Completely wrong.</text>
1025        </feedback>
1026        <tolerance></tolerance>
1027      </answer>
1028    </question>';
1029          $xmldata = xmlize($xml);
1030  
1031          $importer = new qformat_xml();
1032          $q = $importer->import_numerical($xmldata['question']);
1033  
1034          $expectedq = new stdClass();
1035          $expectedq->qtype = 'numerical';
1036          $expectedq->name = 'Numerical question';
1037          $expectedq->questiontext = 'What is the answer?';
1038          $expectedq->questiontextformat = FORMAT_HTML;
1039          $expectedq->generalfeedback = 'General feedback: Think Hitch-hikers guide to the Galaxy.';
1040          $expectedq->generalfeedbackformat = FORMAT_HTML;
1041          $expectedq->defaultmark = 1;
1042          $expectedq->length = 1;
1043          $expectedq->penalty = 0.1;
1044  
1045          $expectedq->answer = array('42', '13', '*');
1046          $expectedq->fraction = array(1, 0, 0);
1047          $expectedq->feedback = array(
1048              array('text' => 'Well done!',
1049                      'format' => FORMAT_HTML),
1050              array('text' => 'What were you thinking?!',
1051                      'format' => FORMAT_HTML),
1052              array('text' => 'Completely wrong.',
1053                      'format' => FORMAT_HTML));
1054          $expectedq->tolerance = array(0.001, 1, 0);
1055  
1056          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1057      }
1058  
1059      public function test_export_numerical() {
1060          question_bank::load_question_definition_classes('numerical');
1061  
1062          $qdata = new stdClass();
1063          $qdata->id = 123;
1064          $qdata->contextid = \context_system::instance()->id;
1065          $qdata->qtype = 'numerical';
1066          $qdata->name = 'Numerical question';
1067          $qdata->questiontext = 'What is the answer?';
1068          $qdata->questiontextformat = FORMAT_HTML;
1069          $qdata->generalfeedback = 'General feedback: Think Hitch-hikers guide to the Galaxy.';
1070          $qdata->generalfeedbackformat = FORMAT_HTML;
1071          $qdata->defaultmark = 1;
1072          $qdata->length = 1;
1073          $qdata->penalty = 0.1;
1074          $qdata->hidden = 0;
1075          $qdata->idnumber = null;
1076  
1077          $qdata->options = new stdClass();
1078          $qdata->options->answers = array(
1079              13 => new qtype_numerical_answer(13, '42', 1, 'Well done!',
1080                      FORMAT_HTML, 0.001),
1081              14 => new qtype_numerical_answer(14, '13', 0, 'What were you thinking?!',
1082                      FORMAT_HTML, 1),
1083              15 => new qtype_numerical_answer(15, '*', 0, 'Completely wrong.',
1084                      FORMAT_HTML, ''),
1085          );
1086  
1087          $qdata->options->units = array();
1088  
1089          $exporter = new qformat_xml();
1090          $xml = $exporter->writequestion($qdata);
1091  
1092          $expectedxml = '<!-- question: 123  -->
1093    <question type="numerical">
1094      <name>
1095        <text>Numerical question</text>
1096      </name>
1097      <questiontext format="html">
1098        <text>What is the answer?</text>
1099      </questiontext>
1100      <generalfeedback format="html">
1101        <text>General feedback: Think Hitch-hikers guide to the Galaxy.</text>
1102      </generalfeedback>
1103      <defaultgrade>1</defaultgrade>
1104      <penalty>0.1</penalty>
1105      <hidden>0</hidden>
1106      <idnumber></idnumber>
1107      <answer fraction="100" format="plain_text">
1108        <text>42</text>
1109        <feedback format="html">
1110          <text>Well done!</text>
1111        </feedback>
1112        <tolerance>0.001</tolerance>
1113      </answer>
1114      <answer fraction="0" format="plain_text">
1115        <text>13</text>
1116        <feedback format="html">
1117          <text>What were you thinking?!</text>
1118        </feedback>
1119        <tolerance>1</tolerance>
1120      </answer>
1121      <answer fraction="0" format="plain_text">
1122        <text>*</text>
1123        <feedback format="html">
1124          <text>Completely wrong.</text>
1125        </feedback>
1126        <tolerance>0</tolerance>
1127      </answer>
1128    </question>
1129  ';
1130  
1131          $this->assert_same_xml($expectedxml, $xml);
1132      }
1133  
1134      public function test_import_shortanswer_19() {
1135          $xml = '  <question type="shortanswer">
1136      <name>
1137        <text>Short answer question</text>
1138      </name>
1139      <questiontext format="html">
1140        <text>Fill in the gap in this sequence: Alpha, ________, Gamma.</text>
1141      </questiontext>
1142      <generalfeedback>
1143        <text>The answer is Beta.</text>
1144      </generalfeedback>
1145      <defaultgrade>1</defaultgrade>
1146      <penalty>0.3333333</penalty>
1147      <hidden>0</hidden>
1148      <usecase>0</usecase>
1149      <answer fraction="100" format="plain_text">
1150        <text>Beta</text>
1151        <feedback>
1152          <text>Well done!</text>
1153        </feedback>
1154      </answer>
1155      <answer fraction="0" format="plain_text">
1156        <text>*</text>
1157        <feedback>
1158          <text>Doh!</text>
1159        </feedback>
1160      </answer>
1161      <hint>
1162        <text>Hint 1</text>
1163      </hint>
1164      <hint>
1165        <text>Hint 2</text>
1166      </hint>
1167    </question>';
1168          $xmldata = xmlize($xml);
1169  
1170          $importer = new qformat_xml();
1171          $q = $importer->import_shortanswer($xmldata['question']);
1172  
1173          $expectedq = new stdClass();
1174          $expectedq->qtype = 'shortanswer';
1175          $expectedq->name = 'Short answer question';
1176          $expectedq->questiontext = 'Fill in the gap in this sequence: Alpha, ________, Gamma.';
1177          $expectedq->questiontextformat = FORMAT_HTML;
1178          $expectedq->generalfeedback = 'The answer is Beta.';
1179          $expectedq->usecase = false;
1180          $expectedq->defaultmark = 1;
1181          $expectedq->length = 1;
1182          $expectedq->penalty = 0.3333333;
1183  
1184          $expectedq->answer = array('Beta', '*');
1185          $expectedq->fraction = array(1, 0);
1186          $expectedq->feedback = array(
1187              array('text' => 'Well done!', 'format' => FORMAT_HTML),
1188              array('text' => 'Doh!', 'format' => FORMAT_HTML));
1189  
1190          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1191      }
1192  
1193      public function test_export_shortanswer() {
1194          $qdata = new stdClass();
1195          $qdata->id = 123;
1196          $qdata->contextid = \context_system::instance()->id;
1197          $qdata->qtype = 'shortanswer';
1198          $qdata->name = 'Short answer question';
1199          $qdata->questiontext = 'Fill in the gap in this sequence: Alpha, ________, Gamma.';
1200          $qdata->questiontextformat = FORMAT_HTML;
1201          $qdata->generalfeedback = 'The answer is Beta.';
1202          $qdata->generalfeedbackformat = FORMAT_HTML;
1203          $qdata->defaultmark = 1;
1204          $qdata->length = 1;
1205          $qdata->penalty = 0.3333333;
1206          $qdata->hidden = 0;
1207          $qdata->idnumber = null;
1208  
1209          $qdata->options = new stdClass();
1210          $qdata->options->usecase = 0;
1211  
1212          $qdata->options->answers = array(
1213              13 => new question_answer(13, 'Beta', 1, 'Well done!', FORMAT_HTML),
1214              14 => new question_answer(14, '*', 0, 'Doh!', FORMAT_HTML),
1215          );
1216  
1217          $qdata->hints = array(
1218              new question_hint(0, 'Hint 1', FORMAT_HTML),
1219              new question_hint(0, 'Hint 2', FORMAT_HTML),
1220          );
1221  
1222          $exporter = new qformat_xml();
1223          $xml = $exporter->writequestion($qdata);
1224  
1225          $expectedxml = '<!-- question: 123  -->
1226    <question type="shortanswer">
1227      <name>
1228        <text>Short answer question</text>
1229      </name>
1230      <questiontext format="html">
1231        <text>Fill in the gap in this sequence: Alpha, ________, Gamma.</text>
1232      </questiontext>
1233      <generalfeedback format="html">
1234        <text>The answer is Beta.</text>
1235      </generalfeedback>
1236      <defaultgrade>1</defaultgrade>
1237      <penalty>0.3333333</penalty>
1238      <hidden>0</hidden>
1239      <idnumber></idnumber>
1240      <usecase>0</usecase>
1241      <answer fraction="100" format="plain_text">
1242        <text>Beta</text>
1243        <feedback format="html">
1244          <text>Well done!</text>
1245        </feedback>
1246      </answer>
1247      <answer fraction="0" format="plain_text">
1248        <text>*</text>
1249        <feedback format="html">
1250          <text>Doh!</text>
1251        </feedback>
1252      </answer>
1253      <hint format="html">
1254        <text>Hint 1</text>
1255      </hint>
1256      <hint format="html">
1257        <text>Hint 2</text>
1258      </hint>
1259    </question>
1260  ';
1261  
1262          $this->assert_same_xml($expectedxml, $xml);
1263      }
1264  
1265      public function test_import_truefalse_19() {
1266          $xml = '  <question type="truefalse">
1267      <name>
1268        <text>True false question</text>
1269      </name>
1270      <questiontext format="html">
1271        <text>The answer is true.</text>
1272      </questiontext>
1273      <generalfeedback>
1274        <text>General feedback: You should have chosen true.</text>
1275      </generalfeedback>
1276      <defaultgrade>1</defaultgrade>
1277      <penalty>1</penalty>
1278      <hidden>0</hidden>
1279      <answer fraction="100">
1280        <text>true</text>
1281        <feedback>
1282          <text>Well done!</text>
1283        </feedback>
1284      </answer>
1285      <answer fraction="0">
1286        <text>false</text>
1287        <feedback>
1288          <text>Doh!</text>
1289        </feedback>
1290      </answer>
1291    </question>';
1292          $xmldata = xmlize($xml);
1293  
1294          $importer = new qformat_xml();
1295          $q = $importer->import_truefalse($xmldata['question']);
1296  
1297          $expectedq = new stdClass();
1298          $expectedq->qtype = 'truefalse';
1299          $expectedq->name = 'True false question';
1300          $expectedq->questiontext = 'The answer is true.';
1301          $expectedq->questiontextformat = FORMAT_HTML;
1302          $expectedq->generalfeedback = 'General feedback: You should have chosen true.';
1303          $expectedq->defaultmark = 1;
1304          $expectedq->length = 1;
1305          $expectedq->penalty = 1;
1306  
1307          $expectedq->feedbacktrue = array('text' => 'Well done!',
1308                  'format' => FORMAT_HTML);
1309          $expectedq->feedbackfalse = array('text' => 'Doh!',
1310                  'format' => FORMAT_HTML);
1311          $expectedq->correctanswer = true;
1312  
1313          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1314      }
1315  
1316      public function test_import_truefalse_with_idnumber() {
1317          $xml = '  <question type="truefalse">
1318      <name>
1319        <text>True false question</text>
1320      </name>
1321      <questiontext format="html">
1322        <text>The answer is true.</text>
1323      </questiontext>
1324      <generalfeedback>
1325        <text>General feedback: You should have chosen true.</text>
1326      </generalfeedback>
1327      <defaultgrade>1</defaultgrade>
1328      <penalty>1</penalty>
1329      <hidden>0</hidden>
1330      <idnumber>TestIdNum1</idnumber>
1331      <answer fraction="100">
1332        <text>true</text>
1333        <feedback>
1334          <text>Well done!</text>
1335        </feedback>
1336      </answer>
1337      <answer fraction="0">
1338        <text>false</text>
1339        <feedback>
1340          <text>Doh!</text>
1341        </feedback>
1342      </answer>
1343    </question>';
1344          $xmldata = xmlize($xml);
1345  
1346          $importer = new qformat_xml();
1347          $q = $importer->import_truefalse($xmldata['question']);
1348  
1349          $expectedq = new stdClass();
1350          $expectedq->qtype = 'truefalse';
1351          $expectedq->name = 'True false question';
1352          $expectedq->questiontext = 'The answer is true.';
1353          $expectedq->questiontextformat = FORMAT_HTML;
1354          $expectedq->generalfeedback = 'General feedback: You should have chosen true.';
1355          $expectedq->defaultmark = 1;
1356          $expectedq->length = 1;
1357          $expectedq->penalty = 1;
1358          $expectedq->idnumber = 'TestIdNum1';
1359  
1360          $expectedq->feedbacktrue = array('text' => 'Well done!',
1361                  'format' => FORMAT_HTML);
1362          $expectedq->feedbackfalse = array('text' => 'Doh!',
1363                  'format' => FORMAT_HTML);
1364          $expectedq->correctanswer = true;
1365  
1366          $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
1367      }
1368  
1369      public function test_export_truefalse() {
1370          $qdata = new stdClass();
1371          $qdata->id = 12;
1372          $qdata->contextid = \context_system::instance()->id;
1373          $qdata->qtype = 'truefalse';
1374          $qdata->name = 'True false question';
1375          $qdata->questiontext = 'The answer is true.';
1376          $qdata->questiontextformat = FORMAT_HTML;
1377          $qdata->generalfeedback = 'General feedback: You should have chosen true.';
1378          $qdata->generalfeedbackformat = FORMAT_HTML;
1379          $qdata->defaultmark = 1;
1380          $qdata->length = 1;
1381          $qdata->penalty = 1;
1382          $qdata->hidden = 0;
1383          $qdata->idnumber = null;
1384  
1385          $qdata->options = new stdClass();
1386          $qdata->options->answers = array(
1387              1 => new question_answer(1, 'True', 1, 'Well done!', FORMAT_HTML),
1388              2 => new question_answer(2, 'False', 0, 'Doh!', FORMAT_HTML),
1389          );
1390          $qdata->options->trueanswer = 1;
1391          $qdata->options->falseanswer = 2;
1392  
1393          $exporter = new qformat_xml();
1394          $xml = $exporter->writequestion($qdata);
1395  
1396          $expectedxml = '<!-- question: 12  -->
1397    <question type="truefalse">
1398      <name>
1399        <text>True false question</text>
1400      </name>
1401      <questiontext format="html">
1402        <text>The answer is true.</text>
1403      </questiontext>
1404      <generalfeedback format="html">
1405        <text>General feedback: You should have chosen true.</text>
1406      </generalfeedback>
1407      <defaultgrade>1</defaultgrade>
1408      <penalty>1</penalty>
1409      <hidden>0</hidden>
1410      <idnumber></idnumber>
1411      <answer fraction="100" format="plain_text">
1412        <text>true</text>
1413        <feedback format="html">
1414          <text>Well done!</text>
1415        </feedback>
1416      </answer>
1417      <answer fraction="0" format="plain_text">
1418        <text>false</text>
1419        <feedback format="html">
1420          <text>Doh!</text>
1421        </feedback>
1422      </answer>
1423    </question>
1424  ';
1425  
1426          $this->assert_same_xml($expectedxml, $xml);
1427      }
1428  
1429      public function test_export_truefalse_with_idnumber() {
1430          $qdata = new stdClass();
1431          $qdata->id = 12;
1432          $qdata->contextid = \context_system::instance()->id;
1433          $qdata->qtype = 'truefalse';
1434          $qdata->name = 'True false question';
1435          $qdata->questiontext = 'The answer is true.';
1436          $qdata->questiontextformat = FORMAT_HTML;
1437          $qdata->generalfeedback = 'General feedback: You should have chosen true.';
1438          $qdata->generalfeedbackformat = FORMAT_HTML;
1439          $qdata->defaultmark = 1;
1440          $qdata->length = 1;
1441          $qdata->penalty = 1;
1442          $qdata->hidden = 0;
1443          $qdata->idnumber = 'TestIDNum2';
1444  
1445          $qdata->options = new stdClass();
1446          $qdata->options->answers = array(
1447                  1 => new question_answer(1, 'True', 1, 'Well done!', FORMAT_HTML),
1448                  2 => new question_answer(2, 'False', 0, 'Doh!', FORMAT_HTML),
1449          );
1450          $qdata->options->trueanswer = 1;
1451          $qdata->options->falseanswer = 2;
1452  
1453          $exporter = new qformat_xml();
1454          $xml = $exporter->writequestion($qdata);
1455  
1456          $expectedxml = '<!-- question: 12  -->
1457    <question type="truefalse">
1458      <name>
1459        <text>True false question</text>
1460      </name>
1461      <questiontext format="html">
1462        <text>The answer is true.</text>
1463      </questiontext>
1464      <generalfeedback format="html">
1465        <text>General feedback: You should have chosen true.</text>
1466      </generalfeedback>
1467      <defaultgrade>1</defaultgrade>
1468      <penalty>1</penalty>
1469      <hidden>0</hidden>
1470      <idnumber>TestIDNum2</idnumber>
1471      <answer fraction="100" format="plain_text">
1472        <text>true</text>
1473        <feedback format="html">
1474          <text>Well done!</text>
1475        </feedback>
1476      </answer>
1477      <answer fraction="0" format="plain_text">
1478        <text>false</text>
1479        <feedback format="html">
1480          <text>Doh!</text>
1481        </feedback>
1482      </answer>
1483    </question>
1484  ';
1485  
1486          $this->assert_same_xml($expectedxml, $xml);
1487      }
1488  
1489      public function test_import_multianswer() {
1490          $xml = '  <question type="cloze">
1491      <name>
1492        <text>Simple multianswer</text>
1493      </name>
1494      <questiontext format="html">
1495        <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>
1496      </questiontext>
1497      <generalfeedback format="html">
1498        <text><![CDATA[General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea".]]></text>
1499      </generalfeedback>
1500      <penalty>0.5</penalty>
1501      <hidden>0</hidden>
1502      <hint format="html">
1503        <text>Hint 1</text>
1504      </hint>
1505      <hint format="html">
1506        <text>Hint 2</text>
1507      </hint>
1508      <tags>
1509        <tag><text>tagCloze</text></tag>
1510        <tag><text>tagTest</text></tag>
1511      </tags>
1512    </question>
1513  ';
1514          $xmldata = xmlize($xml);
1515  
1516          $importer = new qformat_xml();
1517          $q = $importer->import_multianswer($xmldata['question']);
1518  
1519          // Annoyingly, import works in a weird way (it duplicates code, rather
1520          // than just calling save_question) so we cannot use
1521          // test_question_maker::get_question_form_data('multianswer', 'twosubq').
1522          $expectedqa = new stdClass();
1523          $expectedqa->name = 'Simple multianswer';
1524          $expectedqa->qtype = 'multianswer';
1525          $expectedqa->questiontext = 'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
1526          $expectedqa->generalfeedback =
1527                  'General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea".';
1528          $expectedqa->defaultmark = 2;
1529          $expectedqa->penalty = 0.5;
1530  
1531          $expectedqa->hint = array(
1532              array('text' => 'Hint 1', 'format' => FORMAT_HTML),
1533              array('text' => 'Hint 2', 'format' => FORMAT_HTML),
1534          );
1535  
1536          $sa = new stdClass();
1537  
1538          $sa->questiontext = array('text' => '{1:SHORTANSWER:Dog#Wrong, silly!~=Owl#Well done!~*#Wrong answer}',
1539                  'format' => FORMAT_HTML, 'itemid' => null);
1540          $sa->generalfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1541          $sa->defaultmark = 1.0;
1542          $sa->qtype = 'shortanswer';
1543          $sa->usecase = 0;
1544  
1545          $sa->answer = array('Dog', 'Owl', '*');
1546          $sa->fraction = array(0, 1, 0);
1547          $sa->feedback = array(
1548              array('text' => 'Wrong, silly!', 'format' => FORMAT_HTML, 'itemid' => null),
1549              array('text' => 'Well done!',    'format' => FORMAT_HTML, 'itemid' => null),
1550              array('text' => 'Wrong answer',  'format' => FORMAT_HTML, 'itemid' => null),
1551          );
1552  
1553          $mc = new stdClass();
1554  
1555          $mc->generalfeedback = '';
1556          $mc->questiontext = array('text' => '{1:MULTICHOICE:Bow-wow#You seem to have a dog obsessions!~' .
1557                  'Wiggly worm#Now you are just being ridiculous!~=Pussy-cat#Well done!}',
1558                  'format' => FORMAT_HTML, 'itemid' => null);
1559          $mc->generalfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1560          $mc->defaultmark = 1.0;
1561          $mc->qtype = 'multichoice';
1562  
1563          $mc->layout = 0;
1564          $mc->single = 1;
1565          $mc->shuffleanswers = 0;
1566          $mc->correctfeedback =          array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1567          $mc->partiallycorrectfeedback = array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1568          $mc->incorrectfeedback =        array('text' => '', 'format' => FORMAT_HTML, 'itemid' => null);
1569          $mc->answernumbering = 0;
1570  
1571          $mc->answer = array(
1572              array('text' => 'Bow-wow',     'format' => FORMAT_HTML, 'itemid' => null),
1573              array('text' => 'Wiggly worm', 'format' => FORMAT_HTML, 'itemid' => null),
1574              array('text' => 'Pussy-cat',   'format' => FORMAT_HTML, 'itemid' => null),
1575          );
1576          $mc->fraction = array(0, 0, 1);
1577          $mc->feedback = array(
1578              array('text' => 'You seem to have a dog obsessions!', 'format' => FORMAT_HTML, 'itemid' => null),
1579              array('text' => 'Now you are just being ridiculous!', 'format' => FORMAT_HTML, 'itemid' => null),
1580              array('text' => 'Well done!',                         'format' => FORMAT_HTML, 'itemid' => null),
1581          );
1582  
1583          $expectedqa->options = new stdClass();
1584          $expectedqa->options->questions = array(
1585              1 => $sa,
1586              2 => $mc,
1587          );
1588          $expectedqa->tags = array('tagCloze', 'tagTest');
1589  
1590          $this->assertEquals($expectedqa->hint, $q->hint);
1591          $this->assertEquals($expectedqa->options->questions[1], $q->options->questions[1]);
1592          $this->assertEquals($expectedqa->options->questions[2], $q->options->questions[2]);
1593          $this->assert(new question_check_specified_fields_expectation($expectedqa), $q);
1594      }
1595  
1596      public function test_export_multianswer() {
1597          $qdata = test_question_maker::get_question_data('multianswer', 'twosubq');
1598          $qdata->contextid = \context_system::instance()->id;
1599          $exporter = new qformat_xml();
1600          $xml = $exporter->writequestion($qdata);
1601  
1602          $expectedxml = '<!-- question: 0  -->
1603    <question type="cloze">
1604      <name>
1605        <text>Simple multianswer</text>
1606      </name>
1607      <questiontext format="html">
1608        <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>
1609      </questiontext>
1610      <generalfeedback format="html">
1611        <text><![CDATA[General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: "The owl and the pussycat went to sea]]></text>
1612      </generalfeedback>
1613      <penalty>0.3333333</penalty>
1614      <hidden>0</hidden>
1615      <idnumber></idnumber>
1616      <hint format="html">
1617        <text>Hint 1</text>
1618      </hint>
1619      <hint format="html">
1620        <text>Hint 2</text>
1621      </hint>
1622    </question>
1623  ';
1624  
1625          $this->assert_same_xml($expectedxml, $xml);
1626      }
1627  
1628      public function test_export_multianswer_withdollars() {
1629          $qdata = test_question_maker::get_question_data('multianswer', 'dollarsigns');
1630          $qdata->contextid = \context_system::instance()->id;
1631          $exporter = new qformat_xml();
1632          $xml = $exporter->writequestion($qdata);
1633  
1634          $expectedxml = '<!-- question: 0  -->
1635    <question type="cloze">
1636      <name>
1637        <text>Multianswer with $s</text>
1638      </name>
1639      <questiontext format="html">
1640        <text>Which is the right order? {1:MULTICHOICE:=y,y,$3~$3,y,y}</text>
1641      </questiontext>
1642      <generalfeedback format="html">
1643        <text></text>
1644      </generalfeedback>
1645      <penalty>0.3333333</penalty>
1646      <hidden>0</hidden>
1647      <idnumber></idnumber>
1648    </question>
1649  ';
1650  
1651          $this->assert_same_xml($expectedxml, $xml);
1652      }
1653  
1654      public function test_import_files_as_draft() {
1655          $this->resetAfterTest();
1656          $this->setAdminUser();
1657  
1658          $xml = <<<END
1659  <questiontext format="html">
1660      <text><![CDATA[<p><a href="@@PLUGINFILE@@/moodle.txt">This text file</a> contains the word 'Moodle'.</p>]]></text>
1661      <file name="moodle.txt" encoding="base64">TW9vZGxl</file>
1662  </questiontext>
1663  END;
1664  
1665          $textxml = xmlize($xml);
1666          $qo = new stdClass();
1667  
1668          $importer = new qformat_xml();
1669          $draftitemid = $importer->import_files_as_draft($textxml['questiontext']['#']['file']);
1670          $files = file_get_drafarea_files($draftitemid);
1671  
1672          $this->assertEquals(1, count($files->list));
1673  
1674          $file = $files->list[0];
1675          $this->assertEquals('moodle.txt', $file->filename);
1676          $this->assertEquals('/',          $file->filepath);
1677          $this->assertEquals(6,            $file->size);
1678      }
1679  
1680      public function test_import_truefalse_wih_files() {
1681          $this->resetAfterTest();
1682          $this->setAdminUser();
1683  
1684          $xml = '<question type="truefalse">
1685      <name>
1686        <text>truefalse</text>
1687      </name>
1688      <questiontext format="html">
1689        <text><![CDATA[<p><a href="@@PLUGINFILE@@/myfolder/moodle.txt">This text file</a> contains the word Moodle.</p>]]></text>
1690  <file name="moodle.txt" path="/myfolder/" encoding="base64">TW9vZGxl</file>
1691      </questiontext>
1692      <generalfeedback format="html">
1693        <text><![CDATA[<p>For further information, see the documentation about Moodle.</p>]]></text>
1694  </generalfeedback>
1695      <defaultgrade>1.0000000</defaultgrade>
1696      <penalty>1.0000000</penalty>
1697      <hidden>0</hidden>
1698      <answer fraction="100" format="moodle_auto_format">
1699        <text>true</text>
1700        <feedback format="html">
1701          <text></text>
1702        </feedback>
1703      </answer>
1704      <answer fraction="0" format="moodle_auto_format">
1705        <text>false</text>
1706        <feedback format="html">
1707          <text></text>
1708        </feedback>
1709      </answer>
1710    </question>';
1711          $xmldata = xmlize($xml);
1712  
1713          $importer = new qformat_xml();
1714          $q = $importer->import_truefalse($xmldata['question']);
1715  
1716          $draftitemid = $q->questiontextitemid;
1717          $files = file_get_drafarea_files($draftitemid, '/myfolder/');
1718  
1719          $this->assertEquals(1, count($files->list));
1720  
1721          $file = $files->list[0];
1722          $this->assertEquals('moodle.txt', $file->filename);
1723          $this->assertEquals('/myfolder/', $file->filepath);
1724          $this->assertEquals(6,            $file->size);
1725      }
1726  
1727      public function test_create_dummy_question() {
1728  
1729          $testobject = new mock_qformat_xml();
1730          $categoryname = 'name1';
1731          $categoryinfo = new stdClass();
1732          $categoryinfo->info = 'info1';
1733          $categoryinfo->infoformat = 'infoformat1';
1734          $categoryinfo->idnumber = null;
1735          $dummyquestion = $testobject->mock_create_dummy_question_representing_category($categoryname, $categoryinfo);
1736  
1737          $this->assertEquals('category', $dummyquestion->qtype);
1738          $this->assertEquals($categoryname, $dummyquestion->category);
1739          $this->assertEquals($categoryinfo->info, $dummyquestion->info);
1740          $this->assertEquals($categoryinfo->infoformat, $dummyquestion->infoformat);
1741          $this->assertEquals('Switch category to ' . $categoryname, $dummyquestion->name);
1742          $this->assertEquals(0, $dummyquestion->id);
1743          $this->assertEquals('', $dummyquestion->questiontextformat);
1744          $this->assertEquals(0, $dummyquestion->contextid);
1745      }
1746  }
1747  
1748  /**
1749   * Class mock_qformat_xml exists only to enable testing of the create dummy question category.
1750   * @package    qformat_xml
1751   * @copyright  2018 The Open University
1752   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1753   */
1754  class mock_qformat_xml extends qformat_xml {
1755      /**
1756       * Make public an otherwise protected function.
1757       * @param string $categoryname the name of the category
1758       * @param object $categoryinfo description of the category
1759       */
1760      public function mock_create_dummy_question_representing_category(string $categoryname, $categoryinfo) {
1761          return $this->create_dummy_question_representing_category($categoryname, $categoryinfo);
1762      }
1763  }