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