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_backup; 18 19 use backup; 20 21 defined('MOODLE_INTERNAL') || die(); 22 23 global $CFG; 24 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 25 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 26 require_once($CFG->libdir . '/completionlib.php'); 27 28 /** 29 * Course copy tests. 30 * 31 * @package core_backup 32 * @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/> 33 * @author Matt Porritt <mattp@catalyst-au.net> 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 * @coversDefaultClass \copy_helper 36 */ 37 class copy_helper_test extends \advanced_testcase { 38 39 /** 40 * 41 * @var \stdClass Course used for testing. 42 */ 43 protected $course; 44 45 /** 46 * 47 * @var int User used to perform backups. 48 */ 49 protected $userid; 50 51 /** 52 * 53 * @var array Ids of users in test course. 54 */ 55 protected $courseusers; 56 57 /** 58 * 59 * @var array Names of the created activities. 60 */ 61 protected $activitynames; 62 63 /** 64 * Set up tasks for all tests. 65 */ 66 protected function setUp(): void { 67 global $DB, $CFG, $USER; 68 69 $this->resetAfterTest(true); 70 71 $CFG->enableavailability = true; 72 $CFG->enablecompletion = true; 73 74 // Create a course with some availability data set. 75 $generator = $this->getDataGenerator(); 76 $course = $generator->create_course( 77 array('format' => 'topics', 'numsections' => 3, 78 'enablecompletion' => COMPLETION_ENABLED), 79 array('createsections' => true)); 80 $forum = $generator->create_module('forum', array( 81 'course' => $course->id)); 82 $forum2 = $generator->create_module('forum', array( 83 'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); 84 85 // We need a grade, easiest is to add an assignment. 86 $assignrow = $generator->create_module('assign', array( 87 'course' => $course->id)); 88 $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); 89 $item = $assign->get_grade_item(); 90 91 // Make a test grouping as well. 92 $grouping = $generator->create_grouping(array('courseid' => $course->id, 93 'name' => 'Grouping!')); 94 95 // Create some users. 96 $user1 = $generator->create_user(); 97 $user2 = $generator->create_user(); 98 $user3 = $generator->create_user(); 99 $user4 = $generator->create_user(); 100 $this->courseusers = array( 101 $user1->id, $user2->id, $user3->id, $user4->id 102 ); 103 104 // Enrol users into the course. 105 $generator->enrol_user($user1->id, $course->id, 'student'); 106 $generator->enrol_user($user2->id, $course->id, 'editingteacher'); 107 $generator->enrol_user($user3->id, $course->id, 'manager'); 108 $generator->enrol_user($user4->id, $course->id, 'editingteacher'); 109 $generator->enrol_user($user4->id, $course->id, 'manager'); 110 111 $availability = '{"op":"|","show":false,"c":[' . 112 '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' . 113 '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' . 114 '{"type":"grouping","id":' . $grouping->id . '}' . 115 ']}'; 116 $DB->set_field('course_modules', 'availability', $availability, array( 117 'id' => $forum->cmid)); 118 $DB->set_field('course_sections', 'availability', $availability, array( 119 'course' => $course->id, 'section' => 1)); 120 121 // Add some user data to the course. 122 $discussion = $generator->get_plugin_generator('mod_forum')->create_discussion(['course' => $course->id, 123 'forum' => $forum->id, 'userid' => $user1->id, 'timemodified' => time(), 124 'name' => 'Frog']); 125 $generator->get_plugin_generator('mod_forum')->create_post(['discussion' => $discussion->id, 'userid' => $user1->id]); 126 127 $this->course = $course; 128 $this->userid = $USER->id; // Admin. 129 $this->activitynames = array( 130 $forum->name, 131 $forum2->name, 132 $assignrow->name 133 ); 134 135 // Set the user doing the backup to be a manager in the course. 136 // By default Managers can restore courses AND users, teachers can only do users. 137 $this->setUser($user3); 138 139 // Disable all loggers. 140 $CFG->backup_error_log_logger_level = backup::LOG_NONE; 141 $CFG->backup_output_indented_logger_level = backup::LOG_NONE; 142 $CFG->backup_file_logger_level = backup::LOG_NONE; 143 $CFG->backup_database_logger_level = backup::LOG_NONE; 144 $CFG->backup_file_logger_level_extra = backup::LOG_NONE; 145 } 146 147 /** 148 * Test process form data with invalid data. 149 * 150 * @covers ::process_formdata 151 */ 152 public function test_process_formdata_missing_fields() { 153 $this->expectException(\moodle_exception::class); 154 \copy_helper::process_formdata(new \stdClass); 155 } 156 157 /** 158 * Test processing form data. 159 * 160 * @covers ::process_formdata 161 */ 162 public function test_process_formdata() { 163 $validformdata = [ 164 'courseid' => 1729, 165 'fullname' => 'Taxicab Numbers', 166 'shortname' => 'Taxi101', 167 'category' => 2, 168 'visible' => 1, 169 'startdate' => 87539319, 170 'enddate' => 6963472309248, 171 'idnumber' => 1730, 172 'userdata' => 1 173 ]; 174 175 $roles = [ 176 'role_one' => 1, 177 'role_two' => 2, 178 'role_three' => 0 179 ]; 180 181 $expected = (object)array_merge($validformdata, ['keptroles' => []]); 182 $expected->keptroles = [1, 2]; 183 $processed = \copy_helper::process_formdata( 184 (object)array_merge( 185 $validformdata, 186 $roles, 187 ['extra' => 'stuff', 'remove' => 'this']) 188 ); 189 190 $this->assertEquals($expected, $processed); 191 } 192 193 /** 194 * Test orphaned controller cleanup. 195 * 196 * @covers ::cleanup_orphaned_copy_controllers 197 */ 198 public function test_cleanup_orphaned_copy_controllers() { 199 global $DB; 200 201 // Mock up the form data. 202 $formdata = new \stdClass; 203 $formdata->courseid = $this->course->id; 204 $formdata->fullname = 'foo'; 205 $formdata->shortname = 'data1'; 206 $formdata->category = 1; 207 $formdata->visible = 1; 208 $formdata->startdate = 1582376400; 209 $formdata->enddate = 0; 210 $formdata->idnumber = 123; 211 $formdata->userdata = 1; 212 $formdata->role_1 = 1; 213 $formdata->role_3 = 3; 214 $formdata->role_5 = 5; 215 216 $copies = []; 217 for ($i = 0; $i < 5; $i++) { 218 $formdata->shortname = 'data' . $i; 219 $copies[] = \copy_helper::create_copy($formdata); 220 } 221 222 // Delete one of the restore controllers. Simulates a situation where copy creation 223 // interrupted and the restore controller never gets created. 224 $DB->delete_records('backup_controllers', ['backupid' => $copies[0]['restoreid']]); 225 226 // Set a backup/restore controller pair to be in an intermediate state. 227 \backup_controller::load_controller($copies[2]['backupid'])->set_status(backup::STATUS_FINISHED_OK); 228 229 // Set a backup/restore controller pair to completed. 230 \backup_controller::load_controller($copies[3]['backupid'])->set_status(backup::STATUS_FINISHED_OK); 231 \restore_controller::load_controller($copies[3]['restoreid'])->set_status(backup::STATUS_FINISHED_OK); 232 233 // Set a backup/restore controller pair to have a failed backup. 234 \backup_controller::load_controller($copies[4]['backupid'])->set_status(backup::STATUS_FINISHED_ERR); 235 236 // Create some backup/restore controllers that are unrelated to course copies. 237 $bc = new \backup_controller(backup::TYPE_1COURSE, 1, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 238 2, backup::RELEASESESSION_YES); 239 $rc = new \restore_controller('restore-test-1729', 1, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 1, 2); 240 $rc->save_controller(); 241 $unrelatedvanillacontrollers = ['backupid' => $bc->get_backupid(), 'restoreid' => $rc->get_restoreid()]; 242 243 $bc = new \backup_controller(backup::TYPE_1COURSE, 1, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 244 2, backup::RELEASESESSION_YES); 245 $rc = new \restore_controller('restore-test-1729', 1, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 1, 2); 246 $bc->set_status(backup::STATUS_FINISHED_OK); 247 $rc->set_status(backup::STATUS_FINISHED_OK); 248 $unrelatedfinishedcontrollers = ['backupid' => $bc->get_backupid(), 'restoreid' => $rc->get_restoreid()]; 249 250 $bc = new \backup_controller(backup::TYPE_1COURSE, 1, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 251 2, backup::RELEASESESSION_YES); 252 $rc = new \restore_controller('restore-test-1729', 1, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 1, 2); 253 $bc->set_status(backup::STATUS_FINISHED_ERR); 254 $rc->set_status(backup::STATUS_FINISHED_ERR); 255 $unrelatedfailedcontrollers = ['backupid' => $bc->get_backupid(), 'restoreid' => $rc->get_restoreid()]; 256 257 // Clean up the backup_controllers table. 258 $records = $DB->get_records('backup_controllers', null, '', 'id, backupid, status, operation, purpose, timecreated'); 259 \copy_helper::cleanup_orphaned_copy_controllers($records, 0); 260 261 // Retrieve them again and check. 262 $records = $DB->get_records('backup_controllers', null, '', 'backupid, status'); 263 264 // Verify the backup associated with the deleted restore is marked as failed. 265 $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$copies[0]['backupid']]->status); 266 267 // Verify other controllers remain untouched. 268 $this->assertEquals(backup::STATUS_AWAITING, $records[$copies[1]['backupid']]->status); 269 $this->assertEquals(backup::STATUS_REQUIRE_CONV, $records[$copies[1]['restoreid']]->status); 270 $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$copies[2]['backupid']]->status); 271 $this->assertEquals(backup::STATUS_REQUIRE_CONV, $records[$copies[2]['restoreid']]->status); 272 $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$copies[3]['restoreid']]->status); 273 $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$copies[3]['backupid']]->status); 274 275 // Verify that the restore associated with the failed backup is also marked as failed. 276 $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$copies[4]['restoreid']]->status); 277 278 // Verify that the unrelated controllers remain unchanged. 279 $this->assertEquals(backup::STATUS_AWAITING, $records[$unrelatedvanillacontrollers['backupid']]->status); 280 $this->assertEquals(backup::STATUS_REQUIRE_CONV, $records[$unrelatedvanillacontrollers['restoreid']]->status); 281 $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$unrelatedfinishedcontrollers['backupid']]->status); 282 $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$unrelatedfinishedcontrollers['restoreid']]->status); 283 $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$unrelatedfailedcontrollers['backupid']]->status); 284 $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$unrelatedfailedcontrollers['restoreid']]->status); 285 } 286 287 /** 288 * Test creating a course copy. 289 * 290 * @covers ::create_copy 291 */ 292 public function test_create_copy() { 293 294 // Mock up the form data. 295 $formdata = new \stdClass; 296 $formdata->courseid = $this->course->id; 297 $formdata->fullname = 'foo'; 298 $formdata->shortname = 'bar'; 299 $formdata->category = 1; 300 $formdata->visible = 1; 301 $formdata->startdate = 1582376400; 302 $formdata->enddate = 0; 303 $formdata->idnumber = 123; 304 $formdata->userdata = 1; 305 $formdata->role_1 = 1; 306 $formdata->role_3 = 3; 307 $formdata->role_5 = 5; 308 309 $copydata = \copy_helper::process_formdata($formdata); 310 $result = \copy_helper::create_copy($copydata); 311 312 // Load the controllers, to extract the data we need. 313 $bc = \backup_controller::load_controller($result['backupid']); 314 $rc = \restore_controller::load_controller($result['restoreid']); 315 316 // Check the backup controller. 317 $this->assertEquals(backup::MODE_COPY, $bc->get_mode()); 318 $this->assertEquals($this->course->id, $bc->get_courseid()); 319 $this->assertEquals(backup::TYPE_1COURSE, $bc->get_type()); 320 321 // Check the restore controller. 322 $newcourseid = $rc->get_courseid(); 323 $newcourse = get_course($newcourseid); 324 325 $this->assertEquals(get_string('copyingcourse', 'backup'), $newcourse->fullname); 326 $this->assertEquals(get_string('copyingcourseshortname', 'backup'), $newcourse->shortname); 327 $this->assertEquals(backup::MODE_COPY, $rc->get_mode()); 328 $this->assertEquals($newcourseid, $rc->get_courseid()); 329 330 // Check the created ad-hoc task. 331 $now = time(); 332 $task = \core\task\manager::get_next_adhoc_task($now); 333 334 $this->assertInstanceOf('\\core\\task\\asynchronous_copy_task', $task); 335 $this->assertEquals($result, (array)$task->get_custom_data()); 336 $this->assertFalse($task->is_blocking()); 337 338 \core\task\manager::adhoc_task_complete($task); 339 } 340 341 /** 342 * Test getting the current copies. 343 * 344 * @covers ::get_copies 345 */ 346 public function test_get_copies() { 347 global $USER; 348 349 // Mock up the form data. 350 $formdata = new \stdClass; 351 $formdata->courseid = $this->course->id; 352 $formdata->fullname = 'foo'; 353 $formdata->shortname = 'bar'; 354 $formdata->category = 1; 355 $formdata->visible = 1; 356 $formdata->startdate = 1582376400; 357 $formdata->enddate = 0; 358 $formdata->idnumber = ''; 359 $formdata->userdata = 1; 360 $formdata->role_1 = 1; 361 $formdata->role_3 = 3; 362 $formdata->role_5 = 5; 363 364 $formdata2 = clone($formdata); 365 $formdata2->shortname = 'tree'; 366 367 // Create some copies. 368 $copydata = \copy_helper::process_formdata($formdata); 369 $result = \copy_helper::create_copy($copydata); 370 371 // Backup, awaiting. 372 $copies = \copy_helper::get_copies($USER->id); 373 $this->assertEquals($result['backupid'], $copies[0]->backupid); 374 $this->assertEquals($result['restoreid'], $copies[0]->restoreid); 375 $this->assertEquals(\backup::STATUS_AWAITING, $copies[0]->status); 376 $this->assertEquals(\backup::OPERATION_BACKUP, $copies[0]->operation); 377 378 $bc = \backup_controller::load_controller($result['backupid']); 379 380 // Backup, in progress. 381 $bc->set_status(\backup::STATUS_EXECUTING); 382 $copies = \copy_helper::get_copies($USER->id); 383 $this->assertEquals($result['backupid'], $copies[0]->backupid); 384 $this->assertEquals($result['restoreid'], $copies[0]->restoreid); 385 $this->assertEquals(\backup::STATUS_EXECUTING, $copies[0]->status); 386 $this->assertEquals(\backup::OPERATION_BACKUP, $copies[0]->operation); 387 388 // Restore, ready to process. 389 $bc->set_status(\backup::STATUS_FINISHED_OK); 390 $copies = \copy_helper::get_copies($USER->id); 391 $this->assertEquals(null, $copies[0]->backupid); 392 $this->assertEquals($result['restoreid'], $copies[0]->restoreid); 393 $this->assertEquals(\backup::STATUS_REQUIRE_CONV, $copies[0]->status); 394 $this->assertEquals(\backup::OPERATION_RESTORE, $copies[0]->operation); 395 396 // No records. 397 $bc->set_status(\backup::STATUS_FINISHED_ERR); 398 $copies = \copy_helper::get_copies($USER->id); 399 $this->assertEmpty($copies); 400 401 $copydata2 = \copy_helper::process_formdata($formdata2); 402 $result2 = \copy_helper::create_copy($copydata2); 403 // Set the second copy to be complete. 404 $bc = \backup_controller::load_controller($result2['backupid']); 405 $bc->set_status(\backup::STATUS_FINISHED_OK); 406 // Set the restore to be finished. 407 $rc = \backup_controller::load_controller($result2['restoreid']); 408 $rc->set_status(\backup::STATUS_FINISHED_OK); 409 410 // No records. 411 $copies = \copy_helper::get_copies($USER->id); 412 $this->assertEmpty($copies); 413 } 414 415 /** 416 * Test getting the current copies when they are in an invalid state. 417 * 418 * @covers ::get_copies 419 */ 420 public function test_get_copies_invalid_state() { 421 global $DB, $USER; 422 423 // Mock up the form data. 424 $formdata = new \stdClass; 425 $formdata->courseid = $this->course->id; 426 $formdata->fullname = 'foo'; 427 $formdata->shortname = 'bar'; 428 $formdata->category = 1; 429 $formdata->visible = 1; 430 $formdata->startdate = 1582376400; 431 $formdata->enddate = 0; 432 $formdata->idnumber = ''; 433 $formdata->userdata = 1; 434 $formdata->role_1 = 1; 435 $formdata->role_3 = 3; 436 $formdata->role_5 = 5; 437 438 $formdata2 = clone ($formdata); 439 $formdata2->shortname = 'tree'; 440 441 // Create some copies. 442 $copydata = \copy_helper::process_formdata($formdata); 443 $result = \copy_helper::create_copy($copydata); 444 $copydata2 = \copy_helper::process_formdata($formdata2); 445 $result2 = \copy_helper::create_copy($copydata2); 446 447 $copies = \copy_helper::get_copies($USER->id); 448 449 // Verify get_copies gives back both backup controllers. 450 $this->assertEqualsCanonicalizing([$result['backupid'], $result2['backupid']], array_column($copies, 'backupid')); 451 452 // Set one of the backup controllers to failed, this should cause it to not be present. 453 \backup_controller::load_controller($result['backupid'])->set_status(backup::STATUS_FINISHED_ERR); 454 $copies = \copy_helper::get_copies($USER->id); 455 456 // Verify there is only one backup listed, and that it is not the failed one. 457 $this->assertEqualsCanonicalizing([$result2['backupid']], array_column($copies, 'backupid')); 458 459 // Set the controller back to awaiting. 460 \backup_controller::load_controller($result['backupid'])->set_status(backup::STATUS_AWAITING); 461 $copies = \copy_helper::get_copies($USER->id); 462 463 // Verify both backup controllers are back. 464 $this->assertEqualsCanonicalizing([$result['backupid'], $result2['backupid']], array_column($copies, 'backupid')); 465 466 // Delete the restore controller for one of the copies, this should cause it to not be present. 467 $DB->delete_records('backup_controllers', ['backupid' => $result['restoreid']]); 468 $copies = \copy_helper::get_copies($USER->id); 469 470 // Verify there is only one backup listed, and that it is not the failed one. 471 $this->assertEqualsCanonicalizing([$result2['backupid']], array_column($copies, 'backupid')); 472 } 473 474 /** 475 * Test getting the current copies for specific course. 476 * 477 * @covers ::get_copies 478 */ 479 public function test_get_copies_course() { 480 global $USER; 481 482 // Mock up the form data. 483 $formdata = new \stdClass; 484 $formdata->courseid = $this->course->id; 485 $formdata->fullname = 'foo'; 486 $formdata->shortname = 'bar'; 487 $formdata->category = 1; 488 $formdata->visible = 1; 489 $formdata->startdate = 1582376400; 490 $formdata->enddate = 0; 491 $formdata->idnumber = ''; 492 $formdata->userdata = 1; 493 $formdata->role_1 = 1; 494 $formdata->role_3 = 3; 495 $formdata->role_5 = 5; 496 497 // Create some copies. 498 $copydata = \copy_helper::process_formdata($formdata); 499 \copy_helper::create_copy($copydata); 500 501 // No copies match this course id. 502 $copies = \copy_helper::get_copies($USER->id, ($this->course->id + 1)); 503 $this->assertEmpty($copies); 504 } 505 506 /** 507 * Test getting the current copies if course has been deleted. 508 * 509 * @covers ::get_copies 510 */ 511 public function test_get_copies_course_deleted() { 512 global $USER; 513 514 // Mock up the form data. 515 $formdata = new \stdClass; 516 $formdata->courseid = $this->course->id; 517 $formdata->fullname = 'foo'; 518 $formdata->shortname = 'bar'; 519 $formdata->category = 1; 520 $formdata->visible = 1; 521 $formdata->startdate = 1582376400; 522 $formdata->enddate = 0; 523 $formdata->idnumber = ''; 524 $formdata->userdata = 1; 525 $formdata->role_1 = 1; 526 $formdata->role_3 = 3; 527 $formdata->role_5 = 5; 528 529 // Create some copies. 530 $copydata = \copy_helper::process_formdata($formdata); 531 \copy_helper::create_copy($copydata); 532 533 delete_course($this->course->id, false); 534 535 // No copies match this course id as it has been deleted. 536 $copies = \copy_helper::get_copies($USER->id, ($this->course->id)); 537 $this->assertEmpty($copies); 538 } 539 540 /** 541 * Test course copy. 542 */ 543 public function test_course_copy() { 544 global $DB; 545 546 // Mock up the form data. 547 $formdata = new \stdClass; 548 $formdata->courseid = $this->course->id; 549 $formdata->fullname = 'copy course'; 550 $formdata->shortname = 'copy course short'; 551 $formdata->category = 1; 552 $formdata->visible = 0; 553 $formdata->startdate = 1582376400; 554 $formdata->enddate = 1582386400; 555 $formdata->idnumber = 123; 556 $formdata->userdata = 1; 557 $formdata->role_1 = 1; 558 $formdata->role_3 = 3; 559 $formdata->role_5 = 5; 560 561 // Create the course copy records and associated ad-hoc task. 562 $copydata = \copy_helper::process_formdata($formdata); 563 $copyids = \copy_helper::create_copy($copydata); 564 565 $courseid = $this->course->id; 566 567 // We are expecting trace output during this test. 568 $this->expectOutputRegex("/$courseid/"); 569 570 // Execute adhoc task. 571 $now = time(); 572 $task = \core\task\manager::get_next_adhoc_task($now); 573 $this->assertInstanceOf('\\core\\task\\asynchronous_copy_task', $task); 574 $task->execute(); 575 \core\task\manager::adhoc_task_complete($task); 576 577 $postbackuprec = $DB->get_record('backup_controllers', array('backupid' => $copyids['backupid'])); 578 $postrestorerec = $DB->get_record('backup_controllers', array('backupid' => $copyids['restoreid'])); 579 580 // Check backup was completed successfully. 581 $this->assertEquals(backup::STATUS_FINISHED_OK, $postbackuprec->status); 582 $this->assertEquals(1.0, $postbackuprec->progress); 583 584 // Check restore was completed successfully. 585 $this->assertEquals(backup::STATUS_FINISHED_OK, $postrestorerec->status); 586 $this->assertEquals(1.0, $postrestorerec->progress); 587 588 // Check the restored course itself. 589 $coursecontext = \context_course::instance($postrestorerec->itemid); 590 $users = get_enrolled_users($coursecontext); 591 592 $modinfo = get_fast_modinfo($postrestorerec->itemid); 593 $forums = $modinfo->get_instances_of('forum'); 594 $forum = reset($forums); 595 $discussions = forum_get_discussions($forum); 596 $course = $modinfo->get_course(); 597 598 $this->assertEquals($formdata->startdate, $course->startdate); 599 $this->assertEquals($formdata->enddate, $course->enddate); 600 $this->assertEquals('copy course', $course->fullname); 601 $this->assertEquals('copy course short', $course->shortname); 602 $this->assertEquals(0, $course->visible); 603 $this->assertEquals(123, $course->idnumber); 604 605 foreach ($modinfo->get_cms() as $cm) { 606 $this->assertContains($cm->get_formatted_name(), $this->activitynames); 607 } 608 609 foreach ($this->courseusers as $user) { 610 $this->assertEquals($user, $users[$user]->id); 611 } 612 613 $this->assertEquals(count($this->courseusers), count($users)); 614 $this->assertEquals(2, count($discussions)); 615 } 616 617 /** 618 * Test course copy, not including any users (or data). 619 */ 620 public function test_course_copy_no_users() { 621 global $DB; 622 623 // Mock up the form data. 624 $formdata = new \stdClass; 625 $formdata->courseid = $this->course->id; 626 $formdata->fullname = 'copy course'; 627 $formdata->shortname = 'copy course short'; 628 $formdata->category = 1; 629 $formdata->visible = 0; 630 $formdata->startdate = 1582376400; 631 $formdata->enddate = 1582386400; 632 $formdata->idnumber = 123; 633 $formdata->userdata = 1; 634 $formdata->role_1 = 0; 635 $formdata->role_3 = 0; 636 $formdata->role_5 = 0; 637 638 // Create the course copy records and associated ad-hoc task. 639 $copydata = \copy_helper::process_formdata($formdata); 640 $copyids = \copy_helper::create_copy($copydata); 641 642 $courseid = $this->course->id; 643 644 // We are expecting trace output during this test. 645 $this->expectOutputRegex("/$courseid/"); 646 647 // Execute adhoc task. 648 $now = time(); 649 $task = \core\task\manager::get_next_adhoc_task($now); 650 $this->assertInstanceOf('\\core\\task\\asynchronous_copy_task', $task); 651 $task->execute(); 652 \core\task\manager::adhoc_task_complete($task); 653 654 $postrestorerec = $DB->get_record('backup_controllers', array('backupid' => $copyids['restoreid'])); 655 656 // Check the restored course itself. 657 $coursecontext = \context_course::instance($postrestorerec->itemid); 658 $users = get_enrolled_users($coursecontext); 659 660 $modinfo = get_fast_modinfo($postrestorerec->itemid); 661 $forums = $modinfo->get_instances_of('forum'); 662 $forum = reset($forums); 663 $discussions = forum_get_discussions($forum); 664 $course = $modinfo->get_course(); 665 666 $this->assertEquals($formdata->startdate, $course->startdate); 667 $this->assertEquals($formdata->enddate, $course->enddate); 668 $this->assertEquals('copy course', $course->fullname); 669 $this->assertEquals('copy course short', $course->shortname); 670 $this->assertEquals(0, $course->visible); 671 $this->assertEquals(123, $course->idnumber); 672 673 foreach ($modinfo->get_cms() as $cm) { 674 $this->assertContains($cm->get_formatted_name(), $this->activitynames); 675 } 676 677 // Should be no discussions as the user that made them wasn't included. 678 $this->assertEquals(0, count($discussions)); 679 680 // There should only be one user in the new course, and that's the user who did the copy. 681 $this->assertEquals(1, count($users)); 682 $this->assertEquals($this->courseusers[2], $users[$this->courseusers[2]]->id); 683 684 } 685 686 /** 687 * Test course copy, including students and their data. 688 */ 689 public function test_course_copy_students_data() { 690 global $DB; 691 692 // Mock up the form data. 693 $formdata = new \stdClass; 694 $formdata->courseid = $this->course->id; 695 $formdata->fullname = 'copy course'; 696 $formdata->shortname = 'copy course short'; 697 $formdata->category = 1; 698 $formdata->visible = 0; 699 $formdata->startdate = 1582376400; 700 $formdata->enddate = 1582386400; 701 $formdata->idnumber = 123; 702 $formdata->userdata = 1; 703 $formdata->role_1 = 0; 704 $formdata->role_3 = 0; 705 $formdata->role_5 = 5; 706 707 // Create the course copy records and associated ad-hoc task. 708 $copydata = \copy_helper::process_formdata($formdata); 709 $copyids = \copy_helper::create_copy($copydata); 710 711 $courseid = $this->course->id; 712 713 // We are expecting trace output during this test. 714 $this->expectOutputRegex("/$courseid/"); 715 716 // Execute adhoc task. 717 $now = time(); 718 $task = \core\task\manager::get_next_adhoc_task($now); 719 $this->assertInstanceOf('\\core\\task\\asynchronous_copy_task', $task); 720 $task->execute(); 721 \core\task\manager::adhoc_task_complete($task); 722 723 $postrestorerec = $DB->get_record('backup_controllers', array('backupid' => $copyids['restoreid'])); 724 725 // Check the restored course itself. 726 $coursecontext = \context_course::instance($postrestorerec->itemid); 727 $users = get_enrolled_users($coursecontext); 728 729 $modinfo = get_fast_modinfo($postrestorerec->itemid); 730 $forums = $modinfo->get_instances_of('forum'); 731 $forum = reset($forums); 732 $discussions = forum_get_discussions($forum); 733 $course = $modinfo->get_course(); 734 735 $this->assertEquals($formdata->startdate, $course->startdate); 736 $this->assertEquals($formdata->enddate, $course->enddate); 737 $this->assertEquals('copy course', $course->fullname); 738 $this->assertEquals('copy course short', $course->shortname); 739 $this->assertEquals(0, $course->visible); 740 $this->assertEquals(123, $course->idnumber); 741 742 foreach ($modinfo->get_cms() as $cm) { 743 $this->assertContains($cm->get_formatted_name(), $this->activitynames); 744 } 745 746 // Should be no discussions as the user that made them wasn't included. 747 $this->assertEquals(2, count($discussions)); 748 749 // There should only be two users in the new course. The copier and one student. 750 $this->assertEquals(2, count($users)); 751 $this->assertEquals($this->courseusers[2], $users[$this->courseusers[2]]->id); 752 $this->assertEquals($this->courseusers[0], $users[$this->courseusers[0]]->id); 753 } 754 755 /** 756 * Test course copy, not including any users (or data). 757 */ 758 public function test_course_copy_no_data() { 759 global $DB; 760 761 // Mock up the form data. 762 $formdata = new \stdClass; 763 $formdata->courseid = $this->course->id; 764 $formdata->fullname = 'copy course'; 765 $formdata->shortname = 'copy course short'; 766 $formdata->category = 1; 767 $formdata->visible = 0; 768 $formdata->startdate = 1582376400; 769 $formdata->enddate = 1582386400; 770 $formdata->idnumber = 123; 771 $formdata->userdata = 0; 772 $formdata->role_1 = 1; 773 $formdata->role_3 = 3; 774 $formdata->role_5 = 5; 775 776 // Create the course copy records and associated ad-hoc task. 777 $copydata = \copy_helper::process_formdata($formdata); 778 $copyids = \copy_helper::create_copy($copydata); 779 780 $courseid = $this->course->id; 781 782 // We are expecting trace output during this test. 783 $this->expectOutputRegex("/$courseid/"); 784 785 // Execute adhoc task. 786 $now = time(); 787 $task = \core\task\manager::get_next_adhoc_task($now); 788 $this->assertInstanceOf('\\core\\task\\asynchronous_copy_task', $task); 789 $task->execute(); 790 \core\task\manager::adhoc_task_complete($task); 791 792 $postrestorerec = $DB->get_record('backup_controllers', array('backupid' => $copyids['restoreid'])); 793 794 // Check the restored course itself. 795 $coursecontext = \context_course::instance($postrestorerec->itemid); 796 $users = get_enrolled_users($coursecontext); 797 798 get_fast_modinfo($postrestorerec->itemid, 0, true); 799 $modinfo = get_fast_modinfo($postrestorerec->itemid); 800 $forums = $modinfo->get_instances_of('forum'); 801 $forum = reset($forums); 802 $discussions = forum_get_discussions($forum); 803 $course = $modinfo->get_course(); 804 805 $this->assertEquals($formdata->startdate, $course->startdate); 806 $this->assertEquals($formdata->enddate, $course->enddate); 807 $this->assertEquals('copy course', $course->fullname); 808 $this->assertEquals('copy course short', $course->shortname); 809 $this->assertEquals(0, $course->visible); 810 $this->assertEquals(123, $course->idnumber); 811 812 foreach ($modinfo->get_cms() as $cm) { 813 $this->assertContains($cm->get_formatted_name(), $this->activitynames); 814 } 815 816 // Should be no discussions as the user data wasn't included. 817 $this->assertEquals(0, count($discussions)); 818 819 // There should only be all users in the new course. 820 $this->assertEquals(count($this->courseusers), count($users)); 821 } 822 823 /** 824 * Test instantiation with incomplete formdata. 825 */ 826 public function test_malformed_instantiation() { 827 // Mock up the form data, missing things so we get an exception. 828 $formdata = new \stdClass; 829 $formdata->courseid = $this->course->id; 830 $formdata->fullname = 'copy course'; 831 $formdata->shortname = 'copy course short'; 832 $formdata->category = 1; 833 834 // Expect and exception as form data is incomplete. 835 $this->expectException(\moodle_exception::class); 836 $copydata = \copy_helper::process_formdata($formdata); 837 \copy_helper::create_copy($copydata); 838 } 839 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body