Merge branch 'MDL-63303_master-deleteduserfix' of https://github.com/markn86/moodle
[moodle.git] / course / tests / restore_test.php
blob470e03b13daa028cef2ef3bfacb5064f5163bc91
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.
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/>.
17 /**
18 * Course restore tests.
20 * @package core_course
21 * @copyright 2016 Frédéric Massart - FMCorz.net
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
26 global $CFG;
28 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
29 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
31 /**
32 * Course restore testcase.
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
38 class core_course_restore_testcase extends advanced_testcase {
40 /**
41 * Backup a course and return its backup ID.
43 * @param int $courseid The course ID.
44 * @param int $userid The user doing the backup.
45 * @return string
47 protected function backup_course($courseid, $userid = 2) {
48 $backuptempdir = make_backup_temp_directory('');
49 $packer = get_file_packer('application/vnd.moodle.backup');
51 $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
52 backup::MODE_GENERAL, $userid);
53 $bc->execute_plan();
55 $results = $bc->get_results();
56 $results['backup_destination']->extract_to_pathname($packer, "$backuptempdir/core_course_testcase");
58 $bc->destroy();
59 unset($bc);
60 return 'core_course_testcase';
63 /**
64 * Create a role with capabilities and permissions.
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.
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);
77 accesslib_clear_all_caches_for_unit_testing();
78 return $roleid;
81 /**
82 * Restore a course.
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.
89 protected function restore_course($backupid, $courseid, $userid) {
90 global $DB;
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);
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();
104 $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
106 $rc->destroy();
107 unset($rc);
108 return $course;
112 * Restore a course to an existing course.
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.
119 protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
120 return $this->restore_course($backupid, $courseid, $userid);
124 * Restore a course to a new course.
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.
130 protected function restore_to_new_course($backupid, $userid = 2) {
131 return $this->restore_course($backupid, 0, $userid);
134 public function test_restore_existing_idnumber_in_new_course() {
135 $this->resetAfterTest();
137 $dg = $this->getDataGenerator();
138 $c1 = $dg->create_course(['idnumber' => 'ABC']);
139 $backupid = $this->backup_course($c1->id);
140 $c2 = $this->restore_to_new_course($backupid);
142 // The ID number is set empty.
143 $this->assertEquals('', $c2->idnumber);
146 public function test_restore_non_existing_idnumber_in_new_course() {
147 global $DB;
148 $this->resetAfterTest();
150 $dg = $this->getDataGenerator();
151 $c1 = $dg->create_course(['idnumber' => 'ABC']);
152 $backupid = $this->backup_course($c1->id);
154 $c1->idnumber = 'BCD';
155 $DB->update_record('course', $c1);
157 // The ID number changed.
158 $c2 = $this->restore_to_new_course($backupid);
159 $this->assertEquals('ABC', $c2->idnumber);
162 public function test_restore_existing_idnumber_in_existing_course() {
163 global $DB;
164 $this->resetAfterTest();
166 $dg = $this->getDataGenerator();
167 $c1 = $dg->create_course(['idnumber' => 'ABC']);
168 $c2 = $dg->create_course(['idnumber' => 'DEF']);
169 $backupid = $this->backup_course($c1->id);
171 // The ID number does not change.
172 $c2 = $this->restore_to_existing_course($backupid, $c2->id);
173 $this->assertEquals('DEF', $c2->idnumber);
175 $c1 = $DB->get_record('course', array('id' => $c1->id));
176 $this->assertEquals('ABC', $c1->idnumber);
179 public function test_restore_non_existing_idnumber_in_existing_course() {
180 global $DB;
181 $this->resetAfterTest();
183 $dg = $this->getDataGenerator();
184 $c1 = $dg->create_course(['idnumber' => 'ABC']);
185 $c2 = $dg->create_course(['idnumber' => 'DEF']);
186 $backupid = $this->backup_course($c1->id);
188 $c1->idnumber = 'XXX';
189 $DB->update_record('course', $c1);
191 // The ID number has changed.
192 $c2 = $this->restore_to_existing_course($backupid, $c2->id);
193 $this->assertEquals('ABC', $c2->idnumber);
196 public function test_restore_idnumber_in_existing_course_without_permissions() {
197 global $DB;
198 $this->resetAfterTest();
199 $dg = $this->getDataGenerator();
200 $u1 = $dg->create_user();
202 $managers = get_archetype_roles('manager');
203 $manager = array_shift($managers);
204 $roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
205 $dg->role_assign($manager->id, $u1->id);
206 $dg->role_assign($roleid, $u1->id);
208 $c1 = $dg->create_course(['idnumber' => 'ABC']);
209 $c2 = $dg->create_course(['idnumber' => 'DEF']);
210 $backupid = $this->backup_course($c1->id);
212 $c1->idnumber = 'XXX';
213 $DB->update_record('course', $c1);
215 // The ID number does not change.
216 $c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
217 $this->assertEquals('DEF', $c2->idnumber);
220 public function test_restore_course_info_in_new_course() {
221 global $DB;
222 $this->resetAfterTest();
223 $dg = $this->getDataGenerator();
225 $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
227 $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'startdate' => $startdate,
228 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
229 $backupid = $this->backup_course($c1->id);
231 // The information is restored but adapted because names are already taken.
232 $c2 = $this->restore_to_new_course($backupid);
233 $this->assertEquals('SN_1', $c2->shortname);
234 $this->assertEquals('FN copy 1', $c2->fullname);
235 $this->assertEquals('DESC', $c2->summary);
236 $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
237 $this->assertEquals($startdate, $c2->startdate);
240 public function test_restore_course_info_in_existing_course() {
241 global $DB;
242 $this->resetAfterTest();
243 $dg = $this->getDataGenerator();
245 $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
246 $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
247 $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
249 $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
251 // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
252 $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
253 'startdate' => $startdate]);
254 $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
255 $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
256 'startdate' => $startdate + 2 * WEEKSECS]);
257 $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
258 $backupid = $this->backup_course($c1->id);
260 // The information is restored but adapted because names are already taken.
261 $c2 = $this->restore_to_existing_course($backupid, $c2->id);
262 $this->assertEquals('SN_1', $c2->shortname);
263 $this->assertEquals('FN copy 1', $c2->fullname);
264 $this->assertEquals('DESC', $c2->summary);
265 $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
266 $this->assertEquals($startdate, $c2->startdate);
268 // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
269 // Their dates are exactly the same as they were in the original modules.
270 $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
271 $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
272 $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
273 $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
276 public function test_restore_course_shortname_in_existing_course_without_permissions() {
277 global $DB;
278 $this->resetAfterTest();
279 $dg = $this->getDataGenerator();
280 $u1 = $dg->create_user();
282 $managers = get_archetype_roles('manager');
283 $manager = array_shift($managers);
284 $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
285 $dg->role_assign($manager->id, $u1->id);
286 $dg->role_assign($roleid, $u1->id);
288 $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
289 $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
291 // The shortname does not change.
292 $backupid = $this->backup_course($c1->id);
293 $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
294 $this->assertEquals($c2->shortname, $restored->shortname);
295 $this->assertEquals('FN copy 1', $restored->fullname);
296 $this->assertEquals('DESC', $restored->summary);
297 $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
300 public function test_restore_course_fullname_in_existing_course_without_permissions() {
301 global $DB;
302 $this->resetAfterTest();
303 $dg = $this->getDataGenerator();
304 $u1 = $dg->create_user();
306 $managers = get_archetype_roles('manager');
307 $manager = array_shift($managers);
308 $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
309 $dg->role_assign($manager->id, $u1->id);
310 $dg->role_assign($roleid, $u1->id);
312 $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
313 $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
315 // The fullname does not change.
316 $backupid = $this->backup_course($c1->id);
317 $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
318 $this->assertEquals('SN_1', $restored->shortname);
319 $this->assertEquals($c2->fullname, $restored->fullname);
320 $this->assertEquals('DESC', $restored->summary);
321 $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
324 public function test_restore_course_summary_in_existing_course_without_permissions() {
325 global $DB;
326 $this->resetAfterTest();
327 $dg = $this->getDataGenerator();
328 $u1 = $dg->create_user();
330 $managers = get_archetype_roles('manager');
331 $manager = array_shift($managers);
332 $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
333 $dg->role_assign($manager->id, $u1->id);
334 $dg->role_assign($roleid, $u1->id);
336 $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
337 $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
339 // The summary and format do not change.
340 $backupid = $this->backup_course($c1->id);
341 $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
342 $this->assertEquals('SN_1', $restored->shortname);
343 $this->assertEquals('FN copy 1', $restored->fullname);
344 $this->assertEquals($c2->summary, $restored->summary);
345 $this->assertEquals($c2->summaryformat, $restored->summaryformat);
348 public function test_restore_course_startdate_in_existing_course_without_permissions() {
349 global $DB;
350 $this->resetAfterTest();
351 $dg = $this->getDataGenerator();
353 $u1 = $dg->create_user();
354 $managers = get_archetype_roles('manager');
355 $manager = array_shift($managers);
356 $roleid = $this->create_role_with_caps('moodle/restore:rolldates', CAP_PROHIBIT);
357 $dg->role_assign($manager->id, $u1->id);
358 $dg->role_assign($roleid, $u1->id);
360 // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
361 $startdate1 = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
362 $startdate2 = mktime(12, 0, 0, 1, 13, 2000); // 13-Jan-2000.
363 $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
364 'startdate' => $startdate1]);
365 $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
366 $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
367 'startdate' => $startdate2]);
368 $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
370 // The startdate does not change.
371 $backupid = $this->backup_course($c1->id);
372 $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
373 $this->assertEquals('SN_1', $restored->shortname);
374 $this->assertEquals('FN copy 1', $restored->fullname);
375 $this->assertEquals('DESC', $restored->summary);
376 $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
377 $this->assertEquals($startdate2, $restored->startdate);
379 // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
380 // Start date of the restored chat ('First') was changed to be 1 week after the c2 start date.
381 $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
382 $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
383 $this->assertNotEquals($chat1->chattime, $restoredchat1->chattime);
384 $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
385 $this->assertEquals($c2->startdate + 1 * WEEKSECS, $restoredchat2->chattime);