Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 39 and 311]

   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   * Test xml_writer tests.
  19   *
  20   * @package   core_backup
  21   * @category  test
  22   * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core_backup;
  27  
  28  use memory_xml_output;
  29  use xml_contenttransformer;
  30  use xml_output;
  31  use xml_writer;
  32  use xml_writer_exception;
  33  
  34  defined('MOODLE_INTERNAL') || die();
  35  
  36  // Include all the needed stuff
  37  global $CFG;
  38  require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
  39  require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
  40  require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php');
  41  require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
  42  
  43  /**
  44   * Test xml_writer tests.
  45   *
  46   * @package   core_backup
  47   * @category  test
  48   * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  49   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  50   */
  51  class writer_test extends \basic_testcase {
  52  
  53      /**
  54       * test xml_writer public methods
  55       */
  56      function test_xml_writer_public_api() {
  57          global $CFG;
  58          // Instantiate xml_output
  59          $xo = new memory_xml_output();
  60          $this->assertTrue($xo instanceof xml_output);
  61  
  62          // Instantiate xml_writer with null xml_output
  63          try {
  64              $xw = new mock_xml_writer(null);
  65              $this->assertTrue(false, 'xml_writer_exception expected');
  66          } catch (\Exception $e) {
  67              $this->assertTrue($e instanceof xml_writer_exception);
  68              $this->assertEquals($e->errorcode, 'invalid_xml_output');
  69          }
  70  
  71          // Instantiate xml_writer with wrong xml_output object
  72          try {
  73              $xw = new mock_xml_writer(new \stdClass());
  74              $this->assertTrue(false, 'xml_writer_exception expected');
  75          } catch (\Exception $e) {
  76              $this->assertTrue($e instanceof xml_writer_exception);
  77              $this->assertEquals($e->errorcode, 'invalid_xml_output');
  78          }
  79  
  80          // Instantiate xml_writer with wrong xml_contenttransformer object
  81          try {
  82              $xw = new mock_xml_writer($xo, new \stdClass());
  83              $this->assertTrue(false, 'xml_writer_exception expected');
  84          } catch (\Exception $e) {
  85              $this->assertTrue($e instanceof xml_writer_exception);
  86              $this->assertEquals($e->errorcode, 'invalid_xml_contenttransformer');
  87          }
  88  
  89          // Instantiate xml_writer and start it twice
  90          $xw = new mock_xml_writer($xo);
  91          $xw->start();
  92          try {
  93              $xw->start();
  94              $this->assertTrue(false, 'xml_writer_exception expected');
  95          } catch (\Exception $e) {
  96              $this->assertTrue($e instanceof xml_writer_exception);
  97              $this->assertEquals($e->errorcode, 'xml_writer_already_started');
  98          }
  99  
 100          // Instantiate xml_writer and stop it twice
 101          $xo = new memory_xml_output();
 102          $xw = new mock_xml_writer($xo);
 103          $xw->start();
 104          $xw->stop();
 105          try {
 106              $xw->stop();
 107              $this->assertTrue(false, 'xml_writer_exception expected');
 108          } catch (\Exception $e) {
 109              $this->assertTrue($e instanceof xml_writer_exception);
 110              $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
 111          }
 112  
 113          // Stop writer without starting it
 114          $xo = new memory_xml_output();
 115          $xw = new mock_xml_writer($xo);
 116          try {
 117              $xw->stop();
 118              $this->assertTrue(false, 'xml_writer_exception expected');
 119          } catch (\Exception $e) {
 120              $this->assertTrue($e instanceof xml_writer_exception);
 121              $this->assertEquals($e->errorcode, 'xml_writer_not_started');
 122          }
 123  
 124          // Start writer after stopping it
 125          $xo = new memory_xml_output();
 126          $xw = new mock_xml_writer($xo);
 127          $xw->start();
 128          $xw->stop();
 129          try {
 130              $xw->start();
 131              $this->assertTrue(false, 'xml_writer_exception expected');
 132          } catch (\Exception $e) {
 133              $this->assertTrue($e instanceof xml_writer_exception);
 134              $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
 135          }
 136  
 137          // Try to set prologue/schema after start
 138          $xo = new memory_xml_output();
 139          $xw = new mock_xml_writer($xo);
 140          $xw->start();
 141          try {
 142              $xw->set_nonamespace_schema('http://moodle.org');
 143              $this->assertTrue(false, 'xml_writer_exception expected');
 144          } catch (\Exception $e) {
 145              $this->assertTrue($e instanceof xml_writer_exception);
 146              $this->assertEquals($e->errorcode, 'xml_writer_already_started');
 147          }
 148          try {
 149              $xw->set_prologue('sweet prologue');
 150              $this->assertTrue(false, 'xml_writer_exception expected');
 151          } catch (\Exception $e) {
 152              $this->assertTrue($e instanceof xml_writer_exception);
 153              $this->assertEquals($e->errorcode, 'xml_writer_already_started');
 154          }
 155  
 156          // Instantiate properly with memory_xml_output, start and stop.
 157          // Must get default UTF-8 prologue
 158          $xo = new memory_xml_output();
 159          $xw = new mock_xml_writer($xo);
 160          $xw->start();
 161          $xw->stop();
 162          $this->assertEquals($xo->get_allcontents(), $xw->get_default_prologue());
 163  
 164          // Instantiate, set prologue and schema, put 1 full tag and get results
 165          $xo = new memory_xml_output();
 166          $xw = new mock_xml_writer($xo);
 167          $xw->set_prologue('CLEARLY WRONG PROLOGUE');
 168          $xw->set_nonamespace_schema('http://moodle.org/littleschema');
 169          $xw->start();
 170          $xw->full_tag('TEST', 'Hello World!', array('id' => 1));
 171          $xw->stop();
 172          $result = $xo->get_allcontents();
 173          // Perform various checks
 174          $this->assertEquals(strpos($result, 'WRONG'), 8);
 175          $this->assertEquals(strpos($result, '<TEST id="1"'), 22);
 176          $this->assertEquals(strpos($result, 'xmlns:xsi='), 39);
 177          $this->assertEquals(strpos($result, 'http://moodle.org/littleschema'), 128);
 178          $this->assertEquals(strpos($result, 'Hello World'), 160);
 179          $this->assertFalse(strpos($result, $xw->get_default_prologue()));
 180  
 181          // Try to close one tag in wrong order
 182          $xo = new memory_xml_output();
 183          $xw = new mock_xml_writer($xo);
 184          $xw->start();
 185          $xw->begin_tag('first');
 186          $xw->begin_tag('second');
 187          try {
 188              $xw->end_tag('first');
 189              $this->assertTrue(false, 'xml_writer_exception expected');
 190          } catch (\Exception $e) {
 191              $this->assertTrue($e instanceof xml_writer_exception);
 192              $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
 193          }
 194  
 195          // Try to close one tag before starting any tag
 196          $xo = new memory_xml_output();
 197          $xw = new mock_xml_writer($xo);
 198          $xw->start();
 199          try {
 200              $xw->end_tag('first');
 201              $this->assertTrue(false, 'xml_writer_exception expected');
 202          } catch (\Exception $e) {
 203              $this->assertTrue($e instanceof xml_writer_exception);
 204              $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
 205          }
 206  
 207          // Full tag without contents (null and empty string)
 208          $xo = new memory_xml_output();
 209          $xw = new mock_xml_writer($xo);
 210          $xw->set_prologue(''); // empty prologue for easier matching
 211          $xw->start();
 212          $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
 213          $xw->full_tag('tagname2', '', array('attrname' => 'attrvalue'));
 214          $xw->stop();
 215          $result = $xo->get_allcontents();
 216          $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue"></tagname2>');
 217  
 218  
 219          // Test case-folding is working
 220          $xo = new memory_xml_output();
 221          $xw = new mock_xml_writer($xo, null, true);
 222          $xw->set_prologue(''); // empty prologue for easier matching
 223          $xw->start();
 224          $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attrvalue'));
 225          $xw->stop();
 226          $result = $xo->get_allcontents();
 227          $this->assertEquals($result, '<TAGNAME ATTRNAME="attrvalue">textcontent</TAGNAME>');
 228  
 229          // Test UTF-8 chars in tag and attribute names, attr values and contents
 230          $xo = new memory_xml_output();
 231          $xw = new mock_xml_writer($xo);
 232          $xw->set_prologue(''); // empty prologue for easier matching
 233          $xw->start();
 234          $xw->full_tag('áéíóú', 'ÁÉÍÓÚ', array('àèìòù' => 'ÀÈÌÒÙ'));
 235          $xw->stop();
 236          $result = $xo->get_allcontents();
 237          $this->assertEquals($result, '<áéíóú àèìòù="ÀÈÌÒÙ">ÁÉÍÓÚ</áéíóú>');
 238  
 239          // Try non-safe content in attributes
 240          $xo = new memory_xml_output();
 241          $xw = new mock_xml_writer($xo);
 242          $xw->set_prologue(''); // empty prologue for easier matching
 243          $xw->start();
 244          $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attr' . chr(27) . '\'"value'));
 245          $xw->stop();
 246          $result = $xo->get_allcontents();
 247          $this->assertEquals($result, '<tagname attrname="attr\'&quot;value">textcontent</tagname>');
 248  
 249          // Try non-safe content in text
 250          $xo = new memory_xml_output();
 251          $xw = new mock_xml_writer($xo);
 252          $xw->set_prologue(''); // empty prologue for easier matching
 253          $xw->start();
 254          $xw->full_tag('tagname', "text\r\ncontent\rwith" . chr(27), array('attrname' => 'attrvalue'));
 255          $xw->stop();
 256          $result = $xo->get_allcontents();
 257          $this->assertEquals($result, '<tagname attrname="attrvalue">text' . "\ncontent\n" . 'with</tagname>');
 258  
 259          // Try to stop the writer without clossing all the open tags
 260          $xo = new memory_xml_output();
 261          $xw = new mock_xml_writer($xo);
 262          $xw->start();
 263          $xw->begin_tag('first');
 264          try {
 265              $xw->stop();
 266              $this->assertTrue(false, 'xml_writer_exception expected');
 267          } catch (\Exception $e) {
 268              $this->assertTrue($e instanceof xml_writer_exception);
 269              $this->assertEquals($e->errorcode, 'xml_writer_open_tags_remaining');
 270          }
 271  
 272          // Test simple transformer
 273          $xo = new memory_xml_output();
 274          $xt = new mock_xml_contenttransformer();
 275          $xw = new mock_xml_writer($xo, $xt);
 276          $xw->set_prologue(''); // empty prologue for easier matching
 277          $xw->start();
 278          $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
 279          $xw->full_tag('tagname2', 'somecontent', array('attrname' => 'attrvalue'));
 280          $xw->stop();
 281          $result = $xo->get_allcontents();
 282          $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue">testsomecontent</tagname2>');
 283  
 284          // Build a complex XML file and test results against stored file in fixtures
 285          $xo = new memory_xml_output();
 286          $xw = new mock_xml_writer($xo);
 287          $xw->start();
 288          $xw->begin_tag('toptag', array('name' => 'toptag', 'level' => 1, 'path' => '/toptag'));
 289          $xw->full_tag('secondtag', 'secondvalue', array('name' => 'secondtag', 'level' => 2, 'path' => '/toptag/secondtag', 'value' => 'secondvalue'));
 290          $xw->begin_tag('thirdtag', array('name' => 'thirdtag', 'level' => 2, 'path' => '/toptag/thirdtag'));
 291          $xw->full_tag('onevalue', 'onevalue', array('name' => 'onevalue', 'level' => 3, 'path' => '/toptag/thirdtag/onevalue'));
 292          $xw->full_tag('onevalue', 'anothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'anothervalue'));
 293          $xw->full_tag('onevalue', 'yetanothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'yetanothervalue'));
 294          $xw->full_tag('twovalue', 'twovalue', array('name' => 'twovalue', 'level' => 3, 'path' => '/toptag/thirdtag/twovalue'));
 295          $xw->begin_tag('forthtag', array('name' => 'forthtag', 'level' => 3, 'path' => '/toptag/thirdtag/forthtag'));
 296          $xw->full_tag('innervalue', 'innervalue');
 297          $xw->begin_tag('innertag');
 298          $xw->begin_tag('superinnertag', array('name' => 'superinnertag', 'level' => 5));
 299          $xw->full_tag('superinnervalue', 'superinnervalue', array('name' => 'superinnervalue', 'level' => 6));
 300          $xw->end_tag('superinnertag');
 301          $xw->end_tag('innertag');
 302          $xw->end_tag('forthtag');
 303          $xw->begin_tag('fifthtag', array('level' => 3));
 304          $xw->begin_tag('sixthtag', array('level' => 4));
 305          $xw->full_tag('seventh', 'seventh', array('level' => 5));
 306          $xw->end_tag('sixthtag');
 307          $xw->end_tag('fifthtag');
 308          $xw->full_tag('finalvalue', 'finalvalue', array('name' => 'finalvalue', 'level' => 3, 'path' => '/toptag/thirdtag/finalvalue'));
 309          $xw->full_tag('finalvalue');
 310          $xw->end_tag('thirdtag');
 311          $xw->end_tag('toptag');
 312          $xw->stop();
 313          $result = $xo->get_allcontents();
 314          $fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml');
 315  
 316          // Normalise carriage return characters.
 317          $fcontents = str_replace("\r\n", "\n", $fcontents);
 318          $this->assertEquals(trim($result), trim($fcontents));
 319      }
 320  }
 321  
 322  /*
 323   * helper extended xml_writer class that makes some methods public for testing
 324   */
 325  class mock_xml_writer extends xml_writer {
 326      public function get_default_prologue() {
 327          return parent::get_default_prologue();
 328      }
 329  }
 330  
 331  /*
 332   * helper extended xml_contenttransformer prepending "test" to all the notnull contents
 333   */
 334  class mock_xml_contenttransformer extends xml_contenttransformer {
 335      public function process($content) {
 336          return is_null($content) ? null : 'test' . $content;
 337      }
 338  }