Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 311 and 403] [Versions 400 and 403] [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_with_users() {
 392          global $DB;
 393          $this->resetAfterTest();
 394          $this->setAdminUser();
 395          $dg = $this->getDataGenerator();
 396  
 397          // Create a user and a course, enrol user in the course. Backup this course.
 398          $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 399          $u1 = $dg->create_user(['firstname' => 'Olivia']);
 400          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'startdate' => $startdate,
 401              'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 402          $dg->enrol_user($u1->id, $c1->id, 'student');
 403          $backupid = $this->backup_course($c1->id);
 404  
 405          // Delete the course and the user completely.
 406          delete_course($c1, false);
 407          delete_user($u1);
 408          $DB->delete_records('user', ['id' => $u1->id]);
 409  
 410          // Now restore this course, the user will be created and event user_created event will be triggered.
 411          $sink = $this->redirectEvents();
 412          $c2 = $this->restore_to_new_course($backupid);
 413          $events = $sink->get_events();
 414          $sink->close();
 415  
 416          $user = $DB->get_record('user', ['firstname' => 'Olivia'], '*', MUST_EXIST);
 417          $events = array_values(array_filter($events, function(\core\event\base $event) {
 418              return is_a($event, \core\event\user_created::class);
 419          }));
 420          $this->assertEquals(1, count($events));
 421          $this->assertEquals($user->id, $events[0]->relateduserid);
 422          $this->assertEquals($c2->id, $events[0]->other['courseid']);
 423          $this->assertStringContainsString("during restore of the course with id '{$c2->id}'",
 424              $events[0]->get_description());
 425      }
 426  
 427      public function test_restore_course_info_in_existing_course() {
 428          global $DB;
 429          $this->resetAfterTest();
 430          $dg = $this->getDataGenerator();
 431  
 432          $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
 433          $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
 434          $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
 435  
 436          $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 437  
 438          // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
 439          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
 440              'startdate' => $startdate]);
 441          $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
 442          $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
 443              'startdate' => $startdate + 2 * WEEKSECS]);
 444          $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
 445          $backupid = $this->backup_course($c1->id);
 446  
 447          // The information is restored but adapted because names are already taken.
 448          $c2 = $this->restore_to_existing_course($backupid, $c2->id);
 449          $this->assertEquals('SN_1', $c2->shortname);
 450          $this->assertEquals('FN copy 1', $c2->fullname);
 451          $this->assertEquals('DESC', $c2->summary);
 452          $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
 453          $this->assertEquals($startdate, $c2->startdate);
 454  
 455          // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
 456          // Their dates are exactly the same as they were in the original modules.
 457          $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
 458          $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
 459          $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
 460          $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
 461      }
 462  
 463      public function test_restore_course_shortname_in_existing_course_without_permissions() {
 464          global $DB;
 465          $this->resetAfterTest();
 466          $dg = $this->getDataGenerator();
 467          $u1 = $dg->create_user();
 468  
 469          $managers = get_archetype_roles('manager');
 470          $manager = array_shift($managers);
 471          $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
 472          $dg->role_assign($manager->id, $u1->id);
 473          $dg->role_assign($roleid, $u1->id);
 474  
 475          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 476          $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
 477  
 478          // The shortname does not change.
 479          $backupid = $this->backup_course($c1->id);
 480          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 481          $this->assertEquals($c2->shortname, $restored->shortname);
 482          $this->assertEquals('FN copy 1', $restored->fullname);
 483          $this->assertEquals('DESC', $restored->summary);
 484          $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
 485      }
 486  
 487      public function test_restore_course_fullname_in_existing_course_without_permissions() {
 488          global $DB;
 489          $this->resetAfterTest();
 490          $dg = $this->getDataGenerator();
 491          $u1 = $dg->create_user();
 492  
 493          $managers = get_archetype_roles('manager');
 494          $manager = array_shift($managers);
 495          $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
 496          $dg->role_assign($manager->id, $u1->id);
 497          $dg->role_assign($roleid, $u1->id);
 498  
 499          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 500          $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
 501  
 502          // The fullname does not change.
 503          $backupid = $this->backup_course($c1->id);
 504          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 505          $this->assertEquals('SN_1', $restored->shortname);
 506          $this->assertEquals($c2->fullname, $restored->fullname);
 507          $this->assertEquals('DESC', $restored->summary);
 508          $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
 509      }
 510  
 511      public function test_restore_course_summary_in_existing_course_without_permissions() {
 512          global $DB;
 513          $this->resetAfterTest();
 514          $dg = $this->getDataGenerator();
 515          $u1 = $dg->create_user();
 516  
 517          $managers = get_archetype_roles('manager');
 518          $manager = array_shift($managers);
 519          $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
 520          $dg->role_assign($manager->id, $u1->id);
 521          $dg->role_assign($roleid, $u1->id);
 522  
 523          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
 524          $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
 525  
 526          // The summary and format do not change.
 527          $backupid = $this->backup_course($c1->id);
 528          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 529          $this->assertEquals('SN_1', $restored->shortname);
 530          $this->assertEquals('FN copy 1', $restored->fullname);
 531          $this->assertEquals($c2->summary, $restored->summary);
 532          $this->assertEquals($c2->summaryformat, $restored->summaryformat);
 533      }
 534  
 535      public function test_restore_course_startdate_in_existing_course_without_permissions() {
 536          global $DB;
 537          $this->resetAfterTest();
 538          $dg = $this->getDataGenerator();
 539  
 540          $u1 = $dg->create_user();
 541          $managers = get_archetype_roles('manager');
 542          $manager = array_shift($managers);
 543          $roleid = $this->create_role_with_caps('moodle/restore:rolldates', CAP_PROHIBIT);
 544          $dg->role_assign($manager->id, $u1->id);
 545          $dg->role_assign($roleid, $u1->id);
 546  
 547          // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
 548          $startdate1 = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
 549          $startdate2 = mktime(12, 0, 0, 1, 13, 2000); // 13-Jan-2000.
 550          $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
 551              'startdate' => $startdate1]);
 552          $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
 553          $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
 554              'startdate' => $startdate2]);
 555          $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
 556  
 557          // The startdate does not change.
 558          $backupid = $this->backup_course($c1->id);
 559          $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
 560          $this->assertEquals('SN_1', $restored->shortname);
 561          $this->assertEquals('FN copy 1', $restored->fullname);
 562          $this->assertEquals('DESC', $restored->summary);
 563          $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
 564          $this->assertEquals($startdate2, $restored->startdate);
 565  
 566          // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
 567          // Start date of the restored chat ('First') was changed to be 1 week after the c2 start date.
 568          $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
 569          $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
 570          $this->assertNotEquals($chat1->chattime, $restoredchat1->chattime);
 571          $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
 572          $this->assertEquals($c2->startdate + 1 * WEEKSECS, $restoredchat2->chattime);
 573      }
 574  
 575      /**
 576       * Tests course restore with editor in course format.
 577       *
 578       * @author Matthew Hilton
 579       * @covers \core_courseformat
 580       */
 581      public function test_restore_editor_courseformat() {
 582          $this->resetAfterTest();
 583  
 584          // Setup user with restore permissions.
 585          $dg = $this->getDataGenerator();
 586          $u1 = $dg->create_user();
 587  
 588          $managers = get_archetype_roles('manager');
 589          $manager = array_shift($managers);
 590          $dg->role_assign($manager->id, $u1->id);
 591  
 592          // Create a course with an editor item in the course format.
 593          $courseformatoptiondata = (object) [
 594              "hideoddsections" => 1,
 595              'summary_editor' => [
 596                  'text' => '<p>Somewhere over the rainbow</p><p>The <b>quick</b> brown fox jumpos over the lazy dog.</p>',
 597                  'format' => 1
 598              ]
 599          ];
 600          $course1 = $dg->create_course(['format' => 'theunittest']);
 601          $course2 = $dg->create_course(['format' => 'theunittest']);
 602          $this->assertEquals('theunittest', $course1->format);
 603          course_create_sections_if_missing($course1, array(0, 1));
 604  
 605          // Set the course format.
 606          $courseformat = course_get_format($course1);
 607          $courseformat->update_course_format_options($courseformatoptiondata);
 608  
 609          // Backup and restore the course.
 610          $backupid = $this->backup_course($course1->id);
 611          $this->restore_to_existing_course($backupid, $course2->id, $u1->id);
 612  
 613          // Get the restored course format.
 614          $restoredformat = course_get_format($course2);
 615          $restoredformatoptions = $restoredformat->get_format_options();
 616  
 617          $this->assertEqualsCanonicalizing($courseformatoptiondata, (object) $restoredformatoptions);
 618      }
 619  }