Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 38 and 311] [Versions 39 and 311]
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_course; 18 19 use advanced_testcase; 20 use backup_controller; 21 use backup; 22 use blog_entry; 23 use cache; 24 use calendar_event; 25 use coding_exception; 26 use comment; 27 use completion_criteria_date; 28 use completion_completion; 29 use context_course; 30 use context_module; 31 use context_system; 32 use context_coursecat; 33 use core_completion_external; 34 use core_external; 35 use core_tag_index_builder; 36 use core_tag_tag; 37 use course_capability_assignment; 38 use course_request; 39 use core_course_category; 40 use enrol_imsenterprise\imsenterprise_test; 41 use external_api; 42 use grade_item; 43 use grading_manager; 44 use moodle_exception; 45 use moodle_url; 46 use phpunit_util; 47 use rating_manager; 48 use restore_controller; 49 use stdClass; 50 use testing_data_generator; 51 52 defined('MOODLE_INTERNAL') or die(); 53 54 // Require library globally because it's constants are used within dataProvider methods, executed before setUpBeforeClass. 55 global $CFG; 56 require_once($CFG->dirroot . '/course/lib.php'); 57 require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php'); 58 require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php'); 59 60 /** 61 * Course related unit tests 62 * 63 * @package core_course 64 * @category test 65 * @copyright 2012 Petr Skoda {@link http://skodak.org} 66 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 67 */ 68 class courselib_test extends advanced_testcase { 69 70 /** 71 * Set forum specific test values for calling create_module(). 72 * 73 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference. 74 */ 75 private function forum_create_set_values(&$moduleinfo) { 76 // Completion specific to forum - optional. 77 $moduleinfo->completionposts = 3; 78 $moduleinfo->completiondiscussions = 1; 79 $moduleinfo->completionreplies = 2; 80 81 // Specific values to the Forum module. 82 $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE; 83 $moduleinfo->type = 'single'; 84 $moduleinfo->trackingtype = FORUM_TRACKING_FORCED; 85 $moduleinfo->maxbytes = 10240; 86 $moduleinfo->maxattachments = 2; 87 88 // Post threshold for blocking - specific to forum. 89 $moduleinfo->blockperiod = 60*60*24; 90 $moduleinfo->blockafter = 10; 91 $moduleinfo->warnafter = 5; 92 93 // Grading of whole forum settings. 94 $moduleinfo->grade_forum = 0; 95 } 96 97 /** 98 * Execute test asserts on the saved DB data by create_module($forum). 99 * 100 * @param object $moduleinfo - the specific forum values that were used to create a forum. 101 * @param object $dbmodinstance - the DB values of the created forum. 102 */ 103 private function forum_create_run_asserts($moduleinfo, $dbmodinstance) { 104 // Compare values specific to forums. 105 $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe); 106 $this->assertEquals($moduleinfo->type, $dbmodinstance->type); 107 $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed); 108 $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts); 109 $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions); 110 $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies); 111 $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale); 112 $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart); 113 $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish); 114 $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype); 115 $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles); 116 $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype); 117 $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes); 118 $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments); 119 $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod); 120 $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter); 121 $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter); 122 } 123 124 /** 125 * Set assign module specific test values for calling create_module(). 126 * 127 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference. 128 */ 129 private function assign_create_set_values(&$moduleinfo) { 130 // Specific values to the Assign module. 131 $moduleinfo->alwaysshowdescription = true; 132 $moduleinfo->submissiondrafts = true; 133 $moduleinfo->requiresubmissionstatement = true; 134 $moduleinfo->sendnotifications = true; 135 $moduleinfo->sendlatenotifications = true; 136 $moduleinfo->duedate = time() + (7 * 24 * 3600); 137 $moduleinfo->cutoffdate = time() + (7 * 24 * 3600); 138 $moduleinfo->gradingduedate = time() + (7 * 24 * 3600); 139 $moduleinfo->allowsubmissionsfromdate = time(); 140 $moduleinfo->teamsubmission = true; 141 $moduleinfo->requireallteammemberssubmit = true; 142 $moduleinfo->teamsubmissiongroupingid = true; 143 $moduleinfo->blindmarking = true; 144 $moduleinfo->markingworkflow = true; 145 $moduleinfo->markingallocation = true; 146 $moduleinfo->assignsubmission_onlinetext_enabled = true; 147 $moduleinfo->assignsubmission_file_enabled = true; 148 $moduleinfo->assignsubmission_file_maxfiles = 1; 149 $moduleinfo->assignsubmission_file_maxsizebytes = 1000000; 150 $moduleinfo->assignsubmission_comments_enabled = true; 151 $moduleinfo->assignfeedback_comments_enabled = true; 152 $moduleinfo->assignfeedback_offline_enabled = true; 153 $moduleinfo->assignfeedback_file_enabled = true; 154 155 // Advanced grading. 156 $gradingmethods = grading_manager::available_methods(); 157 $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods)); 158 } 159 160 /** 161 * Execute test asserts on the saved DB data by create_module($assign). 162 * 163 * @param object $moduleinfo - the specific assign module values that were used to create an assign module. 164 * @param object $dbmodinstance - the DB values of the created assign module. 165 */ 166 private function assign_create_run_asserts($moduleinfo, $dbmodinstance) { 167 global $DB; 168 169 $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription); 170 $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts); 171 $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement); 172 $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications); 173 $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate); 174 $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate); 175 $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate); 176 $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission); 177 $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit); 178 $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid); 179 $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking); 180 $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow); 181 $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation); 182 // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries. 183 184 // Advanced grading. 185 $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id); 186 $contextmodule = context_module::instance($cm->id); 187 $advancedgradingmethod = $DB->get_record('grading_areas', 188 array('contextid' => $contextmodule->id, 189 'activemethod' => $moduleinfo->advancedgradingmethod_submissions)); 190 $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod); 191 } 192 193 /** 194 * Run some asserts test for a specific module for the function create_module(). 195 * 196 * The function has been created (and is called) for $this->test_create_module(). 197 * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts. 198 * So if you want, you can overwrite the default values/asserts in the respective functions. 199 * @param string $modulename Name of the module ('forum', 'assign', 'book'...). 200 */ 201 private function create_specific_module_test($modulename) { 202 global $DB, $CFG; 203 204 $this->resetAfterTest(true); 205 206 $this->setAdminUser(); 207 208 // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard. 209 require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php'); 210 211 // Enable avaibility. 212 // If not enabled all conditional fields will be ignored. 213 set_config('enableavailability', 1); 214 215 // Enable course completion. 216 // If not enabled all completion settings will be ignored. 217 set_config('enablecompletion', COMPLETION_ENABLED); 218 219 // Enable forum RSS feeds. 220 set_config('enablerssfeeds', 1); 221 set_config('forum_enablerssfeeds', 1); 222 223 $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED), 224 array('createsections'=>true)); 225 226 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 227 228 // Create assign module instance for test. 229 $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); 230 $params['course'] = $course->id; 231 $instance = $generator->create_instance($params); 232 $assigncm = get_coursemodule_from_instance('assign', $instance->id); 233 234 // Module test values. 235 $moduleinfo = new stdClass(); 236 237 // Always mandatory generic values to any module. 238 $moduleinfo->modulename = $modulename; 239 $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database. 240 $moduleinfo->course = $course->id; 241 $moduleinfo->groupingid = $grouping->id; 242 $moduleinfo->visible = true; 243 $moduleinfo->visibleoncoursepage = true; 244 245 // Sometimes optional generic values for some modules. 246 $moduleinfo->name = 'My test module'; 247 $moduleinfo->showdescription = 1; // standard boolean 248 require_once($CFG->libdir . '/gradelib.php'); 249 $gradecats = grade_get_categories_menu($moduleinfo->course, false); 250 $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats 251 $moduleinfo->gradecat = $gradecatid; 252 $moduleinfo->groupmode = VISIBLEGROUPS; 253 $moduleinfo->cmidnumber = 'idnumber_XXX'; 254 255 // Completion common to all module. 256 $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC; 257 $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED; 258 $moduleinfo->completiongradeitemnumber = 1; 259 $moduleinfo->completionexpected = time() + (7 * 24 * 3600); 260 261 // Conditional activity. 262 $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' . 263 '{"type":"date","d":">=","t":' . time() . '},' . 264 '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' . 265 ']}'; 266 $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself. 267 $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80)); 268 $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@')); 269 $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none 270 271 // Grading and Advanced grading. 272 require_once($CFG->dirroot . '/rating/lib.php'); 273 $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE; 274 $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number. 275 $moduleinfo->assesstimestart = time(); 276 $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600); 277 278 // RSS. 279 $moduleinfo->rsstype = 2; 280 $moduleinfo->rssarticles = 10; 281 282 // Optional intro editor (depends of module). 283 $draftid_editor = 0; 284 file_prepare_draft_area($draftid_editor, null, null, null, null); 285 $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor); 286 287 // Following is the advanced grading method area called 'submissions' for the 'assign' module. 288 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 289 $moduleinfo->grade = 100; 290 } 291 292 // Plagiarism form values. 293 // No plagiarism plugin installed by default. Use this space to make your own test. 294 295 // Values specific to the module. 296 $modulesetvalues = $modulename.'_create_set_values'; 297 $this->$modulesetvalues($moduleinfo); 298 299 // Create the module. 300 $result = create_module($moduleinfo); 301 302 // Retrieve the module info. 303 $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance)); 304 $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance); 305 // We passed the course section number to create_courses but $dbcm contain the section id. 306 // We need to retrieve the db course section number. 307 $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section)); 308 // Retrieve the grade item. 309 $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course, 310 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename)); 311 312 // Compare the values common to all module instances. 313 $this->assertEquals($moduleinfo->modulename, $dbcm->modname); 314 $this->assertEquals($moduleinfo->section, $section->section); 315 $this->assertEquals($moduleinfo->course, $dbcm->course); 316 $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid); 317 $this->assertEquals($moduleinfo->visible, $dbcm->visible); 318 $this->assertEquals($moduleinfo->completion, $dbcm->completion); 319 $this->assertEquals($moduleinfo->completionview, $dbcm->completionview); 320 $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber); 321 $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected); 322 $this->assertEquals($moduleinfo->availability, $dbcm->availability); 323 $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription); 324 $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode); 325 $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber); 326 $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid); 327 328 // Optional grade testing. 329 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 330 $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade); 331 } 332 333 // Some optional (but quite common) to some module. 334 $this->assertEquals($moduleinfo->name, $dbmodinstance->name); 335 $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro); 336 $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat); 337 338 // Test specific to the module. 339 $modulerunasserts = $modulename.'_create_run_asserts'; 340 $this->$modulerunasserts($moduleinfo, $dbmodinstance); 341 return $moduleinfo; 342 } 343 344 /** 345 * Create module associated blog and tags. 346 * 347 * @param object $course Course. 348 * @param object $modulecontext The context of the module. 349 */ 350 private function create_module_asscociated_blog($course, $modulecontext) { 351 global $DB, $CFG; 352 353 // Create default group. 354 $group = new stdClass(); 355 $group->courseid = $course->id; 356 $group->name = 'Group'; 357 $group->id = $DB->insert_record('groups', $group); 358 359 // Create default user. 360 $user = $this->getDataGenerator()->create_user(array( 361 'username' => 'testuser', 362 'firstname' => 'Firsname', 363 'lastname' => 'Lastname' 364 )); 365 366 // Create default post. 367 $post = new stdClass(); 368 $post->userid = $user->id; 369 $post->groupid = $group->id; 370 $post->content = 'test post content text'; 371 $post->module = 'blog'; 372 $post->id = $DB->insert_record('post', $post); 373 374 // Create default tag. 375 $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id, 376 'rawname' => 'Testtagname', 'isstandard' => 1)); 377 // Apply the tag to the blog. 378 $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user', 379 'component' => 'core', 'itemid' => $post->id, 'ordering' => 0)); 380 381 require_once($CFG->dirroot . '/blog/locallib.php'); 382 $blog = new blog_entry($post->id); 383 $blog->add_association($modulecontext->id); 384 385 return $blog; 386 } 387 388 /** 389 * Test create_module() for multiple modules defined in the $modules array (first declaration of the function). 390 */ 391 public function test_create_module() { 392 // Add the module name you want to test here. 393 // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts(). 394 $modules = array('forum', 'assign'); 395 // Run all tests. 396 foreach ($modules as $modulename) { 397 $this->create_specific_module_test($modulename); 398 } 399 } 400 401 /** 402 * Test update_module() for multiple modules defined in the $modules array (first declaration of the function). 403 */ 404 public function test_update_module() { 405 // Add the module name you want to test here. 406 // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts(). 407 $modules = array('forum'); 408 // Run all tests. 409 foreach ($modules as $modulename) { 410 $this->update_specific_module_test($modulename); 411 } 412 } 413 414 /** 415 * Set forum specific test values for calling update_module(). 416 * 417 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference. 418 */ 419 private function forum_update_set_values(&$moduleinfo) { 420 // Completion specific to forum - optional. 421 $moduleinfo->completionposts = 3; 422 $moduleinfo->completiondiscussions = 1; 423 $moduleinfo->completionreplies = 2; 424 425 // Specific values to the Forum module. 426 $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE; 427 $moduleinfo->type = 'single'; 428 $moduleinfo->trackingtype = FORUM_TRACKING_FORCED; 429 $moduleinfo->maxbytes = 10240; 430 $moduleinfo->maxattachments = 2; 431 432 // Post threshold for blocking - specific to forum. 433 $moduleinfo->blockperiod = 60*60*24; 434 $moduleinfo->blockafter = 10; 435 $moduleinfo->warnafter = 5; 436 437 // Grading of whole forum settings. 438 $moduleinfo->grade_forum = 0; 439 } 440 441 /** 442 * Execute test asserts on the saved DB data by update_module($forum). 443 * 444 * @param object $moduleinfo - the specific forum values that were used to update a forum. 445 * @param object $dbmodinstance - the DB values of the updated forum. 446 */ 447 private function forum_update_run_asserts($moduleinfo, $dbmodinstance) { 448 // Compare values specific to forums. 449 $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe); 450 $this->assertEquals($moduleinfo->type, $dbmodinstance->type); 451 $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed); 452 $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts); 453 $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions); 454 $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies); 455 $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale); 456 $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart); 457 $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish); 458 $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype); 459 $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles); 460 $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype); 461 $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes); 462 $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments); 463 $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod); 464 $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter); 465 $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter); 466 } 467 468 469 470 /** 471 * Test a specific type of module. 472 * 473 * @param string $modulename - the module name to test 474 */ 475 private function update_specific_module_test($modulename) { 476 global $DB, $CFG; 477 478 $this->resetAfterTest(true); 479 480 $this->setAdminUser(); 481 482 // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard. 483 require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php'); 484 485 // Enable avaibility. 486 // If not enabled all conditional fields will be ignored. 487 set_config('enableavailability', 1); 488 489 // Enable course completion. 490 // If not enabled all completion settings will be ignored. 491 set_config('enablecompletion', COMPLETION_ENABLED); 492 493 // Enable forum RSS feeds. 494 set_config('enablerssfeeds', 1); 495 set_config('forum_enablerssfeeds', 1); 496 497 $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED), 498 array('createsections'=>true)); 499 500 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 501 502 // Create assign module instance for testing gradeitem. 503 $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); 504 $params['course'] = $course->id; 505 $instance = $generator->create_instance($params); 506 $assigncm = get_coursemodule_from_instance('assign', $instance->id); 507 508 // Create the test forum to update. 509 $initvalues = new stdClass(); 510 $initvalues->introformat = FORMAT_HTML; 511 $initvalues->course = $course->id; 512 $forum = self::getDataGenerator()->create_module('forum', $initvalues); 513 514 // Retrieve course module. 515 $cm = get_coursemodule_from_instance('forum', $forum->id); 516 517 // Module test values. 518 $moduleinfo = new stdClass(); 519 520 // Always mandatory generic values to any module. 521 $moduleinfo->coursemodule = $cm->id; 522 $moduleinfo->modulename = $modulename; 523 $moduleinfo->course = $course->id; 524 $moduleinfo->groupingid = $grouping->id; 525 $moduleinfo->visible = true; 526 $moduleinfo->visibleoncoursepage = true; 527 528 // Sometimes optional generic values for some modules. 529 $moduleinfo->name = 'My test module'; 530 $moduleinfo->showdescription = 1; // standard boolean 531 require_once($CFG->libdir . '/gradelib.php'); 532 $gradecats = grade_get_categories_menu($moduleinfo->course, false); 533 $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats 534 $moduleinfo->gradecat = $gradecatid; 535 $moduleinfo->groupmode = VISIBLEGROUPS; 536 $moduleinfo->cmidnumber = 'idnumber_XXX'; 537 538 // Completion common to all module. 539 $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC; 540 $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED; 541 $moduleinfo->completiongradeitemnumber = 1; 542 $moduleinfo->completionexpected = time() + (7 * 24 * 3600); 543 $moduleinfo->completionunlocked = 1; 544 545 // Conditional activity. 546 $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself. 547 $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json( 548 array(\availability_date\condition::get_json('>=', time()), 549 \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)), 550 \availability_grade\condition::get_json($coursegradeitem->id, 10, 80), 551 \availability_profile\condition::get_json(false, 'email', 'contains', '@'), 552 \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&')); 553 554 // Grading and Advanced grading. 555 require_once($CFG->dirroot . '/rating/lib.php'); 556 $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE; 557 $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number. 558 $moduleinfo->assesstimestart = time(); 559 $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600); 560 561 // RSS. 562 $moduleinfo->rsstype = 2; 563 $moduleinfo->rssarticles = 10; 564 565 // Optional intro editor (depends of module). 566 $draftid_editor = 0; 567 file_prepare_draft_area($draftid_editor, null, null, null, null); 568 $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor); 569 570 // Following is the advanced grading method area called 'submissions' for the 'assign' module. 571 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 572 $moduleinfo->grade = 100; 573 } 574 // Plagiarism form values. 575 // No plagiarism plugin installed by default. Use this space to make your own test. 576 577 // Values specific to the module. 578 $modulesetvalues = $modulename.'_update_set_values'; 579 $this->$modulesetvalues($moduleinfo); 580 581 // Create the module. 582 $result = update_module($moduleinfo); 583 584 // Retrieve the module info. 585 $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance)); 586 $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance); 587 // Retrieve the grade item. 588 $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course, 589 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename)); 590 591 // Compare the values common to all module instances. 592 $this->assertEquals($moduleinfo->modulename, $dbcm->modname); 593 $this->assertEquals($moduleinfo->course, $dbcm->course); 594 $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid); 595 $this->assertEquals($moduleinfo->visible, $dbcm->visible); 596 $this->assertEquals($moduleinfo->completion, $dbcm->completion); 597 $this->assertEquals($moduleinfo->completionview, $dbcm->completionview); 598 $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber); 599 $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected); 600 $this->assertEquals($moduleinfo->availability, $dbcm->availability); 601 $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription); 602 $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode); 603 $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber); 604 $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid); 605 606 // Optional grade testing. 607 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 608 $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade); 609 } 610 611 // Some optional (but quite common) to some module. 612 $this->assertEquals($moduleinfo->name, $dbmodinstance->name); 613 $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro); 614 $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat); 615 616 // Test specific to the module. 617 $modulerunasserts = $modulename.'_update_run_asserts'; 618 $this->$modulerunasserts($moduleinfo, $dbmodinstance); 619 return $moduleinfo; 620 } 621 622 /** 623 * Data provider for course_delete module 624 * 625 * @return array An array of arrays contain test data 626 */ 627 public function provider_course_delete_module() { 628 $data = array(); 629 630 $data['assign'] = array('assign', array('duedate' => time())); 631 $data['quiz'] = array('quiz', array('duedate' => time())); 632 633 return $data; 634 } 635 636 /** 637 * Test the create_course function 638 */ 639 public function test_create_course() { 640 global $DB; 641 $this->resetAfterTest(true); 642 $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0"); 643 644 $course = new stdClass(); 645 $course->fullname = 'Apu loves Unit TÉ™sts'; 646 $course->shortname = 'Spread the lÅve'; 647 $course->idnumber = '123'; 648 $course->summary = 'Awesome!'; 649 $course->summaryformat = FORMAT_PLAIN; 650 $course->format = 'topics'; 651 $course->newsitems = 0; 652 $course->category = $defaultcategory; 653 $original = (array) $course; 654 655 $created = create_course($course); 656 $context = context_course::instance($created->id); 657 658 // Compare original and created. 659 $this->assertEquals($original, array_intersect_key((array) $created, $original)); 660 661 // Ensure default section is created. 662 $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0)); 663 $this->assertTrue($sectioncreated); 664 665 // Ensure that the shortname isn't duplicated. 666 try { 667 $created = create_course($course); 668 $this->fail('Exception expected'); 669 } catch (moodle_exception $e) { 670 $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage()); 671 } 672 673 // Ensure that the idnumber isn't duplicated. 674 $course->shortname .= '1'; 675 try { 676 $created = create_course($course); 677 $this->fail('Exception expected'); 678 } catch (moodle_exception $e) { 679 $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage()); 680 } 681 } 682 683 public function test_create_course_with_generator() { 684 global $DB; 685 $this->resetAfterTest(true); 686 $course = $this->getDataGenerator()->create_course(); 687 688 // Ensure default section is created. 689 $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0)); 690 $this->assertTrue($sectioncreated); 691 } 692 693 public function test_create_course_sections() { 694 global $DB; 695 $this->resetAfterTest(true); 696 697 $numsections = 5; 698 $course = $this->getDataGenerator()->create_course( 699 array('shortname' => 'GrowingCourse', 700 'fullname' => 'Growing Course', 701 'numsections' => $numsections), 702 array('createsections' => true)); 703 704 // Ensure all 6 (0-5) sections were created and course content cache works properly 705 $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all()); 706 $this->assertEquals(range(0, $numsections), $sectionscreated); 707 708 // this will do nothing, section already exists 709 $this->assertFalse(course_create_sections_if_missing($course, $numsections)); 710 711 // this will create new section 712 $this->assertTrue(course_create_sections_if_missing($course, $numsections + 1)); 713 714 // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly 715 $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all()); 716 $this->assertEquals(range(0, $numsections + 1), $sectionscreated); 717 } 718 719 public function test_update_course() { 720 global $DB; 721 722 $this->resetAfterTest(); 723 724 $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0'); 725 726 $course = new stdClass(); 727 $course->fullname = 'Apu loves Unit TÉ™sts'; 728 $course->shortname = 'test1'; 729 $course->idnumber = '1'; 730 $course->summary = 'Awesome!'; 731 $course->summaryformat = FORMAT_PLAIN; 732 $course->format = 'topics'; 733 $course->newsitems = 0; 734 $course->numsections = 5; 735 $course->category = $defaultcategory; 736 737 $created = create_course($course); 738 // Ensure the checks only work on idnumber/shortname that are not already ours. 739 update_course($created); 740 741 $course->shortname = 'test2'; 742 $course->idnumber = '2'; 743 744 $created2 = create_course($course); 745 746 // Test duplicate idnumber. 747 $created2->idnumber = '1'; 748 try { 749 update_course($created2); 750 $this->fail('Expected exception when trying to update a course with duplicate idnumber'); 751 } catch (moodle_exception $e) { 752 $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage()); 753 } 754 755 // Test duplicate shortname. 756 $created2->idnumber = '2'; 757 $created2->shortname = 'test1'; 758 try { 759 update_course($created2); 760 $this->fail('Expected exception when trying to update a course with a duplicate shortname'); 761 } catch (moodle_exception $e) { 762 $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage()); 763 } 764 } 765 766 public function test_update_course_section_time_modified() { 767 global $DB; 768 769 $this->resetAfterTest(); 770 771 // Create the course with sections. 772 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true)); 773 $sections = $DB->get_records('course_sections', array('course' => $course->id)); 774 775 // Get the last section's time modified value. 776 $section = array_pop($sections); 777 $oldtimemodified = $section->timemodified; 778 779 // Update the section. 780 $this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp. 781 course_update_section($course, $section, array()); 782 783 // Check that the time has changed. 784 $section = $DB->get_record('course_sections', array('id' => $section->id)); 785 $newtimemodified = $section->timemodified; 786 $this->assertGreaterThan($oldtimemodified, $newtimemodified); 787 } 788 789 /** 790 * Relative dates mode settings provider for course creation. 791 */ 792 public function create_course_relative_dates_provider() { 793 return [ 794 [0, 0, 0], 795 [0, 1, 0], 796 [1, 0, 0], 797 [1, 1, 1], 798 ]; 799 } 800 801 /** 802 * Test create_course by attempting to change the relative dates mode. 803 * 804 * @dataProvider create_course_relative_dates_provider 805 * @param int $setting The value for the 'enablecourserelativedates' admin setting. 806 * @param int $mode The value for the course's 'relativedatesmode' field. 807 * @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation. 808 */ 809 public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue) { 810 global $DB; 811 812 $this->resetAfterTest(); 813 814 set_config('enablecourserelativedates', $setting); 815 816 // Generate a course with relative dates mode set to $mode. 817 $course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]); 818 819 // Verify that the relative dates match what's expected. 820 $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]); 821 $this->assertEquals($expectedvalue, $relativedatesmode); 822 } 823 824 /** 825 * Test update_course by attempting to change the relative dates mode. 826 */ 827 public function test_relative_dates_mode_for_course_update() { 828 global $DB; 829 830 $this->resetAfterTest(); 831 832 set_config('enablecourserelativedates', 1); 833 834 // Generate a course with relative dates mode set to 1. 835 $course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]); 836 837 // Attempt to update the course with a changed relativedatesmode. 838 $course->relativedatesmode = 0; 839 update_course($course); 840 841 // Verify that the relative dates mode has not changed. 842 $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]); 843 $this->assertEquals(1, $relativedatesmode); 844 } 845 846 public function test_course_add_cm_to_section() { 847 global $DB; 848 $this->resetAfterTest(true); 849 850 // Create course with 1 section. 851 $course = $this->getDataGenerator()->create_course( 852 array('shortname' => 'GrowingCourse', 853 'fullname' => 'Growing Course', 854 'numsections' => 1), 855 array('createsections' => true)); 856 857 // Trash modinfo. 858 rebuild_course_cache($course->id, true); 859 860 // Create some cms for testing. 861 $cmids = array(); 862 for ($i=0; $i<4; $i++) { 863 $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id)); 864 } 865 866 // Add it to section that exists. 867 course_add_cm_to_section($course, $cmids[0], 1); 868 869 // Check it got added to sequence. 870 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1)); 871 $this->assertEquals($cmids[0], $sequence); 872 873 // Add a second, this time using courseid variant of parameters. 874 $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id)); 875 course_add_cm_to_section($course->id, $cmids[1], 1); 876 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1)); 877 $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence); 878 879 // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly). 880 $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id))); 881 $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id)); 882 883 // Add one to section that doesn't exist (this might rebuild modinfo). 884 course_add_cm_to_section($course, $cmids[2], 2); 885 $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id))); 886 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2)); 887 $this->assertEquals($cmids[2], $sequence); 888 889 // Add using the 'before' option. 890 course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]); 891 $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id))); 892 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2)); 893 $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence); 894 } 895 896 public function test_reorder_sections() { 897 global $DB; 898 $this->resetAfterTest(true); 899 900 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 901 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 902 $oldsections = array(); 903 $sections = array(); 904 foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) { 905 $oldsections[$section->section] = $section->id; 906 $sections[$section->id] = $section->section; 907 } 908 ksort($oldsections); 909 910 $neworder = reorder_sections($sections, 2, 4); 911 $neworder = array_keys($neworder); 912 $this->assertEquals($oldsections[0], $neworder[0]); 913 $this->assertEquals($oldsections[1], $neworder[1]); 914 $this->assertEquals($oldsections[2], $neworder[4]); 915 $this->assertEquals($oldsections[3], $neworder[2]); 916 $this->assertEquals($oldsections[4], $neworder[3]); 917 $this->assertEquals($oldsections[5], $neworder[5]); 918 $this->assertEquals($oldsections[6], $neworder[6]); 919 920 $neworder = reorder_sections($sections, 4, 2); 921 $neworder = array_keys($neworder); 922 $this->assertEquals($oldsections[0], $neworder[0]); 923 $this->assertEquals($oldsections[1], $neworder[1]); 924 $this->assertEquals($oldsections[2], $neworder[3]); 925 $this->assertEquals($oldsections[3], $neworder[4]); 926 $this->assertEquals($oldsections[4], $neworder[2]); 927 $this->assertEquals($oldsections[5], $neworder[5]); 928 $this->assertEquals($oldsections[6], $neworder[6]); 929 930 $neworder = reorder_sections(1, 2, 4); 931 $this->assertFalse($neworder); 932 } 933 934 public function test_move_section_down() { 935 global $DB; 936 $this->resetAfterTest(true); 937 938 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 939 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 940 $oldsections = array(); 941 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 942 $oldsections[$section->section] = $section->id; 943 } 944 ksort($oldsections); 945 946 // Test move section down.. 947 move_section_to($course, 2, 4); 948 $sections = array(); 949 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 950 $sections[$section->section] = $section->id; 951 } 952 ksort($sections); 953 954 $this->assertEquals($oldsections[0], $sections[0]); 955 $this->assertEquals($oldsections[1], $sections[1]); 956 $this->assertEquals($oldsections[2], $sections[4]); 957 $this->assertEquals($oldsections[3], $sections[2]); 958 $this->assertEquals($oldsections[4], $sections[3]); 959 $this->assertEquals($oldsections[5], $sections[5]); 960 $this->assertEquals($oldsections[6], $sections[6]); 961 } 962 963 public function test_move_section_up() { 964 global $DB; 965 $this->resetAfterTest(true); 966 967 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 968 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 969 $oldsections = array(); 970 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 971 $oldsections[$section->section] = $section->id; 972 } 973 ksort($oldsections); 974 975 // Test move section up.. 976 move_section_to($course, 6, 4); 977 $sections = array(); 978 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 979 $sections[$section->section] = $section->id; 980 } 981 ksort($sections); 982 983 $this->assertEquals($oldsections[0], $sections[0]); 984 $this->assertEquals($oldsections[1], $sections[1]); 985 $this->assertEquals($oldsections[2], $sections[2]); 986 $this->assertEquals($oldsections[3], $sections[3]); 987 $this->assertEquals($oldsections[4], $sections[5]); 988 $this->assertEquals($oldsections[5], $sections[6]); 989 $this->assertEquals($oldsections[6], $sections[4]); 990 } 991 992 public function test_move_section_marker() { 993 global $DB; 994 $this->resetAfterTest(true); 995 996 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 997 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 998 999 // Set course marker to the section we are going to move.. 1000 course_set_marker($course->id, 2); 1001 // Verify that the course marker is set correctly. 1002 $course = $DB->get_record('course', array('id' => $course->id)); 1003 $this->assertEquals(2, $course->marker); 1004 1005 // Test move the marked section down.. 1006 move_section_to($course, 2, 4); 1007 1008 // Verify that the course marker has been moved along with the section.. 1009 $course = $DB->get_record('course', array('id' => $course->id)); 1010 $this->assertEquals(4, $course->marker); 1011 1012 // Test move the marked section up.. 1013 move_section_to($course, 4, 3); 1014 1015 // Verify that the course marker has been moved along with the section.. 1016 $course = $DB->get_record('course', array('id' => $course->id)); 1017 $this->assertEquals(3, $course->marker); 1018 1019 // Test moving a non-marked section above the marked section.. 1020 move_section_to($course, 4, 2); 1021 1022 // Verify that the course marker has been moved down to accomodate.. 1023 $course = $DB->get_record('course', array('id' => $course->id)); 1024 $this->assertEquals(4, $course->marker); 1025 1026 // Test moving a non-marked section below the marked section.. 1027 move_section_to($course, 3, 6); 1028 1029 // Verify that the course marker has been up to accomodate.. 1030 $course = $DB->get_record('course', array('id' => $course->id)); 1031 $this->assertEquals(3, $course->marker); 1032 } 1033 1034 /** 1035 * Test move_section_to method. 1036 * Make sure that we only update the moving sections, not all the sections in the current course. 1037 * 1038 * @return void 1039 */ 1040 public function test_move_section_to() { 1041 global $DB, $CFG; 1042 $this->resetAfterTest(); 1043 $this->setAdminUser(); 1044 1045 // Generate the course and pre-requisite module. 1046 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]); 1047 1048 ob_start(); 1049 $DB->set_debug(true); 1050 // Move section. 1051 move_section_to($course, 2, 3); 1052 $DB->set_debug(false); 1053 $debuginfo = ob_get_contents(); 1054 ob_end_clean(); 1055 $sectionmovequerycount = substr_count($debuginfo, 'UPDATE ' . $CFG->phpunit_prefix . 'course_sections SET'); 1056 // We are updating the course_section table in steps to avoid breaking database uniqueness constraint. 1057 // So the queries will be doubled. See: course/lib.php:1423 1058 // Make sure that we only need 4 queries to update the position of section 2 and section 3. 1059 $this->assertEquals(4, $sectionmovequerycount); 1060 } 1061 1062 public function test_course_can_delete_section() { 1063 global $DB; 1064 $this->resetAfterTest(true); 1065 1066 $generator = $this->getDataGenerator(); 1067 1068 $courseweeks = $generator->create_course( 1069 array('numsections' => 5, 'format' => 'weeks'), 1070 array('createsections' => true)); 1071 $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1)); 1072 $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2)); 1073 1074 $coursetopics = $generator->create_course( 1075 array('numsections' => 5, 'format' => 'topics'), 1076 array('createsections' => true)); 1077 1078 $coursesingleactivity = $generator->create_course( 1079 array('format' => 'singleactivity'), 1080 array('createsections' => true)); 1081 1082 // Enrol student and teacher. 1083 $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); 1084 $student = $generator->create_user(); 1085 $teacher = $generator->create_user(); 1086 1087 $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']); 1088 $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']); 1089 1090 $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']); 1091 $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']); 1092 1093 $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']); 1094 $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']); 1095 1096 // Teacher should be able to delete sections (except for 0) in topics and weeks format. 1097 $this->setUser($teacher); 1098 1099 // For topics and weeks formats will return false for section 0 and true for any other section. 1100 $this->assertFalse(course_can_delete_section($courseweeks, 0)); 1101 $this->assertTrue(course_can_delete_section($courseweeks, 1)); 1102 1103 $this->assertFalse(course_can_delete_section($coursetopics, 0)); 1104 $this->assertTrue(course_can_delete_section($coursetopics, 1)); 1105 1106 // For singleactivity course format no section can be deleted. 1107 $this->assertFalse(course_can_delete_section($coursesingleactivity, 0)); 1108 $this->assertFalse(course_can_delete_section($coursesingleactivity, 1)); 1109 1110 // Now let's revoke a capability from teacher to manage activity in section 1. 1111 $modulecontext = context_module::instance($assign1->cmid); 1112 assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'], 1113 $modulecontext); 1114 $this->assertFalse(course_can_delete_section($courseweeks, 1)); 1115 $this->assertTrue(course_can_delete_section($courseweeks, 2)); 1116 1117 // Student does not have permissions to delete sections. 1118 $this->setUser($student); 1119 $this->assertFalse(course_can_delete_section($courseweeks, 1)); 1120 $this->assertFalse(course_can_delete_section($coursetopics, 1)); 1121 $this->assertFalse(course_can_delete_section($coursesingleactivity, 1)); 1122 } 1123 1124 public function test_course_delete_section() { 1125 global $DB; 1126 $this->resetAfterTest(true); 1127 1128 $generator = $this->getDataGenerator(); 1129 1130 $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'), 1131 array('createsections' => true)); 1132 $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0)); 1133 $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1)); 1134 $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2)); 1135 $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2)); 1136 $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3)); 1137 $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5)); 1138 $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6)); 1139 1140 $this->setAdminUser(); 1141 1142 // Attempt to delete non-existing section. 1143 $this->assertFalse(course_delete_section($course, 10, false)); 1144 $this->assertFalse(course_delete_section($course, 9, true)); 1145 1146 // Attempt to delete 0-section. 1147 $this->assertFalse(course_delete_section($course, 0, true)); 1148 $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid))); 1149 1150 // Delete last section. 1151 $this->assertTrue(course_delete_section($course, 6, true)); 1152 $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid))); 1153 $this->assertEquals(5, course_get_format($course)->get_last_section_number()); 1154 1155 // Delete empty section. 1156 $this->assertTrue(course_delete_section($course, 4, false)); 1157 $this->assertEquals(4, course_get_format($course)->get_last_section_number()); 1158 1159 // Delete section in the middle (2). 1160 $this->assertFalse(course_delete_section($course, 2, false)); 1161 $this->assertTrue(course_delete_section($course, 2, true)); 1162 $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid))); 1163 $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid))); 1164 $this->assertEquals(3, course_get_format($course)->get_last_section_number()); 1165 $this->assertEquals(array(0 => array($assign0->cmid), 1166 1 => array($assign1->cmid), 1167 2 => array($assign3->cmid), 1168 3 => array($assign5->cmid)), get_fast_modinfo($course)->sections); 1169 1170 // Remove marked section. 1171 course_set_marker($course->id, 1); 1172 $this->assertTrue(course_get_format($course)->is_section_current(1)); 1173 $this->assertTrue(course_delete_section($course, 1, true)); 1174 $this->assertFalse(course_get_format($course)->is_section_current(1)); 1175 } 1176 1177 public function test_get_course_display_name_for_list() { 1178 global $CFG; 1179 $this->resetAfterTest(true); 1180 1181 $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life')); 1182 1183 $CFG->courselistshortnames = 0; 1184 $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course)); 1185 1186 $CFG->courselistshortnames = 1; 1187 $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course)); 1188 } 1189 1190 public function test_move_module_in_course() { 1191 global $DB; 1192 1193 $this->resetAfterTest(true); 1194 // Setup fixture 1195 $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true)); 1196 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id)); 1197 1198 $cms = get_fast_modinfo($course)->get_cms(); 1199 $cm = reset($cms); 1200 1201 $newsection = get_fast_modinfo($course)->get_section_info(3); 1202 $oldsectionid = $cm->section; 1203 1204 // Perform the move 1205 moveto_module($cm, $newsection); 1206 1207 $cms = get_fast_modinfo($course)->get_cms(); 1208 $cm = reset($cms); 1209 1210 // Check that the cached modinfo contains the correct section info 1211 $modinfo = get_fast_modinfo($course); 1212 $this->assertTrue(empty($modinfo->sections[0])); 1213 $this->assertFalse(empty($modinfo->sections[3])); 1214 1215 // Check that the old section's sequence no longer contains this ID 1216 $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid)); 1217 $oldsequences = explode(',', $newsection->sequence); 1218 $this->assertFalse(in_array($cm->id, $oldsequences)); 1219 1220 // Check that the new section's sequence now contains this ID 1221 $newsection = $DB->get_record('course_sections', array('id' => $newsection->id)); 1222 $newsequences = explode(',', $newsection->sequence); 1223 $this->assertTrue(in_array($cm->id, $newsequences)); 1224 1225 // Check that the section number has been changed in the cm 1226 $this->assertEquals($newsection->id, $cm->section); 1227 1228 1229 // Perform a second move as some issues were only seen on the second move 1230 $newsection = get_fast_modinfo($course)->get_section_info(2); 1231 $oldsectionid = $cm->section; 1232 moveto_module($cm, $newsection); 1233 1234 $cms = get_fast_modinfo($course)->get_cms(); 1235 $cm = reset($cms); 1236 1237 // Check that the cached modinfo contains the correct section info 1238 $modinfo = get_fast_modinfo($course); 1239 $this->assertTrue(empty($modinfo->sections[0])); 1240 $this->assertFalse(empty($modinfo->sections[2])); 1241 1242 // Check that the old section's sequence no longer contains this ID 1243 $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid)); 1244 $oldsequences = explode(',', $newsection->sequence); 1245 $this->assertFalse(in_array($cm->id, $oldsequences)); 1246 1247 // Check that the new section's sequence now contains this ID 1248 $newsection = $DB->get_record('course_sections', array('id' => $newsection->id)); 1249 $newsequences = explode(',', $newsection->sequence); 1250 $this->assertTrue(in_array($cm->id, $newsequences)); 1251 } 1252 1253 public function test_module_visibility() { 1254 $this->setAdminUser(); 1255 $this->resetAfterTest(true); 1256 1257 // Create course and modules. 1258 $course = $this->getDataGenerator()->create_course(array('numsections' => 5)); 1259 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 1260 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id)); 1261 $modules = compact('forum', 'assign'); 1262 1263 // Hiding the modules. 1264 foreach ($modules as $mod) { 1265 set_coursemodule_visible($mod->cmid, 0); 1266 $this->check_module_visibility($mod, 0, 0); 1267 } 1268 1269 // Showing the modules. 1270 foreach ($modules as $mod) { 1271 set_coursemodule_visible($mod->cmid, 1); 1272 $this->check_module_visibility($mod, 1, 1); 1273 } 1274 } 1275 1276 public function test_section_visibility_events() { 1277 $this->setAdminUser(); 1278 $this->resetAfterTest(true); 1279 1280 $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true)); 1281 $sectionnumber = 1; 1282 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 1283 array('section' => $sectionnumber)); 1284 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 1285 'course' => $course->id), array('section' => $sectionnumber)); 1286 $sink = $this->redirectEvents(); 1287 set_section_visible($course->id, $sectionnumber, 0); 1288 $events = $sink->get_events(); 1289 1290 // Extract the number of events related to what we are testing, other events 1291 // such as course_section_updated could have been triggered. 1292 $count = 0; 1293 foreach ($events as $event) { 1294 if ($event instanceof \core\event\course_module_updated) { 1295 $count++; 1296 } 1297 } 1298 $this->assertSame(2, $count); 1299 $sink->close(); 1300 } 1301 1302 public function test_section_visibility() { 1303 $this->setAdminUser(); 1304 $this->resetAfterTest(true); 1305 1306 // Create course. 1307 $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true)); 1308 1309 $sink = $this->redirectEvents(); 1310 1311 // Testing an empty section. 1312 $sectionnumber = 1; 1313 set_section_visible($course->id, $sectionnumber, 0); 1314 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1315 $this->assertEquals($section_info->visible, 0); 1316 set_section_visible($course->id, $sectionnumber, 1); 1317 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1318 $this->assertEquals($section_info->visible, 1); 1319 1320 // Checking that an event was fired. 1321 $events = $sink->get_events(); 1322 $this->assertInstanceOf('\core\event\course_section_updated', $events[0]); 1323 $sink->close(); 1324 1325 // Testing a section with visible modules. 1326 $sectionnumber = 2; 1327 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 1328 array('section' => $sectionnumber)); 1329 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 1330 'course' => $course->id), array('section' => $sectionnumber)); 1331 $modules = compact('forum', 'assign'); 1332 set_section_visible($course->id, $sectionnumber, 0); 1333 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1334 $this->assertEquals($section_info->visible, 0); 1335 foreach ($modules as $mod) { 1336 $this->check_module_visibility($mod, 0, 1); 1337 } 1338 set_section_visible($course->id, $sectionnumber, 1); 1339 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1340 $this->assertEquals($section_info->visible, 1); 1341 foreach ($modules as $mod) { 1342 $this->check_module_visibility($mod, 1, 1); 1343 } 1344 1345 // Testing a section with hidden modules, which should stay hidden. 1346 $sectionnumber = 3; 1347 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 1348 array('section' => $sectionnumber)); 1349 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 1350 'course' => $course->id), array('section' => $sectionnumber)); 1351 $modules = compact('forum', 'assign'); 1352 foreach ($modules as $mod) { 1353 set_coursemodule_visible($mod->cmid, 0); 1354 $this->check_module_visibility($mod, 0, 0); 1355 } 1356 set_section_visible($course->id, $sectionnumber, 0); 1357 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1358 $this->assertEquals($section_info->visible, 0); 1359 foreach ($modules as $mod) { 1360 $this->check_module_visibility($mod, 0, 0); 1361 } 1362 set_section_visible($course->id, $sectionnumber, 1); 1363 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1364 $this->assertEquals($section_info->visible, 1); 1365 foreach ($modules as $mod) { 1366 $this->check_module_visibility($mod, 0, 0); 1367 } 1368 } 1369 1370 /** 1371 * Helper function to assert that a module has correctly been made visible, or hidden. 1372 * 1373 * @param stdClass $mod module information 1374 * @param int $visibility the current state of the module 1375 * @param int $visibleold the current state of the visibleold property 1376 * @return void 1377 */ 1378 public function check_module_visibility($mod, $visibility, $visibleold) { 1379 global $DB; 1380 $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid); 1381 $this->assertEquals($visibility, $cm->visible); 1382 $this->assertEquals($visibleold, $cm->visibleold); 1383 1384 // Check the module grade items. 1385 $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname, 1386 'iteminstance' => $cm->instance, 'courseid' => $cm->course)); 1387 if ($grade_items) { 1388 foreach ($grade_items as $grade_item) { 1389 if ($visibility) { 1390 $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible"); 1391 } else { 1392 $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden"); 1393 } 1394 } 1395 } 1396 1397 // Check the events visibility. 1398 if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) { 1399 foreach ($events as $event) { 1400 $calevent = new calendar_event($event); 1401 $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility"); 1402 } 1403 } 1404 } 1405 1406 public function test_course_page_type_list() { 1407 global $DB; 1408 $this->resetAfterTest(true); 1409 1410 // Create a category. 1411 $category = new stdClass(); 1412 $category->name = 'Test Category'; 1413 1414 $testcategory = $this->getDataGenerator()->create_category($category); 1415 1416 // Create a course. 1417 $course = new stdClass(); 1418 $course->fullname = 'Apu loves Unit TÉ™sts'; 1419 $course->shortname = 'Spread the lÅve'; 1420 $course->idnumber = '123'; 1421 $course->summary = 'Awesome!'; 1422 $course->summaryformat = FORMAT_PLAIN; 1423 $course->format = 'topics'; 1424 $course->newsitems = 0; 1425 $course->numsections = 5; 1426 $course->category = $testcategory->id; 1427 1428 $testcourse = $this->getDataGenerator()->create_course($course); 1429 1430 // Create contexts. 1431 $coursecontext = context_course::instance($testcourse->id); 1432 $parentcontext = $coursecontext->get_parent_context(); // Not actually used. 1433 $pagetype = 'page-course-x'; // Not used either. 1434 $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext); 1435 1436 // Page type lists for normal courses. 1437 $testpagetypelist1 = array(); 1438 $testpagetypelist1['*'] = 'Any page'; 1439 $testpagetypelist1['course-*'] = 'Any course page'; 1440 $testpagetypelist1['course-view-*'] = 'Any type of course main page'; 1441 1442 $this->assertEquals($testpagetypelist1, $pagetypelist); 1443 1444 // Get the context for the front page course. 1445 $sitecoursecontext = context_course::instance(SITEID); 1446 $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext); 1447 1448 // Page type list for the front page course. 1449 $testpagetypelist2 = array('*' => 'Any page'); 1450 $this->assertEquals($testpagetypelist2, $pagetypelist); 1451 1452 // Make sure that providing no current context to the function doesn't result in an error. 1453 // Calls made from generate_page_type_patterns() may provide null values. 1454 $pagetypelist = course_page_type_list($pagetype, null, null); 1455 $this->assertEquals($pagetypelist, $testpagetypelist1); 1456 } 1457 1458 public function test_compare_activities_by_time_desc() { 1459 1460 // Let's create some test data. 1461 $activitiesivities = array(); 1462 $x = new stdClass(); 1463 $x->timestamp = null; 1464 $activities[] = $x; 1465 1466 $x = new stdClass(); 1467 $x->timestamp = 1; 1468 $activities[] = $x; 1469 1470 $x = new stdClass(); 1471 $x->timestamp = 3; 1472 $activities[] = $x; 1473 1474 $x = new stdClass(); 1475 $x->timestamp = 0; 1476 $activities[] = $x; 1477 1478 $x = new stdClass(); 1479 $x->timestamp = 5; 1480 $activities[] = $x; 1481 1482 $x = new stdClass(); 1483 $activities[] = $x; 1484 1485 $x = new stdClass(); 1486 $x->timestamp = 5; 1487 $activities[] = $x; 1488 1489 // Do the sorting. 1490 usort($activities, 'compare_activities_by_time_desc'); 1491 1492 // Let's check the result. 1493 $last = 10; 1494 foreach($activities as $activity) { 1495 if (empty($activity->timestamp)) { 1496 $activity->timestamp = 0; 1497 } 1498 $this->assertLessThanOrEqual($last, $activity->timestamp); 1499 } 1500 } 1501 1502 public function test_compare_activities_by_time_asc() { 1503 1504 // Let's create some test data. 1505 $activities = array(); 1506 $x = new stdClass(); 1507 $x->timestamp = null; 1508 $activities[] = $x; 1509 1510 $x = new stdClass(); 1511 $x->timestamp = 1; 1512 $activities[] = $x; 1513 1514 $x = new stdClass(); 1515 $x->timestamp = 3; 1516 $activities[] = $x; 1517 1518 $x = new stdClass(); 1519 $x->timestamp = 0; 1520 $activities[] = $x; 1521 1522 $x = new stdClass(); 1523 $x->timestamp = 5; 1524 $activities[] = $x; 1525 1526 $x = new stdClass(); 1527 $activities[] = $x; 1528 1529 $x = new stdClass(); 1530 $x->timestamp = 5; 1531 $activities[] = $x; 1532 1533 // Do the sorting. 1534 usort($activities, 'compare_activities_by_time_asc'); 1535 1536 // Let's check the result. 1537 $last = 0; 1538 foreach($activities as $activity) { 1539 if (empty($activity->timestamp)) { 1540 $activity->timestamp = 0; 1541 } 1542 $this->assertGreaterThanOrEqual($last, $activity->timestamp); 1543 } 1544 } 1545 1546 /** 1547 * Tests moving a module between hidden/visible sections and 1548 * verifies that the course/module visiblity seettings are 1549 * retained. 1550 */ 1551 public function test_moveto_module_between_hidden_sections() { 1552 global $DB; 1553 1554 $this->resetAfterTest(true); 1555 1556 $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true)); 1557 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 1558 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 1559 $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id)); 1560 1561 // Set the page as hidden 1562 set_coursemodule_visible($page->cmid, 0); 1563 1564 // Set sections 3 as hidden. 1565 set_section_visible($course->id, 3, 0); 1566 1567 $modinfo = get_fast_modinfo($course); 1568 1569 $hiddensection = $modinfo->get_section_info(3); 1570 // New section is definitely not visible: 1571 $this->assertEquals($hiddensection->visible, 0); 1572 1573 $forumcm = $modinfo->cms[$forum->cmid]; 1574 $pagecm = $modinfo->cms[$page->cmid]; 1575 1576 // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state. 1577 $this->assertEquals(0, moveto_module($forumcm, $hiddensection)); 1578 $this->assertEquals(0, moveto_module($pagecm, $hiddensection)); 1579 1580 $modinfo = get_fast_modinfo($course); 1581 1582 // Verify that forum and page have been moved to the hidden section and quiz has not. 1583 $this->assertContainsEquals($forum->cmid, $modinfo->sections[3]); 1584 $this->assertContainsEquals($page->cmid, $modinfo->sections[3]); 1585 $this->assertNotContainsEquals($quiz->cmid, $modinfo->sections[3]); 1586 1587 // Verify that forum has been made invisible. 1588 $forumcm = $modinfo->cms[$forum->cmid]; 1589 $this->assertEquals($forumcm->visible, 0); 1590 // Verify that old state has been retained. 1591 $this->assertEquals($forumcm->visibleold, 1); 1592 1593 // Verify that page has stayed invisible. 1594 $pagecm = $modinfo->cms[$page->cmid]; 1595 $this->assertEquals($pagecm->visible, 0); 1596 // Verify that old state has been retained. 1597 $this->assertEquals($pagecm->visibleold, 0); 1598 1599 // Verify that quiz has been unaffected. 1600 $quizcm = $modinfo->cms[$quiz->cmid]; 1601 $this->assertEquals($quizcm->visible, 1); 1602 1603 // Move forum and page back to visible section. 1604 // Make sure the visibility is restored to the original value (visible for forum and hidden for page). 1605 $visiblesection = $modinfo->get_section_info(2); 1606 $this->assertEquals(1, moveto_module($forumcm, $visiblesection)); 1607 $this->assertEquals(0, moveto_module($pagecm, $visiblesection)); 1608 1609 $modinfo = get_fast_modinfo($course); 1610 1611 // Double check that forum has been made visible. 1612 $forumcm = $modinfo->cms[$forum->cmid]; 1613 $this->assertEquals($forumcm->visible, 1); 1614 1615 // Double check that page has stayed invisible. 1616 $pagecm = $modinfo->cms[$page->cmid]; 1617 $this->assertEquals($pagecm->visible, 0); 1618 1619 // Move the page in the same section (this is what mod duplicate does). 1620 // Visibility of page remains 0. 1621 $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm)); 1622 1623 // Double check that the the page is still hidden. 1624 $modinfo = get_fast_modinfo($course); 1625 $pagecm = $modinfo->cms[$page->cmid]; 1626 $this->assertEquals($pagecm->visible, 0); 1627 } 1628 1629 /** 1630 * Tests moving a module around in the same section. moveto_module() 1631 * is called this way in modduplicate. 1632 */ 1633 public function test_moveto_module_in_same_section() { 1634 global $DB; 1635 1636 $this->resetAfterTest(true); 1637 1638 $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true)); 1639 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 1640 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 1641 1642 // Simulate inconsistent visible/visibleold values (MDL-38713). 1643 $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST); 1644 $cm->visible = 0; 1645 $cm->visibleold = 1; 1646 $DB->update_record('course_modules', $cm); 1647 1648 $modinfo = get_fast_modinfo($course); 1649 $forumcm = $modinfo->cms[$forum->cmid]; 1650 $pagecm = $modinfo->cms[$page->cmid]; 1651 1652 // Verify that page is hidden. 1653 $this->assertEquals($pagecm->visible, 0); 1654 1655 // Verify section 0 is where all mods added. 1656 $section = $modinfo->get_section_info(0); 1657 $this->assertEquals($section->id, $forumcm->section); 1658 $this->assertEquals($section->id, $pagecm->section); 1659 1660 1661 // Move the page inside the hidden section. Make sure it is hidden. 1662 $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm)); 1663 1664 // Double check that the the page is still hidden. 1665 $modinfo = get_fast_modinfo($course); 1666 $pagecm = $modinfo->cms[$page->cmid]; 1667 $this->assertEquals($pagecm->visible, 0); 1668 } 1669 1670 /** 1671 * Tests the function that deletes a course module 1672 * 1673 * @param string $type The type of module for the test 1674 * @param array $options The options for the module creation 1675 * @dataProvider provider_course_delete_module 1676 */ 1677 public function test_course_delete_module($type, $options) { 1678 global $DB; 1679 1680 $this->resetAfterTest(true); 1681 $this->setAdminUser(); 1682 1683 // Create course and modules. 1684 $course = $this->getDataGenerator()->create_course(array('numsections' => 5)); 1685 $options['course'] = $course->id; 1686 1687 // Generate an assignment with due date (will generate a course event). 1688 $module = $this->getDataGenerator()->create_module($type, $options); 1689 1690 // Get the module context. 1691 $modcontext = context_module::instance($module->cmid); 1692 1693 $assocblog = $this->create_module_asscociated_blog($course, $modcontext); 1694 1695 // Verify context exists. 1696 $this->assertInstanceOf('context_module', $modcontext); 1697 1698 // Make module specific messes. 1699 switch ($type) { 1700 case 'assign': 1701 // Add some tags to this assignment. 1702 core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3')); 1703 core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5')); 1704 1705 // Confirm the tag instances were added. 1706 $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id); 1707 $this->assertEquals(3, $DB->count_records('tag_instance', $criteria)); 1708 $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id); 1709 $this->assertEquals(3, $DB->count_records('tag_instance', $criteria)); 1710 1711 // Verify event assignment event has been generated. 1712 $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type)); 1713 $this->assertEquals(1, $eventcount); 1714 1715 break; 1716 case 'quiz': 1717 $qgen = $this->getDataGenerator()->get_plugin_generator('core_question'); 1718 $qcat = $qgen->create_question_category(array('contextid' => $modcontext->id)); 1719 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)); 1720 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)); 1721 break; 1722 default: 1723 break; 1724 } 1725 1726 // Run delete.. 1727 course_delete_module($module->cmid); 1728 1729 // Verify the context has been removed. 1730 $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING)); 1731 1732 // Verify the course_module record has been deleted. 1733 $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid)); 1734 $this->assertEmpty($cmcount); 1735 1736 // Verify the blog_association record has been deleted. 1737 $this->assertCount(0, $DB->get_records('blog_association', 1738 array('contextid' => $modcontext->id))); 1739 1740 // Verify the blog post record has been deleted. 1741 $this->assertCount(0, $DB->get_records('post', 1742 array('id' => $assocblog->id))); 1743 1744 // Verify the tag instance record has been deleted. 1745 $this->assertCount(0, $DB->get_records('tag_instance', 1746 array('itemid' => $assocblog->id))); 1747 1748 // Test clean up of module specific messes. 1749 switch ($type) { 1750 case 'assign': 1751 // Verify event assignment events have been removed. 1752 $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type)); 1753 $this->assertEmpty($eventcount); 1754 1755 // Verify the tag instances were deleted. 1756 $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id); 1757 $this->assertEquals(0, $DB->count_records('tag_instance', $criteria)); 1758 1759 $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id); 1760 $this->assertEquals(0, $DB->count_records('tag_instance', $criteria)); 1761 break; 1762 case 'quiz': 1763 // Verify category deleted. 1764 $criteria = array('contextid' => $modcontext->id); 1765 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 1766 1767 // Verify questions deleted. 1768 $criteria = array('category' => $qcat->id); 1769 $this->assertEquals(0, $DB->count_records('question', $criteria)); 1770 break; 1771 default: 1772 break; 1773 } 1774 } 1775 1776 /** 1777 * Test that triggering a course_created event works as expected. 1778 */ 1779 public function test_course_created_event() { 1780 global $DB; 1781 1782 $this->resetAfterTest(); 1783 1784 // Catch the events. 1785 $sink = $this->redirectEvents(); 1786 1787 // Create the course with an id number which is used later when generating a course via the imsenterprise plugin. 1788 $data = new stdClass(); 1789 $data->idnumber = 'idnumber'; 1790 $course = $this->getDataGenerator()->create_course($data); 1791 // Get course from DB for comparison. 1792 $course = $DB->get_record('course', array('id' => $course->id)); 1793 1794 // Capture the event. 1795 $events = $sink->get_events(); 1796 $sink->close(); 1797 1798 // Validate the event. 1799 $event = $events[0]; 1800 $this->assertInstanceOf('\core\event\course_created', $event); 1801 $this->assertEquals('course', $event->objecttable); 1802 $this->assertEquals($course->id, $event->objectid); 1803 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 1804 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id)); 1805 $this->assertEquals('course_created', $event->get_legacy_eventname()); 1806 $this->assertEventLegacyData($course, $event); 1807 $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')'); 1808 $this->assertEventLegacyLogData($expectedlog, $event); 1809 1810 // Now we want to trigger creating a course via the imsenterprise. 1811 // Delete the course we created earlier, as we want the imsenterprise plugin to create this. 1812 // We do not want print out any of the text this function generates while doing this, which is why 1813 // we are using ob_start() and ob_end_clean(). 1814 ob_start(); 1815 delete_course($course); 1816 ob_end_clean(); 1817 1818 // Create the XML file we want to use. 1819 $course->category = (array)$course->category; 1820 $imstestcase = new imsenterprise_test(); 1821 $imstestcase->imsplugin = enrol_get_plugin('imsenterprise'); 1822 $imstestcase->set_test_config(); 1823 $imstestcase->set_xml_file(false, array($course)); 1824 1825 // Capture the event. 1826 $sink = $this->redirectEvents(); 1827 $imstestcase->imsplugin->cron(); 1828 $events = $sink->get_events(); 1829 $sink->close(); 1830 $event = null; 1831 foreach ($events as $eventinfo) { 1832 if ($eventinfo instanceof \core\event\course_created ) { 1833 $event = $eventinfo; 1834 break; 1835 } 1836 } 1837 1838 // Validate the event triggered is \core\event\course_created. There is no need to validate the other values 1839 // as they have already been validated in the previous steps. Here we only want to make sure that when the 1840 // imsenterprise plugin creates a course an event is triggered. 1841 $this->assertInstanceOf('\core\event\course_created', $event); 1842 $this->assertEventContextNotUsed($event); 1843 } 1844 1845 /** 1846 * Test that triggering a course_updated event works as expected. 1847 */ 1848 public function test_course_updated_event() { 1849 global $DB; 1850 1851 $this->resetAfterTest(); 1852 1853 // Create a course. 1854 $course = $this->getDataGenerator()->create_course(); 1855 1856 // Create a category we are going to move this course to. 1857 $category = $this->getDataGenerator()->create_category(); 1858 1859 // Create a hidden category we are going to move this course to. 1860 $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0)); 1861 1862 // Update course and catch course_updated event. 1863 $sink = $this->redirectEvents(); 1864 update_course($course); 1865 $events = $sink->get_events(); 1866 $sink->close(); 1867 1868 // Get updated course information from the DB. 1869 $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 1870 // Validate event. 1871 $event = array_shift($events); 1872 $this->assertInstanceOf('\core\event\course_updated', $event); 1873 $this->assertEquals('course', $event->objecttable); 1874 $this->assertEquals($updatedcourse->id, $event->objectid); 1875 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 1876 $url = new moodle_url('/course/edit.php', array('id' => $event->objectid)); 1877 $this->assertEquals($url, $event->get_url()); 1878 $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid)); 1879 $this->assertEquals('course_updated', $event->get_legacy_eventname()); 1880 $this->assertEventLegacyData($updatedcourse, $event); 1881 $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id); 1882 $this->assertEventLegacyLogData($expectedlog, $event); 1883 1884 // Move course and catch course_updated event. 1885 $sink = $this->redirectEvents(); 1886 move_courses(array($course->id), $category->id); 1887 $events = $sink->get_events(); 1888 $sink->close(); 1889 1890 // Return the moved course information from the DB. 1891 $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 1892 // Validate event. 1893 $event = array_shift($events); 1894 $this->assertInstanceOf('\core\event\course_updated', $event); 1895 $this->assertEquals('course', $event->objecttable); 1896 $this->assertEquals($movedcourse->id, $event->objectid); 1897 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 1898 $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id)); 1899 $this->assertEquals('course_updated', $event->get_legacy_eventname()); 1900 $this->assertEventLegacyData($movedcourse, $event); 1901 $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id); 1902 $this->assertEventLegacyLogData($expectedlog, $event); 1903 1904 // Move course to hidden category and catch course_updated event. 1905 $sink = $this->redirectEvents(); 1906 move_courses(array($course->id), $categoryhidden->id); 1907 $events = $sink->get_events(); 1908 $sink->close(); 1909 1910 // Return the moved course information from the DB. 1911 $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 1912 // Validate event. 1913 $event = array_shift($events); 1914 $this->assertInstanceOf('\core\event\course_updated', $event); 1915 $this->assertEquals('course', $event->objecttable); 1916 $this->assertEquals($movedcoursehidden->id, $event->objectid); 1917 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 1918 $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id)); 1919 $this->assertEquals('course_updated', $event->get_legacy_eventname()); 1920 $this->assertEventLegacyData($movedcoursehidden, $event); 1921 $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id); 1922 $this->assertEventLegacyLogData($expectedlog, $event); 1923 $this->assertEventContextNotUsed($event); 1924 } 1925 1926 /** 1927 * Test that triggering a course_updated event logs changes. 1928 */ 1929 public function test_course_updated_event_with_changes() { 1930 global $DB; 1931 1932 $this->resetAfterTest(); 1933 1934 // Create a course. 1935 $course = $this->getDataGenerator()->create_course((object)['visible' => 1]); 1936 1937 $editedcourse = $DB->get_record('course', ['id' => $course->id]); 1938 $editedcourse->visible = 0; 1939 1940 // Update course and catch course_updated event. 1941 $sink = $this->redirectEvents(); 1942 update_course($editedcourse); 1943 $events = $sink->get_events(); 1944 $sink->close(); 1945 1946 $event = array_shift($events); 1947 $this->assertInstanceOf('\core\event\course_updated', $event); 1948 $otherdata = [ 1949 'shortname' => $course->shortname, 1950 'fullname' => $course->fullname, 1951 'updatedfields' => [ 1952 'visible' => 0 1953 ] 1954 ]; 1955 $this->assertEquals($otherdata, $event->other); 1956 1957 } 1958 1959 /** 1960 * Test that triggering a course_deleted event works as expected. 1961 */ 1962 public function test_course_deleted_event() { 1963 $this->resetAfterTest(); 1964 1965 // Create the course. 1966 $course = $this->getDataGenerator()->create_course(); 1967 1968 // Save the course context before we delete the course. 1969 $coursecontext = context_course::instance($course->id); 1970 1971 // Catch the update event. 1972 $sink = $this->redirectEvents(); 1973 1974 // Call delete_course() which will trigger the course_deleted event and the course_content_deleted 1975 // event. This function prints out data to the screen, which we do not want during a PHPUnit test, 1976 // so use ob_start and ob_end_clean to prevent this. 1977 ob_start(); 1978 delete_course($course); 1979 ob_end_clean(); 1980 1981 // Capture the event. 1982 $events = $sink->get_events(); 1983 $sink->close(); 1984 1985 // Validate the event. 1986 $event = array_pop($events); 1987 $this->assertInstanceOf('\core\event\course_deleted', $event); 1988 $this->assertEquals('course', $event->objecttable); 1989 $this->assertEquals($course->id, $event->objectid); 1990 $this->assertEquals($coursecontext->id, $event->contextid); 1991 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id)); 1992 $this->assertEquals('course_deleted', $event->get_legacy_eventname()); 1993 $eventdata = $event->get_data(); 1994 $this->assertSame($course->idnumber, $eventdata['other']['idnumber']); 1995 $this->assertSame($course->fullname, $eventdata['other']['fullname']); 1996 $this->assertSame($course->shortname, $eventdata['other']['shortname']); 1997 1998 // The legacy data also passed the context in the course object and substitutes timemodified with the current date. 1999 $expectedlegacy = clone($course); 2000 $expectedlegacy->context = $coursecontext; 2001 $expectedlegacy->timemodified = $event->timecreated; 2002 $this->assertEventLegacyData($expectedlegacy, $event); 2003 2004 // Validate legacy log data. 2005 $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')'); 2006 $this->assertEventLegacyLogData($expectedlog, $event); 2007 $this->assertEventContextNotUsed($event); 2008 } 2009 2010 /** 2011 * Test that triggering a course_content_deleted event works as expected. 2012 */ 2013 public function test_course_content_deleted_event() { 2014 global $DB; 2015 2016 $this->resetAfterTest(); 2017 2018 // Create the course. 2019 $course = $this->getDataGenerator()->create_course(); 2020 2021 // Get the course from the DB. The data generator adds some extra properties, such as 2022 // numsections, to the course object which will fail the assertions later on. 2023 $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 2024 2025 // Save the course context before we delete the course. 2026 $coursecontext = context_course::instance($course->id); 2027 2028 // Catch the update event. 2029 $sink = $this->redirectEvents(); 2030 2031 remove_course_contents($course->id, false); 2032 2033 // Capture the event. 2034 $events = $sink->get_events(); 2035 $sink->close(); 2036 2037 // Validate the event. 2038 $event = array_pop($events); 2039 $this->assertInstanceOf('\core\event\course_content_deleted', $event); 2040 $this->assertEquals('course', $event->objecttable); 2041 $this->assertEquals($course->id, $event->objectid); 2042 $this->assertEquals($coursecontext->id, $event->contextid); 2043 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id)); 2044 $this->assertEquals('course_content_removed', $event->get_legacy_eventname()); 2045 // The legacy data also passed the context and options in the course object. 2046 $course->context = $coursecontext; 2047 $course->options = array(); 2048 $this->assertEventLegacyData($course, $event); 2049 $this->assertEventContextNotUsed($event); 2050 } 2051 2052 /** 2053 * Test that triggering a course_category_deleted event works as expected. 2054 */ 2055 public function test_course_category_deleted_event() { 2056 $this->resetAfterTest(); 2057 2058 // Create a category. 2059 $category = $this->getDataGenerator()->create_category(); 2060 2061 // Save the original record/context before it is deleted. 2062 $categoryrecord = $category->get_db_record(); 2063 $categorycontext = context_coursecat::instance($category->id); 2064 2065 // Catch the update event. 2066 $sink = $this->redirectEvents(); 2067 2068 // Delete the category. 2069 $category->delete_full(); 2070 2071 // Capture the event. 2072 $events = $sink->get_events(); 2073 $sink->close(); 2074 2075 // Validate the event. 2076 $event = $events[0]; 2077 $this->assertInstanceOf('\core\event\course_category_deleted', $event); 2078 $this->assertEquals('course_categories', $event->objecttable); 2079 $this->assertEquals($category->id, $event->objectid); 2080 $this->assertEquals($categorycontext->id, $event->contextid); 2081 $this->assertEquals([ 2082 'name' => $category->name, 2083 ], $event->other); 2084 $this->assertEquals($categoryrecord, $event->get_record_snapshot($event->objecttable, $event->objectid)); 2085 $this->assertEquals('course_category_deleted', $event->get_legacy_eventname()); 2086 $this->assertEquals(null, $event->get_url()); 2087 $this->assertEventLegacyData($category, $event); 2088 $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')'); 2089 $this->assertEventLegacyLogData($expectedlog, $event); 2090 2091 // Create two categories. 2092 $category = $this->getDataGenerator()->create_category(); 2093 $category2 = $this->getDataGenerator()->create_category(); 2094 2095 // Save the original record/context before it is moved and then deleted. 2096 $category2record = $category2->get_db_record(); 2097 $category2context = context_coursecat::instance($category2->id); 2098 2099 // Catch the update event. 2100 $sink = $this->redirectEvents(); 2101 2102 // Move the category. 2103 $category2->delete_move($category->id); 2104 2105 // Capture the event. 2106 $events = $sink->get_events(); 2107 $sink->close(); 2108 2109 // Validate the event. 2110 $event = $events[0]; 2111 $this->assertInstanceOf('\core\event\course_category_deleted', $event); 2112 $this->assertEquals('course_categories', $event->objecttable); 2113 $this->assertEquals($category2->id, $event->objectid); 2114 $this->assertEquals($category2context->id, $event->contextid); 2115 $this->assertEquals([ 2116 'name' => $category2->name, 2117 'contentmovedcategoryid' => $category->id, 2118 ], $event->other); 2119 $this->assertEquals($category2record, $event->get_record_snapshot($event->objecttable, $event->objectid)); 2120 $this->assertEquals('course_category_deleted', $event->get_legacy_eventname()); 2121 $this->assertEventLegacyData($category2, $event); 2122 $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')'); 2123 $this->assertEventLegacyLogData($expectedlog, $event); 2124 $this->assertEventContextNotUsed($event); 2125 } 2126 2127 /** 2128 * Test that triggering a course_backup_created event works as expected. 2129 */ 2130 public function test_course_backup_created_event() { 2131 global $CFG; 2132 2133 // Get the necessary files to perform backup and restore. 2134 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 2135 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 2136 2137 $this->resetAfterTest(); 2138 2139 // Set to admin user. 2140 $this->setAdminUser(); 2141 2142 // The user id is going to be 2 since we are the admin user. 2143 $userid = 2; 2144 2145 // Create a course. 2146 $course = $this->getDataGenerator()->create_course(); 2147 2148 // Create backup file and save it to the backup location. 2149 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, 2150 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid); 2151 $sink = $this->redirectEvents(); 2152 $bc->execute_plan(); 2153 2154 // Capture the event. 2155 $events = $sink->get_events(); 2156 $sink->close(); 2157 2158 // Validate the event. 2159 $event = array_pop($events); 2160 $this->assertInstanceOf('\core\event\course_backup_created', $event); 2161 $this->assertEquals('course', $event->objecttable); 2162 $this->assertEquals($bc->get_courseid(), $event->objectid); 2163 $this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid); 2164 2165 $url = new moodle_url('/course/view.php', array('id' => $event->objectid)); 2166 $this->assertEquals($url, $event->get_url()); 2167 $this->assertEventContextNotUsed($event); 2168 2169 // Destroy the resource controller since we are done using it. 2170 $bc->destroy(); 2171 } 2172 2173 /** 2174 * Test that triggering a course_restored event works as expected. 2175 */ 2176 public function test_course_restored_event() { 2177 global $CFG; 2178 2179 // Get the necessary files to perform backup and restore. 2180 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 2181 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 2182 2183 $this->resetAfterTest(); 2184 2185 // Set to admin user. 2186 $this->setAdminUser(); 2187 2188 // The user id is going to be 2 since we are the admin user. 2189 $userid = 2; 2190 2191 // Create a course. 2192 $course = $this->getDataGenerator()->create_course(); 2193 2194 // Create backup file and save it to the backup location. 2195 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, 2196 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid); 2197 $bc->execute_plan(); 2198 $results = $bc->get_results(); 2199 $file = $results['backup_destination']; 2200 $fp = get_file_packer('application/vnd.moodle.backup'); 2201 $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event'; 2202 $file->extract_to_pathname($fp, $filepath); 2203 $bc->destroy(); 2204 2205 // Now we want to catch the restore course event. 2206 $sink = $this->redirectEvents(); 2207 2208 // Now restore the course to trigger the event. 2209 $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO, 2210 backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE); 2211 $rc->execute_precheck(); 2212 $rc->execute_plan(); 2213 2214 // Capture the event. 2215 $events = $sink->get_events(); 2216 $sink->close(); 2217 2218 // Validate the event. 2219 $event = array_pop($events); 2220 $this->assertInstanceOf('\core\event\course_restored', $event); 2221 $this->assertEquals('course', $event->objecttable); 2222 $this->assertEquals($rc->get_courseid(), $event->objectid); 2223 $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid); 2224 $this->assertEquals('course_restored', $event->get_legacy_eventname()); 2225 $legacydata = (object) array( 2226 'courseid' => $rc->get_courseid(), 2227 'userid' => $rc->get_userid(), 2228 'type' => $rc->get_type(), 2229 'target' => $rc->get_target(), 2230 'mode' => $rc->get_mode(), 2231 'operation' => $rc->get_operation(), 2232 'samesite' => $rc->is_samesite() 2233 ); 2234 $url = new moodle_url('/course/view.php', array('id' => $event->objectid)); 2235 $this->assertEquals($url, $event->get_url()); 2236 $this->assertEventLegacyData($legacydata, $event); 2237 $this->assertEventContextNotUsed($event); 2238 2239 // Destroy the resource controller since we are done using it. 2240 $rc->destroy(); 2241 } 2242 2243 /** 2244 * Test that triggering a course_section_updated event works as expected. 2245 */ 2246 public function test_course_section_updated_event() { 2247 global $DB; 2248 2249 $this->resetAfterTest(); 2250 2251 // Create the course with sections. 2252 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true)); 2253 $sections = $DB->get_records('course_sections', array('course' => $course->id)); 2254 2255 $coursecontext = context_course::instance($course->id); 2256 2257 $section = array_pop($sections); 2258 $section->name = 'Test section'; 2259 $section->summary = 'Test section summary'; 2260 $DB->update_record('course_sections', $section); 2261 2262 // Trigger an event for course section update. 2263 $event = \core\event\course_section_updated::create( 2264 array( 2265 'objectid' => $section->id, 2266 'courseid' => $course->id, 2267 'context' => context_course::instance($course->id), 2268 'other' => array( 2269 'sectionnum' => $section->section 2270 ) 2271 ) 2272 ); 2273 $event->add_record_snapshot('course_sections', $section); 2274 // Trigger and catch event. 2275 $sink = $this->redirectEvents(); 2276 $event->trigger(); 2277 $events = $sink->get_events(); 2278 $sink->close(); 2279 2280 // Validate the event. 2281 $event = $events[0]; 2282 $this->assertInstanceOf('\core\event\course_section_updated', $event); 2283 $this->assertEquals('course_sections', $event->objecttable); 2284 $this->assertEquals($section->id, $event->objectid); 2285 $this->assertEquals($course->id, $event->courseid); 2286 $this->assertEquals($coursecontext->id, $event->contextid); 2287 $this->assertEquals($section->section, $event->other['sectionnum']); 2288 $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'"; 2289 $this->assertEquals($expecteddesc, $event->get_description()); 2290 $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid)); 2291 $this->assertEquals($url, $event->get_url()); 2292 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid)); 2293 $id = $section->id; 2294 $sectionnum = $section->section; 2295 $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum); 2296 $this->assertEventLegacyLogData($expectedlegacydata, $event); 2297 $this->assertEventContextNotUsed($event); 2298 } 2299 2300 /** 2301 * Test that triggering a course_section_deleted event works as expected. 2302 */ 2303 public function test_course_section_deleted_event() { 2304 global $USER, $DB; 2305 $this->resetAfterTest(); 2306 $sink = $this->redirectEvents(); 2307 2308 // Create the course with sections. 2309 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true)); 2310 $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section'); 2311 $coursecontext = context_course::instance($course->id); 2312 $section = array_pop($sections); 2313 course_delete_section($course, $section); 2314 $events = $sink->get_events(); 2315 $event = array_pop($events); // Delete section event. 2316 $sink->close(); 2317 2318 // Validate event data. 2319 $this->assertInstanceOf('\core\event\course_section_deleted', $event); 2320 $this->assertEquals('course_sections', $event->objecttable); 2321 $this->assertEquals($section->id, $event->objectid); 2322 $this->assertEquals($course->id, $event->courseid); 2323 $this->assertEquals($coursecontext->id, $event->contextid); 2324 $this->assertEquals($section->section, $event->other['sectionnum']); 2325 $expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " . 2326 "(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'"; 2327 $this->assertEquals($expecteddesc, $event->get_description()); 2328 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid)); 2329 $this->assertNull($event->get_url()); 2330 2331 // Test legacy data. 2332 $sectionnum = $section->section; 2333 $expectedlegacydata = array($course->id, "course", "delete section", 'view.php?id=' . $course->id, $sectionnum); 2334 $this->assertEventLegacyLogData($expectedlegacydata, $event); 2335 $this->assertEventContextNotUsed($event); 2336 } 2337 2338 public function test_course_integrity_check() { 2339 global $DB; 2340 2341 $this->resetAfterTest(true); 2342 $course = $this->getDataGenerator()->create_course(array('numsections' => 1), 2343 array('createsections'=>true)); 2344 2345 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 2346 array('section' => 0)); 2347 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id), 2348 array('section' => 0)); 2349 $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id), 2350 array('section' => 0)); 2351 $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid)); 2352 2353 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2354 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2355 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2356 $this->assertEquals($correctseq, $section0->sequence); 2357 $this->assertEmpty($section1->sequence); 2358 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2359 $this->assertEquals($section0->id, $cms[$page->cmid]->section); 2360 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2361 $this->assertEmpty(course_integrity_check($course->id)); 2362 2363 // Now let's make manual change in DB and let course_integrity_check() fix it: 2364 2365 // 1. Module appears twice in one section. 2366 $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid)); 2367 $this->assertEquals( 2368 array('Failed integrity check for course ['. $course->id. 2369 ']. Sequence for course section ['. $section0->id. '] is "'. 2370 $section0->sequence. ','. $page->cmid. '", must be "'. 2371 $section0->sequence. '"'), 2372 course_integrity_check($course->id)); 2373 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2374 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2375 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2376 $this->assertEquals($correctseq, $section0->sequence); 2377 $this->assertEmpty($section1->sequence); 2378 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2379 $this->assertEquals($section0->id, $cms[$page->cmid]->section); 2380 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2381 2382 // 2. Module appears in two sections (last section wins). 2383 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid)); 2384 // First message about double mentioning in sequence, second message about wrong section field for $page. 2385 $this->assertEquals(array( 2386 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2387 '] must be removed from sequence of section ['. $section0->id. 2388 '] because it is also present in sequence of section ['. $section1->id. ']', 2389 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2390 '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'), 2391 course_integrity_check($course->id)); 2392 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2393 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2394 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2395 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2396 $this->assertEquals(''. $page->cmid, $section1->sequence); 2397 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2398 $this->assertEquals($section1->id, $cms[$page->cmid]->section); 2399 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2400 2401 // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false). 2402 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => '')); 2403 $this->assertEmpty(course_integrity_check($course->id)); // Not an error! 2404 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2405 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2406 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2407 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2408 $this->assertEmpty($section1->sequence); 2409 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2410 $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed. 2411 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2412 2413 // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true). 2414 $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['. 2415 $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'), 2416 course_integrity_check($course->id, null, null, true)); // Error! 2417 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2418 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2419 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2420 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2421 $this->assertEquals(''. $page->cmid, $section1->sequence); // Yay, module added to section. 2422 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2423 $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed. 2424 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2425 2426 // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true). 2427 $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765)); 2428 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => '')); 2429 $this->assertEquals(array( 2430 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2431 '] is missing from sequence of section ['. $section0->id. ']', 2432 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2433 '] points to section [8765] instead of ['. $section0->id. ']'), 2434 course_integrity_check($course->id, null, null, true)); 2435 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2436 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2437 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2438 $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section. 2439 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2440 $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0. 2441 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2442 2443 // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true). 2444 $DB->delete_records('course_modules', array('id' => $page->cmid)); 2445 $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['. 2446 $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'), 2447 course_integrity_check($course->id, null, null, true)); 2448 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2449 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2450 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2451 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2452 $this->assertEmpty($section1->sequence); 2453 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2454 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2455 $this->assertEquals(2, count($cms)); 2456 } 2457 2458 /** 2459 * Tests for event related to course module creation. 2460 */ 2461 public function test_course_module_created_event() { 2462 global $USER; 2463 2464 $this->resetAfterTest(); 2465 $this->setAdminUser(); 2466 2467 // Create an assign module. 2468 $sink = $this->redirectEvents(); 2469 $course = $this->getDataGenerator()->create_course(); 2470 $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]); 2471 $events = $sink->get_events(); 2472 $eventscount = 0; 2473 2474 // Validate event data. 2475 foreach ($events as $event) { 2476 if ($event instanceof \core\event\course_module_created) { 2477 $eventscount++; 2478 2479 $this->assertEquals($module->cmid, $event->objectid); 2480 $this->assertEquals($USER->id, $event->userid); 2481 $this->assertEquals('course_modules', $event->objecttable); 2482 $url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid)); 2483 $this->assertEquals($url, $event->get_url()); 2484 2485 // Test legacy data. 2486 $this->assertSame('mod_created', $event->get_legacy_eventname()); 2487 $eventdata = new stdClass(); 2488 $eventdata->modulename = 'assign'; 2489 $eventdata->name = $module->name; 2490 $eventdata->cmid = $module->cmid; 2491 $eventdata->courseid = $module->course; 2492 $eventdata->userid = $USER->id; 2493 $this->assertEventLegacyData($eventdata, $event); 2494 2495 $arr = array( 2496 array($module->course, "course", "add mod", "../mod/assign/view.php?id=$module->cmid", "assign $module->id"), 2497 array($module->course, "assign", "add", "view.php?id=$module->cmid", $module->id, $module->cmid) 2498 ); 2499 $this->assertEventLegacyLogData($arr, $event); 2500 $this->assertEventContextNotUsed($event); 2501 } 2502 } 2503 // Only one \core\event\course_module_created event should be triggered. 2504 $this->assertEquals(1, $eventscount); 2505 2506 // Let us see if duplicating an activity results in a nice course module created event. 2507 $sink->clear(); 2508 $course = get_course($module->course); 2509 $cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST); 2510 $newcm = duplicate_module($course, $cm); 2511 $events = $sink->get_events(); 2512 $eventscount = 0; 2513 $sink->close(); 2514 2515 foreach ($events as $event) { 2516 if ($event instanceof \core\event\course_module_created) { 2517 $eventscount++; 2518 // Validate event data. 2519 $this->assertInstanceOf('\core\event\course_module_created', $event); 2520 $this->assertEquals($newcm->id, $event->objectid); 2521 $this->assertEquals($USER->id, $event->userid); 2522 $this->assertEquals($course->id, $event->courseid); 2523 $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id)); 2524 $this->assertEquals($url, $event->get_url()); 2525 } 2526 } 2527 2528 // Only one \core\event\course_module_created event should be triggered. 2529 $this->assertEquals(1, $eventscount); 2530 } 2531 2532 /** 2533 * Tests for event validations related to course module creation. 2534 */ 2535 public function test_course_module_created_event_exceptions() { 2536 2537 $this->resetAfterTest(); 2538 2539 // Generate data. 2540 $modinfo = $this->create_specific_module_test('assign'); 2541 $context = context_module::instance($modinfo->coursemodule); 2542 2543 // Test not setting instanceid. 2544 try { 2545 $event = \core\event\course_module_created::create(array( 2546 'courseid' => $modinfo->course, 2547 'context' => $context, 2548 'objectid' => $modinfo->coursemodule, 2549 'other' => array( 2550 'modulename' => 'assign', 2551 'name' => 'My assignment', 2552 ) 2553 )); 2554 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without 2555 other['instanceid']"); 2556 } catch (coding_exception $e) { 2557 $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage()); 2558 } 2559 2560 // Test not setting modulename. 2561 try { 2562 $event = \core\event\course_module_created::create(array( 2563 'courseid' => $modinfo->course, 2564 'context' => $context, 2565 'objectid' => $modinfo->coursemodule, 2566 'other' => array( 2567 'instanceid' => $modinfo->instance, 2568 'name' => 'My assignment', 2569 ) 2570 )); 2571 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without 2572 other['modulename']"); 2573 } catch (coding_exception $e) { 2574 $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage()); 2575 } 2576 2577 // Test not setting name. 2578 2579 try { 2580 $event = \core\event\course_module_created::create(array( 2581 'courseid' => $modinfo->course, 2582 'context' => $context, 2583 'objectid' => $modinfo->coursemodule, 2584 'other' => array( 2585 'modulename' => 'assign', 2586 'instanceid' => $modinfo->instance, 2587 ) 2588 )); 2589 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without 2590 other['name']"); 2591 } catch (coding_exception $e) { 2592 $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage()); 2593 } 2594 2595 } 2596 2597 /** 2598 * Tests for event related to course module updates. 2599 */ 2600 public function test_course_module_updated_event() { 2601 global $USER, $DB; 2602 $this->resetAfterTest(); 2603 2604 // Update a forum module. 2605 $sink = $this->redirectEvents(); 2606 $modinfo = $this->update_specific_module_test('forum'); 2607 $events = $sink->get_events(); 2608 $eventscount = 0; 2609 $sink->close(); 2610 2611 $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST); 2612 $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST); 2613 2614 // Validate event data. 2615 foreach ($events as $event) { 2616 if ($event instanceof \core\event\course_module_updated) { 2617 $eventscount++; 2618 2619 $this->assertEquals($cm->id, $event->objectid); 2620 $this->assertEquals($USER->id, $event->userid); 2621 $this->assertEquals('course_modules', $event->objecttable); 2622 $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id)); 2623 $this->assertEquals($url, $event->get_url()); 2624 2625 // Test legacy data. 2626 $this->assertSame('mod_updated', $event->get_legacy_eventname()); 2627 $eventdata = new stdClass(); 2628 $eventdata->modulename = 'forum'; 2629 $eventdata->name = $mod->name; 2630 $eventdata->cmid = $cm->id; 2631 $eventdata->courseid = $cm->course; 2632 $eventdata->userid = $USER->id; 2633 $this->assertEventLegacyData($eventdata, $event); 2634 2635 $arr = array( 2636 array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"), 2637 array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id) 2638 ); 2639 $this->assertEventLegacyLogData($arr, $event); 2640 $this->assertEventContextNotUsed($event); 2641 } 2642 } 2643 2644 // Only one \core\event\course_module_updated event should be triggered. 2645 $this->assertEquals(1, $eventscount); 2646 } 2647 2648 /** 2649 * Tests for create_from_cm method. 2650 */ 2651 public function test_course_module_create_from_cm() { 2652 $this->resetAfterTest(); 2653 $this->setAdminUser(); 2654 2655 // Create course and modules. 2656 $course =