Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * @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  require_once (__DIR__.'/fixtures/plan_fixtures.php');
  27  
  28  
  29  /*
  30   * step tests (all)
  31   */
  32  class backup_step_testcase extends advanced_testcase {
  33  
  34      protected $moduleid;  // course_modules id used for testing
  35      protected $sectionid; // course_sections id used for testing
  36      protected $courseid;  // course id used for testing
  37      protected $userid;      // user record used for testing
  38  
  39      protected function setUp(): void {
  40          global $DB, $CFG;
  41          parent::setUp();
  42  
  43          $this->resetAfterTest(true);
  44  
  45          $course = $this->getDataGenerator()->create_course();
  46          $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
  47          $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
  48  
  49          $this->moduleid  = $coursemodule->id;
  50          $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
  51          $this->courseid  = $coursemodule->course;
  52          $this->userid = 2; // admin
  53  
  54          // Disable all loggers
  55          $CFG->backup_error_log_logger_level = backup::LOG_NONE;
  56          $CFG->backup_file_logger_level = backup::LOG_NONE;
  57          $CFG->backup_database_logger_level = backup::LOG_NONE;
  58          $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
  59      }
  60  
  61      /**
  62       * test base_step class
  63       */
  64      function test_base_step() {
  65  
  66          $bp = new mock_base_plan('planname'); // We need one plan
  67          $bt = new mock_base_task('taskname', $bp); // We need one task
  68          // Instantiate
  69          $bs = new mock_base_step('stepname', $bt);
  70          $this->assertTrue($bs instanceof base_step);
  71          $this->assertEquals($bs->get_name(), 'stepname');
  72      }
  73  
  74      /**
  75       * test backup_step class
  76       */
  77      function test_backup_step() {
  78  
  79          // We need one (non interactive) controller for instatiating plan
  80          $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
  81              backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
  82          // We need one plan
  83          $bp = new backup_plan($bc);
  84          // We need one task
  85          $bt = new mock_backup_task('taskname', $bp);
  86          // Instantiate step
  87          $bs = new mock_backup_step('stepname', $bt);
  88          $this->assertTrue($bs instanceof backup_step);
  89          $this->assertEquals($bs->get_name(), 'stepname');
  90  
  91          $bc->destroy();
  92      }
  93  
  94      /**
  95       * test restore_step class, decrypt method
  96       */
  97      public function test_restore_step_decrypt() {
  98  
  99          $this->resetAfterTest(true);
 100  
 101          if (!function_exists('openssl_encrypt')) {
 102              $this->markTestSkipped('OpenSSL extension is not loaded.');
 103  
 104          } else if (!function_exists('hash_hmac')) {
 105              $this->markTestSkipped('Hash extension is not loaded.');
 106  
 107          } else if (!in_array(backup::CIPHER, openssl_get_cipher_methods())) {
 108              $this->markTestSkipped('Expected cipher not available: ' . backup::CIPHER);
 109          }
 110  
 111          $bt = new mock_restore_task_basepath('taskname');
 112          $bs = new mock_restore_structure_step('steptest', null, $bt);
 113          $this->assertTrue(method_exists($bs, 'decrypt'));
 114  
 115          // Let's prepare a string for being decrypted.
 116          $secret = 'This is a secret message that nobody else will be able to read but me 💩  ';
 117          $key = hash('md5', 'Moodle rocks and this is not secure key, who cares, it is a test');
 118          $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(backup::CIPHER));
 119          $message = $iv . openssl_encrypt($secret, backup::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
 120          $hmac = hash_hmac('sha256', $message, $key, true);
 121          $crypt = base64_encode($hmac . $message);
 122  
 123          // Running it without a key configured, returns null.
 124          $this->assertNull($bs->decrypt($crypt));
 125  
 126          // Store the key into config.
 127          set_config('backup_encryptkey', base64_encode($key), 'backup');
 128  
 129          // Verify decrypt works and returns original.
 130          $this->assertSame($secret, $bs->decrypt($crypt));
 131  
 132          // Finally, test the integrity failure detection is working.
 133          // (this can be caused by changed hmac, key or message, in
 134          // this case we are just forcing it via changed hmac).
 135          $hmac = md5($message);
 136          $crypt = base64_encode($hmac . $message);
 137          $this->assertNull($bs->decrypt($crypt));
 138      }
 139  
 140      /**
 141       * test backup_structure_step class
 142       */
 143      function test_backup_structure_step() {
 144          global $CFG;
 145  
 146          $file = $CFG->tempdir . '/test/test_backup_structure_step.txt';
 147          // Remove the test dir and any content
 148          @remove_dir(dirname($file));
 149          // Recreate test dir
 150          if (!check_dir_exists(dirname($file), true, true)) {
 151              throw new moodle_exception('error_creating_temp_dir', 'error', dirname($file));
 152          }
 153  
 154          // We need one (non interactive) controller for instatiating plan
 155          $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
 156              backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
 157          // We need one plan
 158          $bp = new backup_plan($bc);
 159          // We need one task with mocked basepath
 160          $bt = new mock_backup_task_basepath('taskname');
 161          $bp->add_task($bt);
 162          // Instantiate backup_structure_step (and add it to task)
 163          $bs = new mock_backup_structure_step('steptest', basename($file), $bt);
 164          // Execute backup_structure_step
 165          $bs->execute();
 166  
 167          // Test file has been created
 168          $this->assertTrue(file_exists($file));
 169  
 170          // Some simple tests with contents
 171          $contents = file_get_contents($file);
 172          $this->assertTrue(strpos($contents, '<?xml version="1.0"') !== false);
 173          $this->assertTrue(strpos($contents, '<test id="1">') !== false);
 174          $this->assertTrue(strpos($contents, '<field1>value1</field1>') !== false);
 175          $this->assertTrue(strpos($contents, '<field2>value2</field2>') !== false);
 176          $this->assertTrue(strpos($contents, '</test>') !== false);
 177  
 178          $bc->destroy();
 179  
 180          unlink($file); // delete file
 181  
 182          // Remove the test dir and any content
 183          @remove_dir(dirname($file));
 184      }
 185  
 186  
 187      /**
 188       * Verify the add_plugin_structure() backup method behavior and created structures.
 189       */
 190      public function test_backup_structure_step_add_plugin_structure() {
 191          // Create mocked task, step and element.
 192          $bt = new mock_backup_task_basepath('taskname');
 193          $bs = new mock_backup_structure_step('steptest', null, $bt);
 194          $el = new backup_nested_element('question', array('id'), array('one', 'two', 'qtype'));
 195          // Wrong plugintype.
 196          try {
 197              $bs->add_plugin_structure('fakeplugin', $el, true);
 198              $this->assertTrue(false, 'base_step_exception expected');
 199          } catch (exception $e) {
 200              $this->assertTrue($e instanceof backup_step_exception);
 201              $this->assertEquals('incorrect_plugin_type', $e->errorcode);
 202          }
 203          // Correct plugintype qtype call (@ 'question' level).
 204          $bs->add_plugin_structure('qtype', $el, false);
 205          $ch = $el->get_children();
 206          $this->assertEquals(1, count($ch));
 207          $og = reset($ch);
 208          $this->assertTrue($og instanceof backup_optigroup);
 209          $ch = $og->get_children();
 210          $this->assertTrue(array_key_exists('optigroup_qtype_calculatedsimple_question', $ch));
 211          $this->assertTrue($ch['optigroup_qtype_calculatedsimple_question'] instanceof backup_plugin_element);
 212      }
 213  
 214      /**
 215       * Verify the add_subplugin_structure() backup method behavior and created structures.
 216       */
 217      public function test_backup_structure_step_add_subplugin_structure() {
 218          // Create mocked task, step and element.
 219          $bt = new mock_backup_task_basepath('taskname');
 220          $bs = new mock_backup_structure_step('steptest', null, $bt);
 221          $el = new backup_nested_element('workshop', array('id'), array('one', 'two', 'qtype'));
 222          // Wrong plugin type.
 223          try {
 224              $bs->add_subplugin_structure('fakesubplugin', $el, true, 'fakeplugintype', 'fakepluginname');
 225              $this->assertTrue(false, 'base_step_exception expected');
 226          } catch (exception $e) {
 227              $this->assertTrue($e instanceof backup_step_exception);
 228              $this->assertEquals('incorrect_plugin_type', $e->errorcode);
 229          }
 230          // Wrong plugin type.
 231          try {
 232              $bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'fakepluginname');
 233              $this->assertTrue(false, 'base_step_exception expected');
 234          } catch (exception $e) {
 235              $this->assertTrue($e instanceof backup_step_exception);
 236              $this->assertEquals('incorrect_plugin_name', $e->errorcode);
 237          }
 238          // Wrong plugin not having subplugins.
 239          try {
 240              $bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'page');
 241              $this->assertTrue(false, 'base_step_exception expected');
 242          } catch (exception $e) {
 243              $this->assertTrue($e instanceof backup_step_exception);
 244              $this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
 245          }
 246          // Wrong BC (defaulting to mod and modulename) use not having subplugins.
 247          try {
 248              $bt->set_modulename('page');
 249              $bs->add_subplugin_structure('fakesubplugin', $el, true);
 250              $this->assertTrue(false, 'base_step_exception expected');
 251          } catch (exception $e) {
 252              $this->assertTrue($e instanceof backup_step_exception);
 253              $this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
 254          }
 255          // Wrong subplugin type.
 256          try {
 257              $bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'workshop');
 258              $this->assertTrue(false, 'base_step_exception expected');
 259          } catch (exception $e) {
 260              $this->assertTrue($e instanceof backup_step_exception);
 261              $this->assertEquals('incorrect_subplugin_type', $e->errorcode);
 262          }
 263          // Wrong BC subplugin type.
 264          try {
 265              $bt->set_modulename('workshop');
 266              $bs->add_subplugin_structure('fakesubplugin', $el, true);
 267              $this->assertTrue(false, 'base_step_exception expected');
 268          } catch (exception $e) {
 269              $this->assertTrue($e instanceof backup_step_exception);
 270              $this->assertEquals('incorrect_subplugin_type', $e->errorcode);
 271          }
 272          // Correct call to workshopform subplugin (@ 'workshop' level).
 273          $bs->add_subplugin_structure('workshopform', $el, true, 'mod', 'workshop');
 274          $ch = $el->get_children();
 275          $this->assertEquals(1, count($ch));
 276          $og = reset($ch);
 277          $this->assertTrue($og instanceof backup_optigroup);
 278          $ch = $og->get_children();
 279          $this->assertTrue(array_key_exists('optigroup_workshopform_accumulative_workshop', $ch));
 280          $this->assertTrue($ch['optigroup_workshopform_accumulative_workshop'] instanceof backup_subplugin_element);
 281  
 282          // Correct BC call to workshopform subplugin (@ 'assessment' level).
 283          $el = new backup_nested_element('assessment', array('id'), array('one', 'two', 'qtype'));
 284          $bt->set_modulename('workshop');
 285          $bs->add_subplugin_structure('workshopform', $el, true);
 286          $ch = $el->get_children();
 287          $this->assertEquals(1, count($ch));
 288          $og = reset($ch);
 289          $this->assertTrue($og instanceof backup_optigroup);
 290          $ch = $og->get_children();
 291          $this->assertTrue(array_key_exists('optigroup_workshopform_accumulative_assessment', $ch));
 292          $this->assertTrue($ch['optigroup_workshopform_accumulative_assessment'] instanceof backup_subplugin_element);
 293  
 294          // TODO: Add some test covering a non-mod subplugin once we have some implemented in core.
 295      }
 296  
 297      /**
 298       * Verify the add_plugin_structure() restore method behavior and created structures.
 299       */
 300      public function test_restore_structure_step_add_plugin_structure() {
 301          // Create mocked task, step and element.
 302          $bt = new mock_restore_task_basepath('taskname');
 303          $bs = new mock_restore_structure_step('steptest', null, $bt);
 304          $el = new restore_path_element('question', '/some/path/to/question');
 305          // Wrong plugintype.
 306          try {
 307              $bs->add_plugin_structure('fakeplugin', $el);
 308              $this->assertTrue(false, 'base_step_exception expected');
 309          } catch (exception $e) {
 310              $this->assertTrue($e instanceof restore_step_exception);
 311              $this->assertEquals('incorrect_plugin_type', $e->errorcode);
 312          }
 313          // Correct plugintype qtype call (@ 'question' level).
 314          $bs->add_plugin_structure('qtype', $el);
 315          $patheles = $bs->get_pathelements();
 316          // Verify some well-known qtype plugin restore_path_elements have been added.
 317          $keys = array(
 318              '/some/path/to/question/plugin_qtype_calculated_question/answers/answer',
 319              '/some/path/to/question/plugin_qtype_calculated_question/dataset_definitions/dataset_definition',
 320              '/some/path/to/question/plugin_qtype_calculated_question/calculated_options/calculated_option',
 321              '/some/path/to/question/plugin_qtype_essay_question/essay',
 322              '/some/path/to/question/plugin_qtype_random_question',
 323              '/some/path/to/question/plugin_qtype_truefalse_question/answers/answer');
 324          foreach ($keys as $key) {
 325              // Verify the element exists.
 326              $this->assertArrayHasKey($key, $patheles);
 327              // Verify the element is a restore_path_element.
 328              $this->assertTrue($patheles[$key] instanceof restore_path_element);
 329              // Check it has a processing object.
 330              $po = $patheles[$key]->get_processing_object();
 331              $this->assertTrue($po instanceof restore_plugin);
 332          }
 333      }
 334  
 335      /**
 336       * Verify the add_subplugin_structure() restore method behavior and created structures.
 337       */
 338      public function test_restore_structure_step_add_subplugin_structure() {
 339          // Create mocked task, step and element.
 340          $bt = new mock_restore_task_basepath('taskname');
 341          $bs = new mock_restore_structure_step('steptest', null, $bt);
 342          $el = new restore_path_element('workshop', '/path/to/workshop');
 343          // Wrong plugin type.
 344          try {
 345              $bs->add_subplugin_structure('fakesubplugin', $el, 'fakeplugintype', 'fakepluginname');
 346              $this->assertTrue(false, 'base_step_exception expected');
 347          } catch (exception $e) {
 348              $this->assertTrue($e instanceof restore_step_exception);
 349              $this->assertEquals('incorrect_plugin_type', $e->errorcode);
 350          }
 351          // Wrong plugin type.
 352          try {
 353              $bs->add_subplugin_structure('fakesubplugin', $el, 'mod', 'fakepluginname');
 354              $this->assertTrue(false, 'base_step_exception expected');
 355          } catch (exception $e) {
 356              $this->assertTrue($e instanceof restore_step_exception);
 357              $this->assertEquals('incorrect_plugin_name', $e->errorcode);
 358          }
 359          // Wrong plugin not having subplugins.
 360          try {
 361              $bs->add_subplugin_structure('fakesubplugin', $el, 'mod', 'page');
 362              $this->assertTrue(false, 'base_step_exception expected');
 363          } catch (exception $e) {
 364              $this->assertTrue($e instanceof restore_step_exception);
 365              $this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
 366          }
 367          // Wrong BC (defaulting to mod and modulename) use not having subplugins.
 368          try {
 369              $bt->set_modulename('page');
 370              $bs->add_subplugin_structure('fakesubplugin', $el);
 371              $this->assertTrue(false, 'base_step_exception expected');
 372          } catch (exception $e) {
 373              $this->assertTrue($e instanceof restore_step_exception);
 374              $this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
 375          }
 376          // Wrong subplugin type.
 377          try {
 378              $bs->add_subplugin_structure('fakesubplugin', $el, 'mod', 'workshop');
 379              $this->assertTrue(false, 'base_step_exception expected');
 380          } catch (exception $e) {
 381              $this->assertTrue($e instanceof restore_step_exception);
 382              $this->assertEquals('incorrect_subplugin_type', $e->errorcode);
 383          }
 384          // Wrong BC subplugin type.
 385          try {
 386              $bt->set_modulename('workshop');
 387              $bs->add_subplugin_structure('fakesubplugin', $el);
 388              $this->assertTrue(false, 'base_step_exception expected');
 389          } catch (exception $e) {
 390              $this->assertTrue($e instanceof restore_step_exception);
 391              $this->assertEquals('incorrect_subplugin_type', $e->errorcode);
 392          }
 393          // Correct call to workshopform subplugin (@ 'workshop' level).
 394          $bt = new mock_restore_task_basepath('taskname');
 395          $bs = new mock_restore_structure_step('steptest', null, $bt);
 396          $el = new restore_path_element('workshop', '/path/to/workshop');
 397          $bs->add_subplugin_structure('workshopform', $el, 'mod', 'workshop');
 398          $patheles = $bs->get_pathelements();
 399          // Verify some well-known workshopform subplugin restore_path_elements have been added.
 400          $keys = array(
 401              '/path/to/workshop/subplugin_workshopform_accumulative_workshop/workshopform_accumulative_dimension',
 402              '/path/to/workshop/subplugin_workshopform_comments_workshop/workshopform_comments_dimension',
 403              '/path/to/workshop/subplugin_workshopform_numerrors_workshop/workshopform_numerrors_map',
 404              '/path/to/workshop/subplugin_workshopform_rubric_workshop/workshopform_rubric_config');
 405          foreach ($keys as $key) {
 406              // Verify the element exists.
 407              $this->assertArrayHasKey($key, $patheles);
 408              // Verify the element is a restore_path_element.
 409              $this->assertTrue($patheles[$key] instanceof restore_path_element);
 410              // Check it has a processing object.
 411              $po = $patheles[$key]->get_processing_object();
 412              $this->assertTrue($po instanceof restore_subplugin);
 413          }
 414  
 415          // Correct BC call to workshopform subplugin (@ 'assessment' level).
 416          $bt = new mock_restore_task_basepath('taskname');
 417          $bs = new mock_restore_structure_step('steptest', null, $bt);
 418          $el = new restore_path_element('assessment', '/a/assessment');
 419          $bt->set_modulename('workshop');
 420          $bs->add_subplugin_structure('workshopform', $el);
 421          $patheles = $bs->get_pathelements();
 422          // Verify some well-known workshopform subplugin restore_path_elements have been added.
 423          $keys = array(
 424              '/a/assessment/subplugin_workshopform_accumulative_assessment/workshopform_accumulative_grade',
 425              '/a/assessment/subplugin_workshopform_comments_assessment/workshopform_comments_grade',
 426              '/a/assessment/subplugin_workshopform_numerrors_assessment/workshopform_numerrors_grade',
 427              '/a/assessment/subplugin_workshopform_rubric_assessment/workshopform_rubric_grade');
 428          foreach ($keys as $key) {
 429              // Verify the element exists.
 430              $this->assertArrayHasKey($key, $patheles);
 431              // Verify the element is a restore_path_element.
 432              $this->assertTrue($patheles[$key] instanceof restore_path_element);
 433              // Check it has a processing object.
 434              $po = $patheles[$key]->get_processing_object();
 435              $this->assertTrue($po instanceof restore_subplugin);
 436          }
 437  
 438          // TODO: Add some test covering a non-mod subplugin once we have some implemented in core.
 439      }
 440  
 441      /**
 442       * wrong base_step class tests
 443       */
 444      function test_base_step_wrong() {
 445  
 446          // Try to pass one wrong task
 447          try {
 448              $bt = new mock_base_step('teststep', new stdclass());
 449              $this->assertTrue(false, 'base_step_exception expected');
 450          } catch (exception $e) {
 451              $this->assertTrue($e instanceof base_step_exception);
 452              $this->assertEquals($e->errorcode, 'wrong_base_task_specified');
 453          }
 454      }
 455  
 456      /**
 457       * wrong backup_step class tests
 458       */
 459      function test_backup_test_wrong() {
 460  
 461          // Try to pass one wrong task
 462          try {
 463              $bt = new mock_backup_step('teststep', new stdclass());
 464              $this->assertTrue(false, 'backup_step_exception expected');
 465          } catch (exception $e) {
 466              $this->assertTrue($e instanceof backup_step_exception);
 467              $this->assertEquals($e->errorcode, 'wrong_backup_task_specified');
 468          }
 469      }
 470  }