2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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();
28 require_once($CFG->dirroot
. '/backup/util/includes/backup_includes.php');
29 require_once($CFG->dirroot
. '/backup/util/includes/restore_includes.php');
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
{
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.
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);
55 $results = $bc->get_results();
56 $results['backup_destination']->extract_to_pathname($packer, "$backuptempdir/core_course_testcase");
60 return 'core_course_testcase';
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();
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) {
92 $target = backup
::TARGET_CURRENT_ADDING
;
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());
104 $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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() {
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
);