Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 311 and 401] [Versions 401 and 402] [Versions 401 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  namespace core_course\backup;
  18  
  19  use backup;
  20  use backup_controller;
  21  use restore_controller;
  22  use restore_dbops;
  23  
  24  defined('MOODLE_INTERNAL') || die();
  25  global $CFG;
  26  
  27  require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
  28  require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  29  require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest.php');
  30  
  31  /**
  32   * Course restore testcase.
  33   *
  34   * @package    core_course
  35   * @copyright  2016 Frédéric Massart - FMCorz.net
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class restore_test extends \advanced_testcase {
  39  
  40      /**
  41       * Backup a course and return its backup ID.
  42       *
  43       * @param int $courseid The course ID.
  44       * @param int $userid The user doing the backup.
  45       * @return string
  46       */
  47      protected function backup_course($courseid, $userid = 2) {
  48          $backuptempdir = make_backup_temp_directory('');
  49          $packer = get_file_packer('application/vnd.moodle.backup');
  50  
  51          $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
  52              backup::MODE_GENERAL, $userid);
  53          $bc->execute_plan();
  54  
  55          $results = $bc->get_results();
  56          $results['backup_destination']->extract_to_pathname($packer, "$backuptempdir/core_course_testcase");
  57  
  58          $bc->destroy();
  59          unset($bc);
  60          return 'core_course_testcase';
  61      }
  62  
  63      /**
  64       * Create a role with capabilities and permissions.
  65       *
  66       * @param string|array $caps Capability names.
  67       * @param int $perm Constant CAP_* to apply to the capabilities.
  68       * @return int The new role ID.
  69       */
  70      protected function create_role_with_caps($caps, $perm) {
  71          $caps = (array) $caps;
  72          $dg = $this->getDataGenerator();
  73          $roleid = $dg->create_role();
  74          foreach ($caps as $cap) {
  75              assign_capability($cap, $perm, $roleid, \context_system::instance()->id, true);
  76          }
  77          accesslib_clear_all_caches_for_unit_testing();
  78          return $roleid;
  79      }
  80  
  81      /**
  82       * Restore a course.
  83       *
  84       * @param int $backupid The backup ID.
  85       * @param int $courseid The course ID to restore in, or 0.
  86       * @param int $userid The ID of the user performing the restore.
  87       * @return stdClass The updated course object.
  88       */
  89      protected function restore_course($backupid, $courseid, $userid) {
  90          global $DB;
  91  
  92          $target = backup::TARGET_CURRENT_ADDING;
  93          if (!$courseid) {
  94              $target = backup::TARGET_NEW_COURSE;
  95              $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
  96              $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
  97          }
  98  
  99          $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target);
 100          $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
 101          $this->assertTrue($rc->execute_precheck());
 102          $rc->execute_plan();
 103  
 104          $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
 105  
 106          $rc->destroy();
 107          unset($rc);
 108          return $course;
 109      }
 110  
 111      /**
 112       * Restore a course to an existing course.
 113       *
 114       * @param int $backupid The backup ID.
 115       * @param int $courseid The course ID to restore in.
 116       * @param int $userid The ID of the user performing the restore.
 117       * @return stdClass The updated course object.
 118       */
 119      protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
 120          return $this->restore_course($backupid, $courseid, $userid);
 121      }
 122  
 123      /**
 124       * Restore a course to a new course.
 125       *
 126       * @param int $backupid The backup ID.
 127       * @param int $userid The ID of the user performing the restore.
 128       * @return stdClass The new course object.
 129       */
 130      protected function restore_to_new_course($backupid, $userid = 2) {
 131          return $this->restore_course($backupid, 0, $userid);
 132      }
 133  
 134      /**
 135       * Restore a course.
 136       *
 137       * @param int $backupid The backup ID.
 138       * @param int $courseid The course ID to restore in, or 0.
 139       * @param int $userid The ID of the user performing the restore.
 140       * @param int $target THe target of the restore.
 141       *
 142       * @return stdClass The updated course object.
 143       */
 144      protected function async_restore_course($backupid, $courseid, $userid, $target) {
 145          global $DB;
 146  
 147          if (!$courseid) {
 148              $target = backup::TARGET_NEW_COURSE;
 149              $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
 150              $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
 151          }
 152  
 153          $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_ASYNC, $userid, $target);
 154          $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
 155          $this->assertTrue($rc->execute_precheck());
 156  
 157          $restoreid = $rc->get_restoreid();
 158          $rc->destroy();
 159  
 160          // Create the adhoc task.
 161          $asynctask = new \core\task\asynchronous_restore_task();
 162          $asynctask->set_blocking(false);
 163          $asynctask->set_custom_data(array('backupid' => $restoreid));
 164          \core\task\manager::queue_adhoc_task($asynctask);
 165  
 166          // We are expecting trace output during this test.
 167          $this->expectOutputRegex("/$restoreid/");
 168  
 169          // Execute adhoc task.
 170          $now = time();
 171          $task = \core\task\manager::get_next_adhoc_task($now);
 172          $this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
 173          $task->execute();
 174          \core\task\manager::adhoc_task_complete($task);
 175  
 176          $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
 177  
 178          return $course;
 179      }
 180  
 181      /**
 182       * Restore a course to an existing course.
 183       *
 184       * @param int $backupid The backup ID.
 185       * @param int $courseid The course ID to restore in.
 186       * @param int $userid The ID of the user performing the restore.
 187       * @param int $target The type of restore we are performing.
 188       * @return stdClass The updated course object.
 189       */
 190      protected function async_restore_to_existing_course($backupid, $courseid,
 191          $userid = 2, $target = backup::TARGET_CURRENT_ADDING) {
 192          return $this->async_restore_course($backupid, $courseid, $userid, $target);
 193      }
 194  
 195      /**
 196       * Restore a course to a new course.
 197       *
 198       * @param int $backupid The backup ID.
 199       * @param int $userid The ID of the user performing the restore.
 200       * @return stdClass The new course object.
 201       */
 202      protected function async_restore_to_new_course($backupid, $userid = 2) {
 203          return $this->async_restore_course($backupid, 0, $userid, 0);
 204      }
 205  
 206      public function test_async_restore_existing_idnumber_in_new_course() {
 207          $this->resetAfterTest();
 208  
 209          $dg = $this->getDataGenerator();
 210          $c1 = $dg->create_course(['idnumber' => 'ABC']);
 211          $backupid = $this->backup_course($c1->id);
 212          $c2 = $this->async_restore_to_new_course($backupid);
 213  
 214          // The ID number is set empty.
 215          $this->assertEquals('', $c2->idnumber);
 216      }
 217  
 218      public function test_async_restore_course_info_in_existing_course() {
 219          global $DB;
 220          $this->resetAfterTest();
 221          $dg = $this->getDataGenerator();
 222  
 223          $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
 224          $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
 225          $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
 226  
 227          $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 228  
 229          // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
 230          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
 231              'startdate' => $startdate]);
 232          $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
 233          $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
 234              'startdate' => $startdate + 2 * WEEKSECS]);
 235          $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
 236          $backupid = $this->backup_course($c1->id);
 237  
 238          // The information is restored but adapted because names are already taken.
 239          $c2 = $this->async_restore_to_existing_course($backupid, $c2->id);
 240          $this->assertEquals('SN_1', $c2->shortname);
 241          $this->assertEquals('FN copy 1', $c2->fullname);
 242          $this->assertEquals('DESC', $c2->summary);
 243          $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
 244          $this->assertEquals($startdate, $c2->startdate);
 245  
 246          // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
 247          // Their dates are exactly the same as they were in the original modules.
 248          $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
 249          $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
 250          $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
 251          $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
 252      }
 253  
 254      public function test_async_restore_course_info_in_existing_course_delete_first() {
 255          global $DB;
 256          $this->resetAfterTest();
 257          $dg = $this->getDataGenerator();
 258  
 259          $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
 260          $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
 261          $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
 262  
 263          $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 264  
 265          // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
 266          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
 267              'startdate' => $startdate]);
 268          $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
 269          $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
 270              'startdate' => $startdate + 2 * WEEKSECS]);
 271          $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
 272          $backupid = $this->backup_course($c1->id);
 273  
 274          // The information is restored and the existing course settings is modified.
 275          $c2 = $this->async_restore_to_existing_course($backupid, $c2->id, 2, backup::TARGET_CURRENT_DELETING);
 276          $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
 277  
 278          // Now course2 should have a new forum with the original forum deleted.
 279          $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
 280          $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
 281          $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
 282          $this->assertEmpty($restoredchat2);
 283      }
 284  
 285      public function test_restore_existing_idnumber_in_new_course() {
 286          $this->resetAfterTest();
 287  
 288          $dg = $this->getDataGenerator();
 289          $c1 = $dg->create_course(['idnumber' => 'ABC']);
 290          $backupid = $this->backup_course($c1->id);
 291          $c2 = $this->restore_to_new_course($backupid);
 292  
 293          // The ID number is set empty.
 294          $this->assertEquals('', $c2->idnumber);
 295      }
 296  
 297      public function test_restore_non_existing_idnumber_in_new_course() {
 298          global $DB;
 299          $this->resetAfterTest();
 300  
 301          $dg = $this->getDataGenerator();
 302          $c1 = $dg->create_course(['idnumber' => 'ABC']);
 303          $backupid = $this->backup_course($c1->id);
 304  
 305          $c1->idnumber = 'BCD';
 306          $DB->update_record('course', $c1);
 307  
 308          // The ID number changed.
 309          $c2 = $this->restore_to_new_course($backupid);
 310          $this->assertEquals('ABC', $c2->idnumber);
 311      }
 312  
 313      public function test_restore_existing_idnumber_in_existing_course() {
 314          global $DB;
 315          $this->resetAfterTest();
 316  
 317          $dg = $this->getDataGenerator();
 318          $c1 = $dg->create_course(['idnumber' => 'ABC']);
 319          $c2 = $dg->create_course(['idnumber' => 'DEF']);
 320          $backupid = $this->backup_course($c1->id);
 321  
 322          // The ID number does not change.
 323          $c2 = $this->restore_to_existing_course($backupid, $c2->id);
 324          $this->assertEquals('DEF', $c2->idnumber);
 325  
 326          $c1 = $DB->get_record('course', array('id' => $c1->id));
 327          $this->assertEquals('ABC', $c1->idnumber);
 328      }
 329  
 330      public function test_restore_non_existing_idnumber_in_existing_course() {
 331          global $DB;
 332          $this->resetAfterTest();
 333  
 334          $dg = $this->getDataGenerator();
 335          $c1 = $dg->create_course(['idnumber' => 'ABC']);
 336          $c2 = $dg->create_course(['idnumber' => 'DEF']);
 337          $backupid = $this->backup_course($c1->id);
 338  
 339          $c1->idnumber = 'XXX';
 340          $DB->update_record('course', $c1);
 341  
 342          // The ID number has changed.
 343          $c2 = $this->restore_to_existing_course($backupid, $c2->id);
 344          $this->assertEquals('ABC', $c2->idnumber);
 345      }
 346  
 347      public function test_restore_idnumber_in_existing_course_without_permissions() {
 348          global $DB;
 349          $this->resetAfterTest();
 350          $dg = $this->getDataGenerator();
 351          $u1 = $dg->create_user();
 352  
 353          $managers = get_archetype_roles('manager');
 354          $manager = array_shift($managers);
 355          $roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
 356          $dg->role_assign($manager->id, $u1->id);
 357          $dg->role_assign($roleid, $u1->id);
 358  
 359          $c1 = $dg->create_course(['idnumber' => 'ABC']);
 360          $c2 = $dg->create_course(['idnumber' => 'DEF']);
 361          $backupid = $this->backup_course($c1->id);
 362  
 363          $c1->idnumber = 'XXX';
 364          $DB->update_record('course', $c1);
 365  
 366          // The ID number does not change.
 367          $c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 368          $this->assertEquals('DEF', $c2->idnumber);
 369      }
 370  
 371      public function test_restore_course_info_in_new_course() {
 372          global $DB;
 373          $this->resetAfterTest();
 374          $dg = $this->getDataGenerator();
 375  
 376          $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 377  
 378          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'startdate' => $startdate,
 379              'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 380          $backupid = $this->backup_course($c1->id);
 381  
 382          // The information is restored but adapted because names are already taken.
 383          $c2 = $this->restore_to_new_course($backupid);
 384          $this->assertEquals('SN_1', $c2->shortname);
 385          $this->assertEquals('FN copy 1', $c2->fullname);
 386          $this->assertEquals('DESC', $c2->summary);
 387          $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
 388          $this->assertEquals($startdate, $c2->startdate);
 389      }
 390  
 391      public function test_restore_course_info_in_existing_course() {
 392          global $DB;
 393          $this->resetAfterTest();
 394          $dg = $this->getDataGenerator();
 395  
 396          $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
 397          $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
 398          $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
 399  
 400          $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 401  
 402          // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
 403          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
 404              'startdate' => $startdate]);
 405          $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
 406          $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
 407              'startdate' => $startdate + 2 * WEEKSECS]);
 408          $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
 409          $backupid = $this->backup_course($c1->id);
 410  
 411          // The information is restored but adapted because names are already taken.
 412          $c2 = $this->restore_to_existing_course($backupid, $c2->id);
 413          $this->assertEquals('SN_1', $c2->shortname);
 414          $this->assertEquals('FN copy 1', $c2->fullname);
 415          $this->assertEquals('DESC', $c2->summary);
 416          $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
 417          $this->assertEquals($startdate, $c2->startdate);
 418  
 419          // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
 420          // Their dates are exactly the same as they were in the original modules.
 421          $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
 422          $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
 423          $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
 424          $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
 425      }
 426  
 427      public function test_restore_course_shortname_in_existing_course_without_permissions() {
 428          global $DB;
 429          $this->resetAfterTest();
 430          $dg = $this->getDataGenerator();
 431          $u1 = $dg->create_user();
 432  
 433          $managers = get_archetype_roles('manager');
 434          $manager = array_shift($managers);
 435          $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
 436          $dg->role_assign($manager->id, $u1->id);
 437          $dg->role_assign($roleid, $u1->id);
 438  
 439          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 440          $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
 441  
 442          // The shortname does not change.
 443          $backupid = $this->backup_course($c1->id);
 444          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 445          $this->assertEquals($c2->shortname, $restored->shortname);
 446          $this->assertEquals('FN copy 1', $restored->fullname);
 447          $this->assertEquals('DESC', $restored->summary);
 448          $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
 449      }
 450  
 451      public function test_restore_course_fullname_in_existing_course_without_permissions() {
 452          global $DB;
 453          $this->resetAfterTest();
 454          $dg = $this->getDataGenerator();
 455          $u1 = $dg->create_user();
 456  
 457          $managers = get_archetype_roles('manager');
 458          $manager = array_shift($managers);
 459          $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
 460          $dg->role_assign($manager->id, $u1->id);
 461          $dg->role_assign($roleid, $u1->id);
 462  
 463          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 464          $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
 465  
 466          // The fullname does not change.
 467          $backupid = $this->backup_course($c1->id);
 468          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 469          $this->assertEquals('SN_1', $restored->shortname);
 470          $this->assertEquals($c2->fullname, $restored->fullname);
 471          $this->assertEquals('DESC', $restored->summary);
 472          $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
 473      }
 474  
 475      public function test_restore_course_summary_in_existing_course_without_permissions() {
 476          global $DB;
 477          $this->resetAfterTest();
 478          $dg = $this->getDataGenerator();
 479          $u1 = $dg->create_user();
 480  
 481          $managers = get_archetype_roles('manager');
 482          $manager = array_shift($managers);
 483          $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
 484          $dg->role_assign($manager->id, $u1->id);
 485          $dg->role_assign($roleid, $u1->id);
 486  
 487          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 488          $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
 489  
 490          // The summary and format do not change.
 491          $backupid = $this->backup_course($c1->id);
 492          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 493          $this->assertEquals('SN_1', $restored->shortname);
 494          $this->assertEquals('FN copy 1', $restored->fullname);
 495          $this->assertEquals($c2->summary, $restored->summary);
 496          $this->assertEquals($c2->summaryformat, $restored->summaryformat);
 497      }
 498  
 499      public function test_restore_course_startdate_in_existing_course_without_permissions() {
 500          global $DB;
 501          $this->resetAfterTest();
 502          $dg = $this->getDataGenerator();
 503  
 504          $u1 = $dg->create_user();
 505          $managers = get_archetype_roles('manager');
 506          $manager = array_shift($managers);
 507          $roleid = $this->create_role_with_caps('moodle/restore:rolldates', CAP_PROHIBIT);
 508          $dg->role_assign($manager->id, $u1->id);
 509          $dg->role_assign($roleid, $u1->id);
 510  
 511          // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
 512          $startdate1 = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 513          $startdate2 = mktime(12, 0, 0, 1, 13, 2000); // 13-Jan-2000.
 514          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
 515              'startdate' => $startdate1]);
 516          $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
 517          $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
 518              'startdate' => $startdate2]);
 519          $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
 520  
 521          // The startdate does not change.
 522          $backupid = $this->backup_course($c1->id);
 523          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 524          $this->assertEquals('SN_1', $restored->shortname);
 525          $this->assertEquals('FN copy 1', $restored->fullname);
 526          $this->assertEquals('DESC', $restored->summary);
 527          $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
 528          $this->assertEquals($startdate2, $restored->startdate);
 529  
 530          // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
 531          // Start date of the restored chat ('First') was changed to be 1 week after the c2 start date.
 532          $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
 533          $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
 534          $this->assertNotEquals($chat1->chattime, $restoredchat1->chattime);
 535          $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
 536          $this->assertEquals($c2->startdate + 1 * WEEKSECS, $restoredchat2->chattime);
 537      }
 538  
 539      /**
 540       * Tests course restore with editor in course format.
 541       *
 542       * @author Matthew Hilton
 543       * @covers \core_courseformat
 544       */
 545      public function test_restore_editor_courseformat() {
 546          $this->resetAfterTest();
 547  
 548          // Setup user with restore permissions.
 549          $dg = $this->getDataGenerator();
 550          $u1 = $dg->create_user();
 551  
 552          $managers = get_archetype_roles('manager');
 553          $manager = array_shift($managers);
 554          $dg->role_assign($manager->id, $u1->id);
 555  
 556          // Create a course with an editor item in the course format.
 557          $courseformatoptiondata = (object) [
 558              "hideoddsections" => 1,
 559              'summary_editor' => [
 560                  'text' => '<p>Somewhere over the rainbow</p><p>The <b>quick</b> brown fox jumpos over the lazy dog.</p>',
 561                  'format' => 1
 562              ]
 563          ];
 564          $course1 = $dg->create_course(['format' => 'theunittest']);
 565          $course2 = $dg->create_course(['format' => 'theunittest']);
 566          $this->assertEquals('theunittest', $course1->format);
 567          course_create_sections_if_missing($course1, array(0, 1));
 568  
 569          // Set the course format.
 570          $courseformat = course_get_format($course1);
 571          $courseformat->update_course_format_options($courseformatoptiondata);
 572  
 573          // Backup and restore the course.
 574          $backupid = $this->backup_course($course1->id);
 575          $this->restore_to_existing_course($backupid, $course2->id, $u1->id);
 576  
 577          // Get the restored course format.
 578          $restoredformat = course_get_format($course2);
 579          $restoredformatoptions = $restoredformat->get_format_options();
 580  
 581          $this->assertEqualsCanonicalizing($courseformatoptiondata, (object) $restoredformatoptions);
 582      }
 583  }