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 311 and 402] [Versions 311 and 403] [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   * Setting tests (all).
  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 activity_backup_setting;
  29  use backup_setting;
  30  use backup_setting_exception;
  31  use base_setting;
  32  use base_setting_exception;
  33  use course_backup_setting;
  34  use section_backup_setting;
  35  use setting_dependency;
  36  use setting_dependency_disabledif_empty;
  37  
  38  defined('MOODLE_INTERNAL') || die();
  39  
  40  // Include all the needed stuff
  41  global $CFG;
  42  require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
  43  require_once($CFG->dirroot . '/backup/backup.class.php');
  44  require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
  45  require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
  46  require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
  47  require_once($CFG->dirroot . '/backup/util/settings/root/root_backup_setting.class.php');
  48  require_once($CFG->dirroot . '/backup/util/settings/activity/activity_backup_setting.class.php');
  49  require_once($CFG->dirroot . '/backup/util/settings/section/section_backup_setting.class.php');
  50  require_once($CFG->dirroot . '/backup/util/settings/course/course_backup_setting.class.php');
  51  require_once($CFG->dirroot . '/backup/util/ui/backup_ui_setting.class.php');
  52  
  53  /**
  54   * Setting tests (all).
  55   *
  56   * @package   core_backup
  57   * @category  test
  58   * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  59   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  60   */
  61  class settings_test extends \basic_testcase {
  62  
  63      /**
  64       * test base_setting class
  65       */
  66      public function test_base_setting() {
  67          // Instantiate base_setting and check everything
  68          $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
  69          $this->assertTrue($bs instanceof base_setting);
  70          $this->assertEquals($bs->get_name(), 'test');
  71          $this->assertEquals($bs->get_vtype(), base_setting::IS_BOOLEAN);
  72          $this->assertTrue(is_null($bs->get_value()));
  73          $this->assertEquals($bs->get_visibility(), base_setting::VISIBLE);
  74          $this->assertEquals($bs->get_status(), base_setting::NOT_LOCKED);
  75  
  76          // Instantiate base_setting with explicit nulls
  77          $bs = new mock_base_setting('test', base_setting::IS_FILENAME, 'filename.txt', null, null);
  78          $this->assertEquals($bs->get_value() , 'filename.txt');
  79          $this->assertEquals($bs->get_visibility(), base_setting::VISIBLE);
  80          $this->assertEquals($bs->get_status(), base_setting::NOT_LOCKED);
  81  
  82          // Instantiate base_setting and set value, visibility and status
  83          $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
  84          $bs->set_value(true);
  85          $this->assertNotEmpty($bs->get_value());
  86          $bs->set_visibility(base_setting::HIDDEN);
  87          $this->assertEquals($bs->get_visibility(), base_setting::HIDDEN);
  88          $bs->set_status(base_setting::LOCKED_BY_HIERARCHY);
  89          $this->assertEquals($bs->get_status(), base_setting::LOCKED_BY_HIERARCHY);
  90  
  91          // Instantiate with wrong vtype
  92          try {
  93              $bs = new mock_base_setting('test', 'one_wrong_type');
  94              $this->assertTrue(false, 'base_setting_exception expected');
  95          } catch (\Exception $e) {
  96              $this->assertTrue($e instanceof base_setting_exception);
  97              $this->assertEquals($e->errorcode, 'setting_invalid_type');
  98          }
  99  
 100          // Instantiate with wrong integer value
 101          try {
 102              $bs = new mock_base_setting('test', base_setting::IS_INTEGER, 99.99);
 103              $this->assertTrue(false, 'base_setting_exception expected');
 104          } catch (\Exception $e) {
 105              $this->assertTrue($e instanceof base_setting_exception);
 106              $this->assertEquals($e->errorcode, 'setting_invalid_integer');
 107          }
 108  
 109          // Instantiate with wrong filename value
 110          try {
 111              $bs = new mock_base_setting('test', base_setting::IS_FILENAME, '../../filename.txt');
 112              $this->assertTrue(false, 'base_setting_exception expected');
 113          } catch (\Exception $e) {
 114              $this->assertTrue($e instanceof base_setting_exception);
 115              $this->assertEquals($e->errorcode, 'setting_invalid_filename');
 116          }
 117  
 118          // Instantiate with wrong visibility
 119          try {
 120              $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, 'one_wrong_visibility');
 121              $this->assertTrue(false, 'base_setting_exception expected');
 122          } catch (\Exception $e) {
 123              $this->assertTrue($e instanceof base_setting_exception);
 124              $this->assertEquals($e->errorcode, 'setting_invalid_visibility');
 125          }
 126  
 127          // Instantiate with wrong status
 128          try {
 129              $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, 'one_wrong_status');
 130              $this->assertTrue(false, 'base_setting_exception expected');
 131          } catch (\Exception $e) {
 132              $this->assertTrue($e instanceof base_setting_exception);
 133              $this->assertEquals($e->errorcode, 'setting_invalid_status');
 134          }
 135  
 136          // Instantiate base_setting and try to set wrong ui_type
 137          // We need a custom error handler to catch the type hinting error
 138          // that should return incorrect_object_passed
 139          $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
 140          set_error_handler('\core_backup\backup_setting_error_handler', E_RECOVERABLE_ERROR);
 141          try {
 142              $bs->set_ui('one_wrong_ui_type', 'label', array(), array());
 143              $this->assertTrue(false, 'base_setting_exception expected');
 144          } catch (\Exception $e) {
 145              $this->assertTrue($e instanceof base_setting_exception);
 146              $this->assertEquals($e->errorcode, 'incorrect_object_passed');
 147          } catch (\TypeError $e) {
 148              // On PHP7+ we get a TypeError raised, lets check we've the right error.
 149              $this->assertMatchesRegularExpression('/must be (of type|an instance of) backup_setting_ui/', $e->getMessage());
 150          }
 151          restore_error_handler();
 152  
 153          // Instantiate base_setting and try to set wrong ui_label
 154          // We need a custom error handler to catch the type hinting error
 155          // that should return incorrect_object_passed
 156          $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
 157          set_error_handler('\core_backup\backup_setting_error_handler', E_RECOVERABLE_ERROR);
 158          try {
 159              $bs->set_ui(base_setting::UI_HTML_CHECKBOX, 'one/wrong/label', array(), array());
 160              $this->assertTrue(false, 'base_setting_exception expected');
 161          } catch (\Exception $e) {
 162              $this->assertTrue($e instanceof base_setting_exception);
 163              $this->assertEquals($e->errorcode, 'incorrect_object_passed');
 164          } catch (\TypeError $e) {
 165              // On PHP7+ we get a TypeError raised, lets check we've the right error.
 166              $this->assertMatchesRegularExpression('/must be (of type|an instance of) backup_setting_ui/', $e->getMessage());
 167          }
 168          restore_error_handler();
 169  
 170          // Try to change value of locked setting by permission
 171          $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, base_setting::LOCKED_BY_PERMISSION);
 172          try {
 173              $bs->set_value(true);
 174              $this->assertTrue(false, 'base_setting_exception expected');
 175          } catch (\Exception $e) {
 176              $this->assertTrue($e instanceof base_setting_exception);
 177              $this->assertEquals($e->errorcode, 'setting_locked_by_permission');
 178          }
 179  
 180          // Try to change value of locked setting by config
 181          $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, base_setting::LOCKED_BY_CONFIG);
 182          try {
 183              $bs->set_value(true);
 184              $this->assertTrue(false, 'base_setting_exception expected');
 185          } catch (\Exception $e) {
 186              $this->assertTrue($e instanceof base_setting_exception);
 187              $this->assertEquals($e->errorcode, 'setting_locked_by_config');
 188          }
 189  
 190          // Try to add same setting twice
 191          $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
 192          $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
 193          $bs1->add_dependency($bs2, null, array('value'=>0));
 194          try {
 195              $bs1->add_dependency($bs2);
 196              $this->assertTrue(false, 'base_setting_exception expected');
 197          } catch (\Exception $e) {
 198              $this->assertTrue($e instanceof base_setting_exception);
 199              $this->assertEquals($e->errorcode, 'setting_already_added');
 200          }
 201  
 202          // Try to create one circular reference
 203          $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
 204          try {
 205              $bs1->add_dependency($bs1); // self
 206              $this->assertTrue(false, 'base_setting_exception expected');
 207          } catch (\Exception $e) {
 208              $this->assertTrue($e instanceof base_setting_exception);
 209              $this->assertEquals($e->errorcode, 'setting_circular_reference');
 210              $this->assertTrue($e->a instanceof \stdClass);
 211              $this->assertEquals($e->a->main, 'test1');
 212              $this->assertEquals($e->a->alreadydependent, 'test1');
 213          }
 214  
 215          $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
 216          $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
 217          $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
 218          $bs4 = new mock_base_setting('test4', base_setting::IS_INTEGER, null);
 219          $bs1->add_dependency($bs2, null, array('value'=>0));
 220          $bs2->add_dependency($bs3, null, array('value'=>0));
 221          $bs3->add_dependency($bs4, null, array('value'=>0));
 222          try {
 223              $bs4->add_dependency($bs1, null, array('value'=>0));
 224              $this->assertTrue(false, 'base_setting_exception expected');
 225          } catch (\Exception $e) {
 226              $this->assertTrue($e instanceof base_setting_exception);
 227              $this->assertEquals($e->errorcode, 'setting_circular_reference');
 228              $this->assertTrue($e->a instanceof \stdClass);
 229              $this->assertEquals($e->a->main, 'test1');
 230              $this->assertEquals($e->a->alreadydependent, 'test4');
 231          }
 232  
 233          $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
 234          $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
 235          $bs1->register_dependency(new setting_dependency_disabledif_empty($bs1, $bs2));
 236          try {
 237              // $bs1 is already dependent on $bs2 so this should fail.
 238              $bs2->register_dependency(new setting_dependency_disabledif_empty($bs2, $bs1));
 239              $this->assertTrue(false, 'base_setting_exception expected');
 240          } catch (\Exception $e) {
 241              $this->assertTrue($e instanceof base_setting_exception);
 242              $this->assertEquals($e->errorcode, 'setting_circular_reference');
 243              $this->assertTrue($e->a instanceof \stdClass);
 244              $this->assertEquals($e->a->main, 'test1');
 245              $this->assertEquals($e->a->alreadydependent, 'test2');
 246          }
 247  
 248          // Create 3 settings and observe between them, last one must
 249          // automatically inherit all the settings defined in the main one
 250          $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
 251          $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
 252          $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
 253          $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
 254          $bs2->add_dependency($bs3, setting_dependency::DISABLED_NOT_EMPTY);
 255          // Check values are spreaded ok
 256          $bs1->set_value(123);
 257          $this->assertEquals($bs1->get_value(), 123);
 258          $this->assertEquals($bs2->get_value(), $bs1->get_value());
 259          $this->assertEquals($bs3->get_value(), $bs1->get_value());
 260  
 261          // Add one more setting and set value again
 262          $bs4 = new mock_base_setting('test4', base_setting::IS_INTEGER, null);
 263          $bs2->add_dependency($bs4, setting_dependency::DISABLED_NOT_EMPTY);
 264          $bs2->set_value(321);
 265          // The above change should change
 266          $this->assertEquals($bs1->get_value(), 123);
 267          $this->assertEquals($bs2->get_value(), 321);
 268          $this->assertEquals($bs3->get_value(), 321);
 269          $this->assertEquals($bs4->get_value(), 321);
 270  
 271          // Check visibility is spreaded ok
 272          $bs1->set_visibility(base_setting::HIDDEN);
 273          $this->assertEquals($bs2->get_visibility(), $bs1->get_visibility());
 274          $this->assertEquals($bs3->get_visibility(), $bs1->get_visibility());
 275          // Check status is spreaded ok
 276          $bs1->set_status(base_setting::LOCKED_BY_HIERARCHY);
 277          $this->assertEquals($bs2->get_status(), $bs1->get_status());
 278          $this->assertEquals($bs3->get_status(), $bs1->get_status());
 279  
 280          // Create 3 settings and observe between them, put them in one array,
 281          // force serialize/deserialize to check the observable pattern continues
 282          // working after that
 283          $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
 284          $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
 285          $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
 286          $bs1->add_dependency($bs2, null, array('value'=>0));
 287          $bs2->add_dependency($bs3, null, array('value'=>0));
 288          // Serialize
 289          $arr = array($bs1, $bs2, $bs3);
 290          $ser = base64_encode(serialize($arr));
 291          // Unserialize and copy to new objects
 292          $newarr = unserialize(base64_decode($ser));
 293          $ubs1 = $newarr[0];
 294          $ubs2 = $newarr[1];
 295          $ubs3 = $newarr[2];
 296          // Must continue being base settings
 297          $this->assertTrue($ubs1 instanceof base_setting);
 298          $this->assertTrue($ubs2 instanceof base_setting);
 299          $this->assertTrue($ubs3 instanceof base_setting);
 300          // Set parent setting
 301          $ubs1->set_value(1234);
 302          $ubs1->set_visibility(base_setting::HIDDEN);
 303          $ubs1->set_status(base_setting::LOCKED_BY_HIERARCHY);
 304          // Check changes have been spreaded
 305          $this->assertEquals($ubs2->get_visibility(), $ubs1->get_visibility());
 306          $this->assertEquals($ubs3->get_visibility(), $ubs1->get_visibility());
 307          $this->assertEquals($ubs2->get_status(), $ubs1->get_status());
 308          $this->assertEquals($ubs3->get_status(), $ubs1->get_status());
 309      }
 310  
 311      /**
 312       * Test that locked and unlocked states on dependent backup settings at the same level
 313       * correctly do not flow from the parent to the child setting when the setting is locked by permissions.
 314       */
 315      public function test_dependency_empty_locked_by_permission_child_is_not_unlocked() {
 316          // Check dependencies are working ok.
 317          $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 2);
 318          $bs1->set_level(1);
 319          $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 2);
 320          $bs2->set_level(1); // Same level *must* work.
 321          $bs1->add_dependency($bs2, setting_dependency::DISABLED_EMPTY);
 322  
 323          $bs1->set_status(base_setting::LOCKED_BY_PERMISSION);
 324          $this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
 325          $this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
 326          $bs2->set_status(base_setting::LOCKED_BY_PERMISSION);
 327          $this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
 328  
 329          // Unlocking the parent should NOT unlock the child.
 330          $bs1->set_status(base_setting::NOT_LOCKED);
 331  
 332          $this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs2->get_status());
 333      }
 334  
 335      /**
 336       * Test that locked and unlocked states on dependent backup settings at the same level
 337       * correctly do flow from the parent to the child setting when the setting is locked by config.
 338       */
 339      public function test_dependency_not_empty_locked_by_config_parent_is_unlocked() {
 340          $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 0);
 341          $bs1->set_level(1);
 342          $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 0);
 343          $bs2->set_level(1); // Same level *must* work.
 344          $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
 345  
 346          $bs1->set_status(base_setting::LOCKED_BY_CONFIG);
 347          $this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
 348          $this->assertEquals(base_setting::LOCKED_BY_CONFIG, $bs1->get_status());
 349  
 350          // Unlocking the parent should unlock the child.
 351          $bs1->set_status(base_setting::NOT_LOCKED);
 352          $this->assertEquals(base_setting::NOT_LOCKED, $bs2->get_status());
 353      }
 354  
 355      /**
 356       * test backup_setting class
 357       */
 358      public function test_backup_setting() {
 359          // Instantiate backup_setting class and set level
 360          $bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
 361          $bs->set_level(1);
 362          $this->assertEquals($bs->get_level(), 1);
 363  
 364          // Instantiate backup setting class and try to add one non backup_setting dependency
 365          set_error_handler('\core_backup\backup_setting_error_handler', E_RECOVERABLE_ERROR);
 366          $bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
 367          try {
 368              $bs->add_dependency(new \stdClass());
 369              $this->assertTrue(false, 'backup_setting_exception expected');
 370          } catch (\Exception $e) {
 371              $this->assertTrue($e instanceof backup_setting_exception);
 372              $this->assertEquals($e->errorcode, 'incorrect_object_passed');
 373          } catch (\TypeError $e) {
 374              // On PHP7+ we get a TypeError raised, lets check we've the right error.
 375              $this->assertMatchesRegularExpression('/must be (an instance of|of type) base_setting/', $e->getMessage());
 376          }
 377          restore_error_handler();
 378  
 379          // Try to assing upper level dependency
 380          $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, null);
 381          $bs1->set_level(1);
 382          $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, null);
 383          $bs2->set_level(2);
 384          try {
 385              $bs2->add_dependency($bs1);
 386              $this->assertTrue(false, 'backup_setting_exception expected');
 387          } catch (\Exception $e) {
 388              $this->assertTrue($e instanceof backup_setting_exception);
 389              $this->assertEquals($e->errorcode, 'cannot_add_upper_level_dependency');
 390          }
 391  
 392          // Check dependencies are working ok
 393          $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, null);
 394          $bs1->set_level(1);
 395          $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, null);
 396          $bs2->set_level(1); // Same level *must* work
 397          $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
 398          $bs1->set_value(123456);
 399          $this->assertEquals($bs2->get_value(), $bs1->get_value());
 400      }
 401  
 402      /**
 403       * test activity_backup_setting class
 404       */
 405      public function test_activity_backup_setting() {
 406          $bs = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
 407          $this->assertEquals($bs->get_level(), backup_setting::ACTIVITY_LEVEL);
 408  
 409          // Check checksum implementation is working
 410          $bs1 = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
 411          $bs1->set_value(123);
 412          $checksum = $bs1->calculate_checksum();
 413          $this->assertNotEmpty($checksum);
 414          $this->assertTrue($bs1->is_checksum_correct($checksum));
 415      }
 416  
 417      /**
 418       * test section_backup_setting class
 419       */
 420      public function test_section_backup_setting() {
 421          $bs = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
 422          $this->assertEquals($bs->get_level(), backup_setting::SECTION_LEVEL);
 423  
 424          // Check checksum implementation is working
 425          $bs1 = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
 426          $bs1->set_value(123);
 427          $checksum = $bs1->calculate_checksum();
 428          $this->assertNotEmpty($checksum);
 429          $this->assertTrue($bs1->is_checksum_correct($checksum));
 430      }
 431  
 432      /**
 433       * test course_backup_setting class
 434       */
 435      public function test_course_backup_setting() {
 436          $bs = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
 437          $this->assertEquals($bs->get_level(), backup_setting::COURSE_LEVEL);
 438  
 439          // Check checksum implementation is working
 440          $bs1 = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
 441          $bs1->set_value(123);
 442          $checksum = $bs1->calculate_checksum();
 443          $this->assertNotEmpty($checksum);
 444          $this->assertTrue($bs1->is_checksum_correct($checksum));
 445      }
 446  }
 447  
 448  /**
 449   * helper extended base_setting class that makes some methods public for testing
 450   */
 451  class mock_base_setting extends base_setting {
 452      public function get_vtype() {
 453          return $this->vtype;
 454      }
 455  
 456      public function process_change($setting, $ctype, $oldv) {
 457          // Simply, inherit from the main object
 458          $this->set_value($setting->get_value());
 459          $this->set_visibility($setting->get_visibility());
 460          $this->set_status($setting->get_status());
 461      }
 462  
 463      public function get_ui_info() {
 464          // Return an array with all the ui info to be tested
 465          return array($this->ui_type, $this->ui_label, $this->ui_values, $this->ui_options);
 466      }
 467  }
 468  
 469  /**
 470   * helper extended backup_setting class that makes some methods public for testing
 471   */
 472  class mock_backup_setting extends backup_setting {
 473      public function set_level($level) {
 474          $this->level = $level;
 475      }
 476  
 477      public function process_change($setting, $ctype, $oldv) {
 478          // Simply, inherit from the main object
 479          $this->set_value($setting->get_value());
 480          $this->set_visibility($setting->get_visibility());
 481          $this->set_status($setting->get_status());
 482      }
 483  }
 484  
 485  /**
 486   * helper extended activity_backup_setting class that makes some methods public for testing
 487   */
 488  class mock_activity_backup_setting extends activity_backup_setting {
 489      public function process_change($setting, $ctype, $oldv) {
 490          // Do nothing
 491      }
 492  }
 493  
 494  /**
 495   * helper extended section_backup_setting class that makes some methods public for testing
 496   */
 497  class mock_section_backup_setting extends section_backup_setting {
 498      public function process_change($setting, $ctype, $oldv) {
 499          // Do nothing
 500      }
 501  }
 502  
 503  /**
 504   * helper extended course_backup_setting class that makes some methods public for testing
 505   */
 506  class mock_course_backup_setting extends course_backup_setting {
 507      public function process_change($setting, $ctype, $oldv) {
 508          // Do nothing
 509      }
 510  }
 511  
 512  /**
 513   * This error handler is used to convert errors to excpetions so that simepltest can
 514   * catch them.
 515   *
 516   * This is required in order to catch type hint mismatches that result in a error
 517   * being thrown. It should only ever be used to catch E_RECOVERABLE_ERROR's.
 518   *
 519   * It throws a backup_setting_exception with 'incorrect_object_passed'
 520   *
 521   * @param int $errno E_RECOVERABLE_ERROR
 522   * @param string $errstr
 523   * @param string $errfile
 524   * @param int $errline
 525   * @return null
 526   */
 527  function backup_setting_error_handler($errno, $errstr, $errfile, $errline) {
 528      if ($errno !== E_RECOVERABLE_ERROR) {
 529          // Currently we only want to deal with type hinting errors
 530          return false;
 531      }
 532      throw new backup_setting_exception('incorrect_object_passed');
 533  }