Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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