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  namespace core_backup;
  18  
  19  use backup;
  20  use backup_attribute;
  21  use backup_controller_dbops;
  22  use backup_final_element;
  23  use backup_nested_element;
  24  use backup_optigroup;
  25  use backup_optigroup_element;
  26  use backup_processor_exception;
  27  use backup_structure_processor;
  28  use base_element_struct_exception;
  29  use base_optigroup_exception;
  30  use base_processor;
  31  use memory_xml_output;
  32  use xml_writer;
  33  
  34  defined('MOODLE_INTERNAL') || die();
  35  
  36  // Include all the needed stuff.
  37  require_once (__DIR__.'/fixtures/structure_fixtures.php');
  38  
  39  global $CFG;
  40  require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php');
  41  
  42  /**
  43   * Unit test case the all the backup structure classes. Note: Uses database
  44   *
  45   * @package   core_backup
  46   * @category  test
  47   * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  48   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   */
  50  class structure_test extends \advanced_testcase {
  51  
  52      /** @var int Store the inserted forum->id for use in test functions */
  53      protected $forumid;
  54      /** @var int Store the inserted discussion1->id for use in test functions */
  55      protected $discussionid1;
  56      /** @var int Store the inserted discussion2->id for use in test functions */
  57      protected $discussionid2;
  58      /** @var int Store the inserted post1->id for use in test functions */
  59      protected $postid1;
  60      /** @var int Store the inserted post2->id for use in test functions */
  61      protected $postid2;
  62      /** @var int Store the inserted post3->id for use in test functions */
  63      protected $postid3;
  64      /** @var int Store the inserted post4->id for use in test functions */
  65      protected $postid4;
  66      /** @var int Official contextid for these tests */
  67      protected $contextid;
  68  
  69  
  70      protected function setUp(): void {
  71          parent::setUp();
  72  
  73          $this->resetAfterTest(true);
  74  
  75          $this->contextid = 666; // Let's assume this is the context for the forum
  76          $this->fill_records(); // Add common stuff needed by various test methods
  77      }
  78  
  79      private function fill_records() {
  80          global $DB;
  81  
  82          // Create one forum
  83          $forum_data = (object)array('course' => 1, 'name' => 'Test forum', 'intro' => 'Intro forum');
  84          $this->forumid = $DB->insert_record('forum', $forum_data);
  85          // With two related file
  86          $f1_forum_data = (object)array(
  87              'contenthash' => 'testf1', 'contextid' => $this->contextid,
  88              'component'=>'mod_forum', 'filearea' => 'intro', 'filename' => 'tf1', 'itemid' => 0,
  89              'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0,
  90              'pathnamehash' => 'testf1'
  91          );
  92          $DB->insert_record('files', $f1_forum_data);
  93          $f2_forum_data = (object)array(
  94              'contenthash' => 'tesft2', 'contextid' => $this->contextid,
  95              'component'=>'mod_forum', 'filearea' => 'intro', 'filename' => 'tf2', 'itemid' => 0,
  96              'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0,
  97              'pathnamehash' => 'testf2'
  98          );
  99          $DB->insert_record('files', $f2_forum_data);
 100  
 101          // Create two discussions
 102          $discussion1 = (object)array('course' => 1, 'forum' => $this->forumid, 'name' => 'd1', 'userid' => 100, 'groupid' => 200);
 103          $this->discussionid1 = $DB->insert_record('forum_discussions', $discussion1);
 104          $discussion2 = (object)array('course' => 1, 'forum' => $this->forumid, 'name' => 'd2', 'userid' => 101, 'groupid' => 201);
 105          $this->discussionid2 = $DB->insert_record('forum_discussions', $discussion2);
 106  
 107          // Create four posts
 108          $post1 = (object)array('discussion' => $this->discussionid1, 'userid' => 100, 'subject' => 'p1', 'message' => 'm1');
 109          $this->postid1 = $DB->insert_record('forum_posts', $post1);
 110          $post2 = (object)array('discussion' => $this->discussionid1, 'parent' => $this->postid1, 'userid' => 102, 'subject' => 'p2', 'message' => 'm2');
 111          $this->postid2 = $DB->insert_record('forum_posts', $post2);
 112          $post3 = (object)array('discussion' => $this->discussionid1, 'parent' => $this->postid2, 'userid' => 103, 'subject' => 'p3', 'message' => 'm3');
 113          $this->postid3 = $DB->insert_record('forum_posts', $post3);
 114          $post4 = (object)array('discussion' => $this->discussionid2, 'userid' => 101, 'subject' => 'p4', 'message' => 'm4');
 115          $this->postid4 = $DB->insert_record('forum_posts', $post4);
 116          // With two related file
 117          $f1_post1 = (object)array(
 118              'contenthash' => 'testp1', 'contextid' => $this->contextid, 'component'=>'mod_forum',
 119              'filearea' => 'post', 'filename' => 'tp1', 'itemid' => $this->postid1,
 120              'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0,
 121              'pathnamehash' => 'testp1'
 122          );
 123          $DB->insert_record('files', $f1_post1);
 124          $f1_post2 = (object)array(
 125              'contenthash' => 'testp2', 'contextid' => $this->contextid, 'component'=>'mod_forum',
 126              'filearea' => 'attachment', 'filename' => 'tp2', 'itemid' => $this->postid2,
 127              'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0,
 128              'pathnamehash' => 'testp2'
 129          );
 130          $DB->insert_record('files', $f1_post2);
 131  
 132          // Create two ratings
 133          $rating1 = (object)array(
 134              'contextid' => $this->contextid, 'userid' => 104, 'itemid' => $this->postid1, 'rating' => 2,
 135              'scaleid' => -1, 'timecreated' => time(), 'timemodified' => time());
 136          $r1id = $DB->insert_record('rating', $rating1);
 137          $rating2 = (object)array(
 138              'contextid' => $this->contextid, 'userid' => 105, 'itemid' => $this->postid1, 'rating' => 3,
 139              'scaleid' => -1, 'timecreated' => time(), 'timemodified' => time());
 140          $r2id = $DB->insert_record('rating', $rating2);
 141  
 142          // Create 1 reads
 143          $read1 = (object)array('userid' => 102, 'forumid' => $this->forumid, 'discussionid' => $this->discussionid2, 'postid' => $this->postid4);
 144          $DB->insert_record('forum_read', $read1);
 145      }
 146  
 147      /**
 148       * Backup structures tests (construction, definition and execution)
 149       */
 150      function test_backup_structure_construct() {
 151          global $DB;
 152  
 153          $backupid = 'Testing Backup ID'; // Official backupid for these tests
 154  
 155          // Create all the elements that will conform the tree
 156          $forum = new backup_nested_element('forum',
 157              array('id'),
 158              array(
 159                  'type', 'name', 'intro', 'introformat',
 160                  'assessed', 'assesstimestart', 'assesstimefinish', 'scale',
 161                  'maxbytes', 'maxattachments', 'forcesubscribe', 'trackingtype',
 162                  'rsstype', 'rssarticles', 'timemodified', 'warnafter',
 163                  'blockafter',
 164                  new backup_final_element('blockperiod'),
 165                  new \mock_skip_final_element('completiondiscussions'),
 166                  new \mock_modify_final_element('completionreplies'),
 167                  new \mock_final_element_interceptor('completionposts'))
 168          );
 169          $discussions = new backup_nested_element('discussions');
 170          $discussion = new backup_nested_element('discussion',
 171              array('id'),
 172              array(
 173                  'forum', 'name', 'firstpost', 'userid',
 174                  'groupid', 'assessed', 'timemodified', 'usermodified',
 175                  'timestart', 'timeend')
 176          );
 177          $posts = new backup_nested_element('posts');
 178          $post = new backup_nested_element('post',
 179              array('id'),
 180              array(
 181                  'discussion', 'parent', 'userid', 'created',
 182                  'modified', 'mailed', 'subject', 'message',
 183                  'messageformat', 'messagetrust', 'attachment', 'totalscore',
 184                  'mailnow')
 185          );
 186          $ratings = new backup_nested_element('ratings');
 187          $rating = new backup_nested_element('rating',
 188              array('id'),
 189              array('userid', 'itemid', 'time', 'post_rating')
 190          );
 191          $reads = new backup_nested_element('readposts');
 192          $read = new backup_nested_element('read',
 193              array('id'),
 194              array(
 195                  'userid', 'discussionid', 'postid',
 196                  'firstread', 'lastread')
 197          );
 198          $inventeds = new backup_nested_element('invented_elements',
 199              array('reason', 'version')
 200          );
 201          $invented = new backup_nested_element('invented',
 202              null,
 203              array('one', 'two', 'three')
 204          );
 205          $one = $invented->get_final_element('one');
 206          $one->add_attributes(array('attr1', 'attr2'));
 207  
 208          // Build the tree
 209          $forum->add_child($discussions);
 210          $discussions->add_child($discussion);
 211  
 212          $discussion->add_child($posts);
 213          $posts->add_child($post);
 214  
 215          $post->add_child($ratings);
 216          $ratings->add_child($rating);
 217  
 218          $forum->add_child($reads);
 219          $reads->add_child($read);
 220  
 221          $forum->add_child($inventeds);
 222          $inventeds->add_child($invented);
 223  
 224          // Let's add 1 optigroup with 4 elements
 225          $alternative1 = new backup_optigroup_element('alternative1',
 226              array('name', 'value'), '../../id', $this->postid1);
 227          $alternative2 = new backup_optigroup_element('alternative2',
 228              array('name', 'value'), backup::VAR_PARENTID, $this->postid2);
 229          $alternative3 = new backup_optigroup_element('alternative3',
 230              array('name', 'value'), '/forum/discussions/discussion/posts/post/id', $this->postid3);
 231          $alternative4 = new backup_optigroup_element('alternative4',
 232              array('forumtype', 'forumname')); // Alternative without conditions
 233          // Create the optigroup, adding one element
 234          $optigroup = new backup_optigroup('alternatives', $alternative1, false);
 235          // Add second opti element
 236          $optigroup->add_child($alternative2);
 237  
 238          // Add optigroup to post element
 239          $post->add_optigroup($optigroup);
 240          // Add third opti element, on purpose after the add_optigroup() line above to check param evaluation works ok
 241          $optigroup->add_child($alternative3);
 242          // Add 4th opti element (the one without conditions, so will be present always)
 243          $optigroup->add_child($alternative4);
 244  
 245          /// Create some new nested elements, both named 'dupetest1', and add them to alternative1 and alternative2
 246          /// (not problem as far as the optigroup in not unique)
 247          $dupetest1 = new backup_nested_element('dupetest1', null, array('field1', 'field2'));
 248          $dupetest2 = new backup_nested_element('dupetest2', null, array('field1', 'field2'));
 249          $dupetest3 = new backup_nested_element('dupetest3', null, array('field1', 'field2'));
 250          $dupetest4 = new backup_nested_element('dupetest1', null, array('field1', 'field2'));
 251          $dupetest1->add_child($dupetest3);
 252          $dupetest2->add_child($dupetest4);
 253          $alternative1->add_child($dupetest1);
 254          $alternative2->add_child($dupetest2);
 255  
 256          // Define sources
 257          $forum->set_source_table('forum', array('id' => backup::VAR_ACTIVITYID));
 258          $discussion->set_source_sql('SELECT *
 259                                         FROM {forum_discussions}
 260                                        WHERE forum = ?',
 261              array('/forum/id')
 262          );
 263          $post->set_source_table('forum_posts', array('discussion' => '/forum/discussions/discussion/id'));
 264          $rating->set_source_sql('SELECT *
 265                                     FROM {rating}
 266                                    WHERE itemid = ?',
 267              array(backup::VAR_PARENTID)
 268          );
 269  
 270          $read->set_source_table('forum_read', array('forumid' => '../../id'));
 271  
 272          $inventeds->set_source_array(array((object)array('reason' => 'I love Moodle', 'version' => '1.0'),
 273              (object)array('reason' => 'I love Moodle', 'version' => '2.0'))); // 2 object array
 274          $invented->set_source_array(array((object)array('one' => 1, 'two' => 2, 'three' => 3),
 275              (object)array('one' => 11, 'two' => 22, 'three' => 33))); // 2 object array
 276  
 277          // Set optigroup_element sources
 278          $alternative1->set_source_array(array((object)array('name' => 'alternative1', 'value' => 1))); // 1 object array
 279          // Skip alternative2 source definition on purpose (will be tested)
 280          // $alternative2->set_source_array(array((object)array('name' => 'alternative2', 'value' => 2))); // 1 object array
 281          $alternative3->set_source_array(array((object)array('name' => 'alternative3', 'value' => 3))); // 1 object array
 282          // Alternative 4 source is the forum type and name, so we'll get that in ALL posts (no conditions) that
 283          // have not another alternative (post4 in our testing data in the only not matching any other alternative)
 284          $alternative4->set_source_sql('SELECT type AS forumtype, name AS forumname
 285                                           FROM {forum}
 286                                          WHERE id = ?',
 287              array('/forum/id')
 288          );
 289          // Set children of optigroup_element source
 290          $dupetest1->set_source_array(array((object)array('field1' => '1', 'field2' => 1))); // 1 object array
 291          $dupetest2->set_source_array(array((object)array('field1' => '2', 'field2' => 2))); // 1 object array
 292          $dupetest3->set_source_array(array((object)array('field1' => '3', 'field2' => 3))); // 1 object array
 293          $dupetest4->set_source_array(array((object)array('field1' => '4', 'field2' => 4))); // 1 object array
 294  
 295          // Define some aliases
 296          $rating->set_source_alias('rating', 'post_rating'); // Map the 'rating' value from DB to 'post_rating' final element
 297  
 298          // Mark to detect files of type 'forum_intro' in forum (and not item id)
 299          $forum->annotate_files('mod_forum', 'intro', null);
 300  
 301          // Mark to detect file of type 'forum_post' and 'forum_attachment' in post (with itemid being post->id)
 302          $post->annotate_files('mod_forum', 'post', 'id');
 303          $post->annotate_files('mod_forum', 'attachment', 'id');
 304  
 305          // Mark various elements to be annotated
 306          $discussion->annotate_ids('user1', 'userid');
 307          $post->annotate_ids('forum_post', 'id');
 308          $rating->annotate_ids('user2', 'userid');
 309          $rating->annotate_ids('forum_post', 'itemid');
 310  
 311          // Create the backup_ids_temp table
 312          backup_controller_dbops::create_backup_ids_temp_table($backupid);
 313  
 314          // Instantiate in memory xml output
 315          $xo = new memory_xml_output();
 316  
 317          // Instantiate xml_writer and start it
 318          $xw = new xml_writer($xo);
 319          $xw->start();
 320  
 321          // Instantiate the backup processor
 322          $processor = new backup_structure_processor($xw);
 323  
 324          // Set some variables
 325          $processor->set_var(backup::VAR_ACTIVITYID, $this->forumid);
 326          $processor->set_var(backup::VAR_BACKUPID, $backupid);
 327          $processor->set_var(backup::VAR_CONTEXTID,$this->contextid);
 328  
 329          // Process the backup structure with the backup processor
 330          $forum->process($processor);
 331  
 332          // Stop the xml_writer
 333          $xw->stop();
 334  
 335          // Check various counters
 336          $this->assertEquals($forum->get_counter(), $DB->count_records('forum'));
 337          $this->assertEquals($discussion->get_counter(), $DB->count_records('forum_discussions'));
 338          $this->assertEquals($rating->get_counter(), $DB->count_records('rating'));
 339          $this->assertEquals($read->get_counter(), $DB->count_records('forum_read'));
 340          $this->assertEquals($inventeds->get_counter(), 2); // Array
 341  
 342          // Perform some validations with the generated XML
 343          $dom = new \DomDocument();
 344          $dom->loadXML($xo->get_allcontents());
 345          $xpath = new \DOMXPath($dom);
 346          // Some more counters
 347          $query = '/forum/discussions/discussion/posts/post';
 348          $posts = $xpath->query($query);
 349          $this->assertEquals($posts->length, $DB->count_records('forum_posts'));
 350          $query = '/forum/invented_elements/invented';
 351          $inventeds = $xpath->query($query);
 352          $this->assertEquals($inventeds->length, 2*2);
 353  
 354          // Check ratings information against DB
 355          $ratings = $dom->getElementsByTagName('rating');
 356          $this->assertEquals($ratings->length, $DB->count_records('rating'));
 357          foreach ($ratings as $rating) {
 358              $ratarr = array();
 359              $ratarr['id'] = $rating->getAttribute('id');
 360              foreach ($rating->childNodes as $node) {
 361                  if ($node->nodeType != XML_TEXT_NODE) {
 362                      $ratarr[$node->nodeName] = $node->nodeValue;
 363                  }
 364              }
 365              $this->assertEquals($DB->get_field('rating', 'userid', array('id' => $ratarr['id'])), $ratarr['userid']);
 366              $this->assertEquals($DB->get_field('rating', 'itemid', array('id' => $ratarr['id'])), $ratarr['itemid']);
 367              $this->assertEquals($DB->get_field('rating', 'rating', array('id' => $ratarr['id'])), $ratarr['post_rating']);
 368          }
 369  
 370          // Check forum has "blockeperiod" with value 0 (was declared by object instead of name)
 371          $query = '/forum[blockperiod="0"]';
 372          $result = $xpath->query($query);
 373          $this->assertEquals(1, $result->length);
 374  
 375          // Check forum is missing "completiondiscussions" (as we are using mock_skip_final_element)
 376          $query = '/forum/completiondiscussions';
 377          $result = $xpath->query($query);
 378          $this->assertEquals(0, $result->length);
 379  
 380          // Check forum has "completionreplies" with value "original was 0, now changed" (because of mock_modify_final_element)
 381          $query = '/forum[completionreplies="original was 0, now changed"]';
 382          $result = $xpath->query($query);
 383          $this->assertEquals(1, $result->length);
 384  
 385          // Check forum has "completionposts" with value "intercepted!" (because of mock_final_element_interceptor)
 386          $query = '/forum[completionposts="intercepted!"]';
 387          $result = $xpath->query($query);
 388          $this->assertEquals(1, $result->length);
 389  
 390          // Check there isn't any alternative2 tag, as far as it hasn't source defined
 391          $query = '//alternative2';
 392          $result = $xpath->query($query);
 393          $this->assertEquals(0, $result->length);
 394  
 395          // Check there are 4 "field1" elements
 396          $query = '/forum/discussions/discussion/posts/post//field1';
 397          $result = $xpath->query($query);
 398          $this->assertEquals(4, $result->length);
 399  
 400          // Check first post has one name element with value "alternative1"
 401          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid1.'"][name="alternative1"]';
 402          $result = $xpath->query($query);
 403          $this->assertEquals(1, $result->length);
 404  
 405          // Check there are two "dupetest1" elements
 406          $query = '/forum/discussions/discussion/posts/post//dupetest1';
 407          $result = $xpath->query($query);
 408          $this->assertEquals(2, $result->length);
 409  
 410          // Check second post has one name element with value "dupetest2"
 411          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid2.'"]/dupetest2';
 412          $result = $xpath->query($query);
 413          $this->assertEquals(1, $result->length);
 414  
 415          // Check element "dupetest2" of second post has one field1 element with value "2"
 416          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid2.'"]/dupetest2[field1="2"]';
 417          $result = $xpath->query($query);
 418          $this->assertEquals(1, $result->length);
 419  
 420          // Check forth post has no name element
 421          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid4.'"]/name';
 422          $result = $xpath->query($query);
 423          $this->assertEquals(0, $result->length);
 424  
 425          // Check 1st, 2nd and 3rd posts have no forumtype element
 426          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid1.'"]/forumtype';
 427          $result = $xpath->query($query);
 428          $this->assertEquals(0, $result->length);
 429          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid2.'"]/forumtype';
 430          $result = $xpath->query($query);
 431          $this->assertEquals(0, $result->length);
 432          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid3.'"]/forumtype';
 433          $result = $xpath->query($query);
 434          $this->assertEquals(0, $result->length);
 435  
 436          // Check 4th post has one forumtype element with value "general"
 437          // (because it doesn't matches alternatives 1, 2, 3, then alternative 4,
 438          // the one without conditions is being applied)
 439          $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid4.'"][forumtype="general"]';
 440          $result = $xpath->query($query);
 441          $this->assertEquals(1, $result->length);
 442  
 443          // Check annotations information against DB
 444          // Count records in original tables
 445          $c_postsid    = $DB->count_records_sql('SELECT COUNT(DISTINCT id) FROM {forum_posts}');
 446          $c_dissuserid = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {forum_discussions}');
 447          $c_ratuserid  = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {rating}');
 448          // Count records in backup_ids_table
 449          $f_forumpost = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'forum_post'));
 450          $f_user1     = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'user1'));
 451          $f_user2     = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'user2'));
 452          $c_notbackupid = $DB->count_records_select('backup_ids_temp', 'backupid != ?', array($backupid));
 453          // Peform tests by comparing counts
 454          $this->assertEquals($c_notbackupid, 0); // there isn't any record with incorrect backupid
 455          $this->assertEquals($c_postsid, $f_forumpost); // All posts have been registered
 456          $this->assertEquals($c_dissuserid, $f_user1); // All users coming from discussions have been registered
 457          $this->assertEquals($c_ratuserid, $f_user2); // All users coming from ratings have been registered
 458  
 459          // Check file annotations against DB
 460          $fannotations = $DB->get_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'file'));
 461          $ffiles       = $DB->get_records('files', array('contextid' => $this->contextid));
 462          $this->assertEquals(count($fannotations), count($ffiles)); // Same number of recs in both (all files have been annotated)
 463          foreach ($fannotations as $annotation) { // Check ids annotated
 464              $this->assertTrue($DB->record_exists('files', array('id' => $annotation->itemid)));
 465          }
 466  
 467          // Drop the backup_ids_temp table
 468          backup_controller_dbops::drop_backup_ids_temp_table('testingid');
 469      }
 470  
 471      /**
 472       * Backup structures wrong tests (trying to do things the wrong way)
 473       */
 474      function test_backup_structure_wrong() {
 475  
 476          // Instantiate the backup processor
 477          $processor = new backup_structure_processor(new xml_writer(new memory_xml_output()));
 478          $this->assertTrue($processor instanceof base_processor);
 479  
 480          // Set one var twice
 481          $processor->set_var('onenewvariable', 999);
 482          try {
 483              $processor->set_var('onenewvariable', 999);
 484              $this->assertTrue(false, 'backup_processor_exception expected');
 485          } catch (\Exception $e) {
 486              $this->assertTrue($e instanceof backup_processor_exception);
 487              $this->assertEquals($e->errorcode, 'processorvariablealreadyset');
 488              $this->assertEquals($e->a, 'onenewvariable');
 489          }
 490  
 491          // Get non-existing var
 492          try {
 493              $var = $processor->get_var('nonexistingvar');
 494              $this->assertTrue(false, 'backup_processor_exception expected');
 495          } catch (\Exception $e) {
 496              $this->assertTrue($e instanceof backup_processor_exception);
 497              $this->assertEquals($e->errorcode, 'processorvariablenotfound');
 498              $this->assertEquals($e->a, 'nonexistingvar');
 499          }
 500  
 501          // Create nested element and try ro get its parent id (doesn't exisit => exception)
 502          $ne = new backup_nested_element('test', 'one', 'two', 'three');
 503          try {
 504              $ne->set_source_table('forum', array('id' => backup::VAR_PARENTID));
 505              $ne->process($processor);
 506              $this->assertTrue(false, 'base_element_struct_exception expected');
 507          } catch (\Exception $e) {
 508              $this->assertTrue($e instanceof base_element_struct_exception);
 509              $this->assertEquals($e->errorcode, 'cannotfindparentidforelement');
 510          }
 511  
 512          // Try to process one nested/final/attribute elements without processor
 513          $ne = new backup_nested_element('test', 'one', 'two', 'three');
 514          try {
 515              $ne->process(new \stdClass());
 516              $this->assertTrue(false, 'base_element_struct_exception expected');
 517          } catch (\Exception $e) {
 518              $this->assertTrue($e instanceof base_element_struct_exception);
 519              $this->assertEquals($e->errorcode, 'incorrect_processor');
 520          }
 521          $fe = new backup_final_element('test');
 522          try {
 523              $fe->process(new \stdClass());
 524              $this->assertTrue(false, 'base_element_struct_exception expected');
 525          } catch (\Exception $e) {
 526              $this->assertTrue($e instanceof base_element_struct_exception);
 527              $this->assertEquals($e->errorcode, 'incorrect_processor');
 528          }
 529          $at = new backup_attribute('test');
 530          try {
 531              $at->process(new \stdClass());
 532              $this->assertTrue(false, 'base_element_struct_exception expected');
 533          } catch (\Exception $e) {
 534              $this->assertTrue($e instanceof base_element_struct_exception);
 535              $this->assertEquals($e->errorcode, 'incorrect_processor');
 536          }
 537  
 538          // Try to put an incorrect alias
 539          $ne = new backup_nested_element('test', 'one', 'two', 'three');
 540          try {
 541              $ne->set_source_alias('last', 'nonexisting');
 542              $this->assertTrue(false, 'base_element_struct_exception expected');
 543          } catch (\Exception $e) {
 544              $this->assertTrue($e instanceof base_element_struct_exception);
 545              $this->assertEquals($e->errorcode, 'incorrectaliasfinalnamenotfound');
 546              $this->assertEquals($e->a, 'nonexisting');
 547          }
 548  
 549          // Try various incorrect paths specifying source
 550          $ne = new backup_nested_element('test', 'one', 'two', 'three');
 551          try {
 552              $ne->set_source_table('forum', array('/test/subtest'));
 553              $this->assertTrue(false, 'base_element_struct_exception expected');
 554          } catch (\Exception $e) {
 555              $this->assertTrue($e instanceof base_element_struct_exception);
 556              $this->assertEquals($e->errorcode, 'baseelementincorrectfinalorattribute');
 557              $this->assertEquals($e->a, 'subtest');
 558          }
 559          try {
 560              $ne->set_source_table('forum', array('/wrongtest'));
 561              $this->assertTrue(false, 'base_element_struct_exception expected');
 562          } catch (\Exception $e) {
 563              $this->assertTrue($e instanceof base_element_struct_exception);
 564              $this->assertEquals($e->errorcode, 'baseelementincorrectgrandparent');
 565              $this->assertEquals($e->a, 'wrongtest');
 566          }
 567          try {
 568              $ne->set_source_table('forum', array('../nonexisting'));
 569              $this->assertTrue(false, 'base_element_struct_exception expected');
 570          } catch (\Exception $e) {
 571              $this->assertTrue($e instanceof base_element_struct_exception);
 572              $this->assertEquals($e->errorcode, 'baseelementincorrectparent');
 573              $this->assertEquals($e->a, '..');
 574          }
 575  
 576          // Try various incorrect file annotations
 577  
 578          $ne = new backup_nested_element('test', 'one', 'two', 'three');
 579          $ne->annotate_files('test', 'filearea', null);
 580          try {
 581              $ne->annotate_files('test', 'filearea', null); // Try to add annotations twice
 582              $this->assertTrue(false, 'base_element_struct_exception expected');
 583          } catch (\Exception $e) {
 584              $this->assertTrue($e instanceof base_element_struct_exception);
 585              $this->assertEquals($e->errorcode, 'annotate_files_duplicate_annotation');
 586              $this->assertEquals($e->a, 'test/filearea/');
 587          }
 588  
 589          $ne = new backup_nested_element('test', 'one', 'two', 'three');
 590          try {
 591              $ne->annotate_files('test', 'filearea', 'four'); // Incorrect element
 592              $this->assertTrue(false, 'base_element_struct_exception expected');
 593          } catch (\Exception $e) {
 594              $this->assertTrue($e instanceof base_element_struct_exception);
 595              $this->assertEquals($e->errorcode, 'baseelementincorrectfinalorattribute');
 596              $this->assertEquals($e->a, 'four');
 597          }
 598  
 599          // Try to add incorrect element to backup_optigroup
 600          $bog = new backup_optigroup('test');
 601          try {
 602              $bog->add_child(new backup_nested_element('test2'));
 603              $this->assertTrue(false, 'base_optigroup_exception expected');
 604          } catch (\Exception $e) {
 605              $this->assertTrue($e instanceof base_optigroup_exception);
 606              $this->assertEquals($e->errorcode, 'optigroup_element_incorrect');
 607              $this->assertEquals($e->a, 'backup_nested_element');
 608          }
 609  
 610          $bog = new backup_optigroup('test');
 611          try {
 612              $bog->add_child('test2');
 613              $this->assertTrue(false, 'base_optigroup_exception expected');
 614          } catch (\Exception $e) {
 615              $this->assertTrue($e instanceof base_optigroup_exception);
 616              $this->assertEquals($e->errorcode, 'optigroup_element_incorrect');
 617              $this->assertEquals($e->a, 'non object');
 618          }
 619  
 620          try {
 621              $bog = new backup_optigroup('test', new \stdClass());
 622              $this->assertTrue(false, 'base_optigroup_exception expected');
 623          } catch (\Exception $e) {
 624              $this->assertTrue($e instanceof base_optigroup_exception);
 625              $this->assertEquals($e->errorcode, 'optigroup_elements_incorrect');
 626          }
 627  
 628          // Try a wrong processor with backup_optigroup
 629          $bog = new backup_optigroup('test');
 630          try {
 631              $bog->process(new \stdClass());
 632              $this->assertTrue(false, 'base_element_struct_exception expected');
 633          } catch (\Exception $e) {
 634              $this->assertTrue($e instanceof base_element_struct_exception);
 635              $this->assertEquals($e->errorcode, 'incorrect_processor');
 636          }
 637  
 638          // Try duplicating used elements with backup_optigroup
 639          // Adding top->down
 640          $bog = new backup_optigroup('test', null, true);
 641          $boge1 = new backup_optigroup_element('boge1');
 642          $boge2 = new backup_optigroup_element('boge2');
 643          $ne1 = new backup_nested_element('ne1');
 644          $ne2 = new backup_nested_element('ne1');
 645          $bog->add_child($boge1);
 646          $bog->add_child($boge2);
 647          $boge1->add_child($ne1);
 648          try {
 649              $boge2->add_child($ne2);
 650              $this->assertTrue(false, 'base_optigroup_exception expected');
 651          } catch (\Exception $e) {
 652              $this->assertTrue($e instanceof base_optigroup_exception);
 653              $this->assertEquals($e->errorcode, 'multiple_optigroup_duplicate_element');
 654              $this->assertEquals($e->a, 'ne1');
 655          }
 656          // Adding down->top
 657          $bog = new backup_optigroup('test', null, true);
 658          $boge1 = new backup_optigroup_element('boge1');
 659          $boge2 = new backup_optigroup_element('boge2');
 660          $ne1 = new backup_nested_element('ne1');
 661          $ne2 = new backup_nested_element('ne1');
 662          $boge1->add_child($ne1);
 663          $boge2->add_child($ne2);
 664          $bog->add_child($boge1);
 665          try {
 666              $bog->add_child($boge2);
 667              $this->assertTrue(false, 'base_element_struct_exception expected');
 668          } catch (\Exception $e) {
 669              $this->assertTrue($e instanceof base_element_struct_exception);
 670              $this->assertEquals($e->errorcode, 'baseelementexisting');
 671              $this->assertEquals($e->a, 'ne1');
 672          }
 673      }
 674  }