[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 /** 18 * Tests for Moodle 2 format backup operation. 19 * 20 * @package core_backup 21 * @copyright 2014 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 global $CFG; 28 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 29 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 30 require_once($CFG->libdir . '/completionlib.php'); 31 32 /** 33 * Tests for Moodle 2 format backup operation. 34 * 35 * @package core_backup 36 * @copyright 2014 The Open University 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class core_backup_moodle2_testcase extends advanced_testcase { 40 41 /** 42 * Tests the availability field on modules and sections is correctly 43 * backed up and restored. 44 */ 45 public function test_backup_availability() { 46 global $DB, $CFG; 47 48 $this->resetAfterTest(true); 49 $this->setAdminUser(); 50 $CFG->enableavailability = true; 51 $CFG->enablecompletion = true; 52 53 // Create a course with some availability data set. 54 $generator = $this->getDataGenerator(); 55 $course = $generator->create_course( 56 array('format' => 'topics', 'numsections' => 3, 57 'enablecompletion' => COMPLETION_ENABLED), 58 array('createsections' => true)); 59 $forum = $generator->create_module('forum', array( 60 'course' => $course->id)); 61 $forum2 = $generator->create_module('forum', array( 62 'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); 63 64 // We need a grade, easiest is to add an assignment. 65 $assignrow = $generator->create_module('assign', array( 66 'course' => $course->id)); 67 $assign = new assign(context_module::instance($assignrow->cmid), false, false); 68 $item = $assign->get_grade_item(); 69 70 // Make a test grouping as well. 71 $grouping = $generator->create_grouping(array('courseid' => $course->id, 72 'name' => 'Grouping!')); 73 74 $availability = '{"op":"|","show":false,"c":[' . 75 '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' . 76 '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' . 77 '{"type":"grouping","id":' . $grouping->id . '}' . 78 ']}'; 79 $DB->set_field('course_modules', 'availability', $availability, array( 80 'id' => $forum->cmid)); 81 $DB->set_field('course_sections', 'availability', $availability, array( 82 'course' => $course->id, 'section' => 1)); 83 84 // Backup and restore it. 85 $newcourseid = $this->backup_and_restore($course); 86 87 // Check settings in new course. 88 $modinfo = get_fast_modinfo($newcourseid); 89 $forums = array_values($modinfo->get_instances_of('forum')); 90 $assigns = array_values($modinfo->get_instances_of('assign')); 91 $newassign = new assign(context_module::instance($assigns[0]->id), false, false); 92 $newitem = $newassign->get_grade_item(); 93 $newgroupingid = $DB->get_field('groupings', 'id', array('courseid' => $newcourseid)); 94 95 // Expected availability should have new ID for the forum, grade, and grouping. 96 $newavailability = str_replace( 97 '"grouping","id":' . $grouping->id, 98 '"grouping","id":' . $newgroupingid, 99 str_replace( 100 '"grade","id":' . $item->id, 101 '"grade","id":' . $newitem->id, 102 str_replace( 103 '"cm":' . $forum2->cmid, 104 '"cm":' . $forums[1]->id, 105 $availability))); 106 107 $this->assertEquals($newavailability, $forums[0]->availability); 108 $this->assertNull($forums[1]->availability); 109 $this->assertEquals($newavailability, $modinfo->get_section_info(1, MUST_EXIST)->availability); 110 $this->assertNull($modinfo->get_section_info(2, MUST_EXIST)->availability); 111 } 112 113 /** 114 * The availability data format was changed in Moodle 2.7. This test 115 * ensures that a Moodle 2.6 backup with this data can still be correctly 116 * restored. 117 */ 118 public function test_restore_legacy_availability() { 119 global $DB, $USER, $CFG; 120 require_once($CFG->dirroot . '/grade/querylib.php'); 121 require_once($CFG->libdir . '/completionlib.php'); 122 123 $this->resetAfterTest(true); 124 $this->setAdminUser(); 125 $CFG->enableavailability = true; 126 $CFG->enablecompletion = true; 127 128 // Extract backup file. 129 $backupid = 'abc'; 130 $backuppath = $CFG->tempdir . '/backup/' . $backupid; 131 check_dir_exists($backuppath); 132 get_file_packer('application/vnd.moodle.backup')->extract_to_pathname( 133 __DIR__ . '/fixtures/availability_26_format.mbz', $backuppath); 134 135 // Do restore to new course with default settings. 136 $generator = $this->getDataGenerator(); 137 $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}"); 138 $newcourseid = restore_dbops::create_new_course( 139 'Test fullname', 'Test shortname', $categoryid); 140 $rc = new restore_controller($backupid, $newcourseid, 141 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 142 backup::TARGET_NEW_COURSE); 143 $thrown = null; 144 try { 145 $this->assertTrue($rc->execute_precheck()); 146 $rc->execute_plan(); 147 $rc->destroy(); 148 } catch (Exception $e) { 149 $thrown = $e; 150 // Because of the PHPUnit exception behaviour in this situation, we 151 // will not see this message unless it is explicitly echoed (just 152 // using it in a fail() call or similar will not work). 153 echo "\n\nEXCEPTION: " . $thrown->getMessage() . '[' . 154 $thrown->getFile() . ':' . $thrown->getLine(). "]\n\n"; 155 } 156 157 $this->assertNull($thrown); 158 159 // Get information about the resulting course and check that it is set 160 // up correctly. 161 $modinfo = get_fast_modinfo($newcourseid); 162 $pages = array_values($modinfo->get_instances_of('page')); 163 $forums = array_values($modinfo->get_instances_of('forum')); 164 $quizzes = array_values($modinfo->get_instances_of('quiz')); 165 $grouping = $DB->get_record('groupings', array('courseid' => $newcourseid)); 166 167 // FROM date. 168 $this->assertEquals( 169 '{"op":"&","showc":[true],"c":[{"type":"date","d":">=","t":1893456000}]}', 170 $pages[1]->availability); 171 // UNTIL date. 172 $this->assertEquals( 173 '{"op":"&","showc":[false],"c":[{"type":"date","d":"<","t":1393977600}]}', 174 $pages[2]->availability); 175 // FROM and UNTIL. 176 $this->assertEquals( 177 '{"op":"&","showc":[true,false],"c":[' . 178 '{"type":"date","d":">=","t":1449705600},' . 179 '{"type":"date","d":"<","t":1893456000}' . 180 ']}', 181 $pages[3]->availability); 182 // Grade >= 75%. 183 $grades = array_values(grade_get_grade_items_for_activity($quizzes[0], true)); 184 $gradeid = $grades[0]->id; 185 $coursegrade = grade_item::fetch_course_item($newcourseid); 186 $this->assertEquals( 187 '{"op":"&","showc":[true],"c":[{"type":"grade","id":' . $gradeid . ',"min":75}]}', 188 $pages[4]->availability); 189 // Grade < 25%. 190 $this->assertEquals( 191 '{"op":"&","showc":[true],"c":[{"type":"grade","id":' . $gradeid . ',"max":25}]}', 192 $pages[5]->availability); 193 // Grade 90-100%. 194 $this->assertEquals( 195 '{"op":"&","showc":[true],"c":[{"type":"grade","id":' . $gradeid . ',"min":90,"max":100}]}', 196 $pages[6]->availability); 197 // Email contains frog. 198 $this->assertEquals( 199 '{"op":"&","showc":[true],"c":[{"type":"profile","op":"contains","sf":"email","v":"frog"}]}', 200 $pages[7]->availability); 201 // Page marked complete.. 202 $this->assertEquals( 203 '{"op":"&","showc":[true],"c":[{"type":"completion","cm":' . $pages[0]->id . 204 ',"e":' . COMPLETION_COMPLETE . '}]}', 205 $pages[8]->availability); 206 // Quiz complete but failed. 207 $this->assertEquals( 208 '{"op":"&","showc":[true],"c":[{"type":"completion","cm":' . $quizzes[0]->id . 209 ',"e":' . COMPLETION_COMPLETE_FAIL . '}]}', 210 $pages[9]->availability); 211 // Quiz complete and succeeded. 212 $this->assertEquals( 213 '{"op":"&","showc":[true],"c":[{"type":"completion","cm":' . $quizzes[0]->id . 214 ',"e":' . COMPLETION_COMPLETE_PASS. '}]}', 215 $pages[10]->availability); 216 // Quiz not complete. 217 $this->assertEquals( 218 '{"op":"&","showc":[true],"c":[{"type":"completion","cm":' . $quizzes[0]->id . 219 ',"e":' . COMPLETION_INCOMPLETE . '}]}', 220 $pages[11]->availability); 221 // Grouping. 222 $this->assertEquals( 223 '{"op":"&","showc":[false],"c":[{"type":"grouping","id":' . $grouping->id . '}]}', 224 $pages[12]->availability); 225 226 // All the options. 227 $this->assertEquals('{"op":"&",' . 228 '"showc":[false,true,false,true,true,true,true,true,true],' . 229 '"c":[' . 230 '{"type":"grouping","id":' . $grouping->id . '},' . 231 '{"type":"date","d":">=","t":1488585600},' . 232 '{"type":"date","d":"<","t":1709510400},' . 233 '{"type":"profile","op":"contains","sf":"email","v":"@"},' . 234 '{"type":"profile","op":"contains","sf":"city","v":"Frogtown"},' . 235 '{"type":"grade","id":' . $gradeid . ',"min":30,"max":35},' . 236 '{"type":"grade","id":' . $coursegrade->id . ',"min":5,"max":10},' . 237 '{"type":"completion","cm":' . $pages[0]->id . ',"e":' . COMPLETION_COMPLETE . '},' . 238 '{"type":"completion","cm":' . $quizzes[0]->id .',"e":' . COMPLETION_INCOMPLETE . '}' . 239 ']}', $pages[13]->availability); 240 241 // Group members only forum. 242 $this->assertEquals( 243 '{"op":"&","showc":[false],"c":[{"type":"group"}]}', 244 $forums[0]->availability); 245 246 // Section with lots of conditions. 247 $this->assertEquals( 248 '{"op":"&","showc":[false,false,false,false],"c":[' . 249 '{"type":"date","d":">=","t":1417737600},' . 250 '{"type":"profile","op":"contains","sf":"email","v":"@"},' . 251 '{"type":"grade","id":' . $gradeid . ',"min":20},' . 252 '{"type":"completion","cm":' . $pages[0]->id . ',"e":' . COMPLETION_COMPLETE . '}]}', 253 $modinfo->get_section_info(3)->availability); 254 255 // Section with grouping. 256 $this->assertEquals( 257 '{"op":"&","showc":[false],"c":[{"type":"grouping","id":' . $grouping->id . '}]}', 258 $modinfo->get_section_info(4)->availability); 259 } 260 261 /** 262 * Tests the backup and restore of single activity to same course (duplicate) 263 * when it contains availability conditions that depend on other items in 264 * course. 265 */ 266 public function test_duplicate_availability() { 267 global $DB, $CFG; 268 269 $this->resetAfterTest(true); 270 $this->setAdminUser(); 271 $CFG->enableavailability = true; 272 $CFG->enablecompletion = true; 273 274 // Create a course with completion enabled and 2 forums. 275 $generator = $this->getDataGenerator(); 276 $course = $generator->create_course( 277 array('format' => 'topics', 'enablecompletion' => COMPLETION_ENABLED)); 278 $forum = $generator->create_module('forum', array( 279 'course' => $course->id)); 280 $forum2 = $generator->create_module('forum', array( 281 'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); 282 283 // We need a grade, easiest is to add an assignment. 284 $assignrow = $generator->create_module('assign', array( 285 'course' => $course->id)); 286 $assign = new assign(context_module::instance($assignrow->cmid), false, false); 287 $item = $assign->get_grade_item(); 288 289 // Make a test group and grouping as well. 290 $group = $generator->create_group(array('courseid' => $course->id, 291 'name' => 'Group!')); 292 $grouping = $generator->create_grouping(array('courseid' => $course->id, 293 'name' => 'Grouping!')); 294 295 // Set the forum to have availability conditions on all those things, 296 // plus some that don't exist or are special values. 297 $availability = '{"op":"|","show":false,"c":[' . 298 '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' . 299 '{"type":"completion","cm":99999999,"e":1},' . 300 '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' . 301 '{"type":"grade","id":99999998,"min":4,"max":94},' . 302 '{"type":"grouping","id":' . $grouping->id . '},' . 303 '{"type":"grouping","id":99999997},' . 304 '{"type":"group","id":' . $group->id . '},' . 305 '{"type":"group"},' . 306 '{"type":"group","id":99999996}' . 307 ']}'; 308 $DB->set_field('course_modules', 'availability', $availability, array( 309 'id' => $forum->cmid)); 310 311 // Duplicate it. 312 $newcmid = $this->duplicate($course, $forum->cmid); 313 314 // For those which still exist on the course we expect it to keep using 315 // the real ID. For those which do not exist on the course any more 316 // (e.g. simulating backup/restore of single activity between 2 courses) 317 // we expect the IDs to be replaced with marker value: 0 for cmid 318 // and grade, -1 for group/grouping. 319 $expected = str_replace( 320 array('99999999', '99999998', '99999997', '99999996'), 321 array(0, 0, -1, -1), 322 $availability); 323 324 // Check settings in new activity. 325 $actual = $DB->get_field('course_modules', 'availability', array('id' => $newcmid)); 326 $this->assertEquals($expected, $actual); 327 } 328 329 /** 330 * When restoring a course, you can change the start date, which shifts other 331 * dates. This test checks that certain dates are correctly modified. 332 */ 333 public function test_restore_dates() { 334 global $DB, $CFG; 335 336 $this->resetAfterTest(true); 337 $this->setAdminUser(); 338 $CFG->enableavailability = true; 339 340 // Create a course with specific start date. 341 $generator = $this->getDataGenerator(); 342 $course = $generator->create_course(array( 343 'startdate' => strtotime('1 Jan 2014 00:00 GMT'))); 344 345 // Add a forum with conditional availability date restriction, including 346 // one of them nested inside a tree. 347 $availability = '{"op":"&","showc":[true,true],"c":[' . 348 '{"op":"&","c":[{"type":"date","d":">=","t":DATE1}]},' . 349 '{"type":"date","d":"<","t":DATE2}]}'; 350 $before = str_replace( 351 array('DATE1', 'DATE2'), 352 array(strtotime('1 Feb 2014 00:00 GMT'), strtotime('10 Feb 2014 00:00 GMT')), 353 $availability); 354 $forum = $generator->create_module('forum', array('course' => $course->id, 355 'availability' => $before)); 356 357 // Add an assign with defined start date. 358 $assign = $generator->create_module('assign', array('course' => $course->id, 359 'allowsubmissionsfromdate' => strtotime('7 Jan 2014 16:00 GMT'))); 360 361 // Do backup and restore. 362 $newcourseid = $this->backup_and_restore($course, strtotime('3 Jan 2015 00:00 GMT')); 363 364 $modinfo = get_fast_modinfo($newcourseid); 365 366 // Check forum dates are modified by the same amount as the course start. 367 $newforums = $modinfo->get_instances_of('forum'); 368 $newforum = reset($newforums); 369 $after = str_replace( 370 array('DATE1', 'DATE2'), 371 array(strtotime('3 Feb 2015 00:00 GMT'), strtotime('12 Feb 2015 00:00 GMT')), 372 $availability); 373 $this->assertEquals($after, $newforum->availability); 374 375 // Check assign date. 376 $newassigns = $modinfo->get_instances_of('assign'); 377 $newassign = reset($newassigns); 378 $this->assertEquals(strtotime('9 Jan 2015 16:00 GMT'), $DB->get_field( 379 'assign', 'allowsubmissionsfromdate', array('id' => $newassign->instance))); 380 } 381 382 /** 383 * Test front page backup/restore and duplicate activities 384 * @return void 385 */ 386 public function test_restore_frontpage() { 387 global $DB, $CFG, $USER; 388 389 $this->resetAfterTest(true); 390 $this->setAdminUser(); 391 $generator = $this->getDataGenerator(); 392 393 $frontpage = $DB->get_record('course', array('id' => SITEID)); 394 $forum = $generator->create_module('forum', array('course' => $frontpage->id)); 395 396 // Activities can be duplicated. 397 $this->duplicate($frontpage, $forum->cmid); 398 399 $modinfo = get_fast_modinfo($frontpage); 400 $this->assertEquals(2, count($modinfo->get_instances_of('forum'))); 401 402 // Front page backup. 403 $frontpagebc = new backup_controller(backup::TYPE_1COURSE, $frontpage->id, 404 backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, 405 $USER->id); 406 $frontpagebackupid = $frontpagebc->get_backupid(); 407 $frontpagebc->execute_plan(); 408 $frontpagebc->destroy(); 409 410 $course = $generator->create_course(); 411 $newcourseid = restore_dbops::create_new_course( 412 $course->fullname . ' 2', $course->shortname . '_2', $course->category); 413 414 // Other course backup. 415 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, 416 backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, 417 $USER->id); 418 $otherbackupid = $bc->get_backupid(); 419 $bc->execute_plan(); 420 $bc->destroy(); 421 422 // We can only restore a front page over the front page. 423 $rc = new restore_controller($frontpagebackupid, $course->id, 424 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 425 backup::TARGET_CURRENT_ADDING); 426 $this->assertFalse($rc->execute_precheck()); 427 $rc->destroy(); 428 429 $rc = new restore_controller($frontpagebackupid, $newcourseid, 430 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 431 backup::TARGET_NEW_COURSE); 432 $this->assertFalse($rc->execute_precheck()); 433 $rc->destroy(); 434 435 $rc = new restore_controller($frontpagebackupid, $frontpage->id, 436 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 437 backup::TARGET_CURRENT_ADDING); 438 $this->assertTrue($rc->execute_precheck()); 439 $rc->execute_plan(); 440 $rc->destroy(); 441 442 // We can't restore a non-front page course on the front page course. 443 $rc = new restore_controller($otherbackupid, $frontpage->id, 444 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 445 backup::TARGET_CURRENT_ADDING); 446 $this->assertFalse($rc->execute_precheck()); 447 $rc->destroy(); 448 449 $rc = new restore_controller($otherbackupid, $newcourseid, 450 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 451 backup::TARGET_NEW_COURSE); 452 $this->assertTrue($rc->execute_precheck()); 453 $rc->execute_plan(); 454 $rc->destroy(); 455 } 456 457 /** 458 * Backs a course up and restores it. 459 * 460 * @param stdClass $course Course object to backup 461 * @param int $newdate If non-zero, specifies custom date for new course 462 * @return int ID of newly restored course 463 */ 464 protected function backup_and_restore($course, $newdate = 0) { 465 global $USER, $CFG; 466 467 // Turn off file logging, otherwise it can't delete the file (Windows). 468 $CFG->backup_file_logger_level = backup::LOG_NONE; 469 470 // Do backup with default settings. MODE_IMPORT means it will just 471 // create the directory and not zip it. 472 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, 473 backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, 474 $USER->id); 475 $backupid = $bc->get_backupid(); 476 $bc->execute_plan(); 477 $bc->destroy(); 478 479 // Do restore to new course with default settings. 480 $newcourseid = restore_dbops::create_new_course( 481 $course->fullname, $course->shortname . '_2', $course->category); 482 $rc = new restore_controller($backupid, $newcourseid, 483 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, 484 backup::TARGET_NEW_COURSE); 485 if ($newdate) { 486 $rc->get_plan()->get_setting('course_startdate')->set_value($newdate); 487 } 488 $this->assertTrue($rc->execute_precheck()); 489 $rc->execute_plan(); 490 $rc->destroy(); 491 492 return $newcourseid; 493 } 494 495 /** 496 * Duplicates a single activity within a course. 497 * 498 * This is based on the code from course/modduplicate.php, but reduced for 499 * simplicity. 500 * 501 * @param stdClass $course Course object 502 * @param int $cmid Activity to duplicate 503 * @return int ID of new activity 504 */ 505 protected function duplicate($course, $cmid) { 506 global $USER; 507 508 // Do backup. 509 $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cmid, backup::FORMAT_MOODLE, 510 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); 511 $backupid = $bc->get_backupid(); 512 $bc->execute_plan(); 513 $bc->destroy(); 514 515 // Do restore. 516 $rc = new restore_controller($backupid, $course->id, 517 backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING); 518 $this->assertTrue($rc->execute_precheck()); 519 $rc->execute_plan(); 520 521 // Find cmid. 522 $tasks = $rc->get_plan()->get_tasks(); 523 $cmcontext = context_module::instance($cmid); 524 $newcmid = 0; 525 foreach ($tasks as $task) { 526 if (is_subclass_of($task, 'restore_activity_task')) { 527 if ($task->get_old_contextid() == $cmcontext->id) { 528 $newcmid = $task->get_moduleid(); 529 break; 530 } 531 } 532 } 533 $rc->destroy(); 534 if (!$newcmid) { 535 throw new coding_exception('Unexpected: failure to find restored cmid'); 536 } 537 return $newcmid; 538 } 539 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |