See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 58 /** 59 * Course related unit tests 60 * 61 * @package core_course 62 * @category test 63 * @copyright 2012 Petr Skoda {@link http://skodak.org} 64 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 65 */ 66 class courselib_test extends advanced_testcase { 67 68 /** 69 * Load required libraries and fixtures. 70 */ 71 public static function setUpBeforeClass(): void { 72 global $CFG; 73 74 require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php'); 75 require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php'); 76 } 77 78 /** 79 * Set forum specific test values for calling create_module(). 80 * 81 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference. 82 */ 83 private function forum_create_set_values(&$moduleinfo) { 84 // Completion specific to forum - optional. 85 $moduleinfo->completionposts = 3; 86 $moduleinfo->completiondiscussions = 1; 87 $moduleinfo->completionreplies = 2; 88 89 // Specific values to the Forum module. 90 $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE; 91 $moduleinfo->type = 'single'; 92 $moduleinfo->trackingtype = FORUM_TRACKING_FORCED; 93 $moduleinfo->maxbytes = 10240; 94 $moduleinfo->maxattachments = 2; 95 96 // Post threshold for blocking - specific to forum. 97 $moduleinfo->blockperiod = 60*60*24; 98 $moduleinfo->blockafter = 10; 99 $moduleinfo->warnafter = 5; 100 101 // Grading of whole forum settings. 102 $moduleinfo->grade_forum = 0; 103 } 104 105 /** 106 * Execute test asserts on the saved DB data by create_module($forum). 107 * 108 * @param object $moduleinfo - the specific forum values that were used to create a forum. 109 * @param object $dbmodinstance - the DB values of the created forum. 110 */ 111 private function forum_create_run_asserts($moduleinfo, $dbmodinstance) { 112 // Compare values specific to forums. 113 $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe); 114 $this->assertEquals($moduleinfo->type, $dbmodinstance->type); 115 $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed); 116 $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts); 117 $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions); 118 $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies); 119 $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale); 120 $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart); 121 $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish); 122 $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype); 123 $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles); 124 $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype); 125 $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes); 126 $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments); 127 $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod); 128 $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter); 129 $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter); 130 } 131 132 /** 133 * Set assign module specific test values for calling create_module(). 134 * 135 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference. 136 */ 137 private function assign_create_set_values(&$moduleinfo) { 138 // Specific values to the Assign module. 139 $moduleinfo->alwaysshowdescription = true; 140 $moduleinfo->submissiondrafts = true; 141 $moduleinfo->requiresubmissionstatement = true; 142 $moduleinfo->sendnotifications = true; 143 $moduleinfo->sendlatenotifications = true; 144 $moduleinfo->duedate = time() + (7 * 24 * 3600); 145 $moduleinfo->cutoffdate = time() + (7 * 24 * 3600); 146 $moduleinfo->gradingduedate = time() + (7 * 24 * 3600); 147 $moduleinfo->allowsubmissionsfromdate = time(); 148 $moduleinfo->teamsubmission = true; 149 $moduleinfo->requireallteammemberssubmit = true; 150 $moduleinfo->teamsubmissiongroupingid = true; 151 $moduleinfo->blindmarking = true; 152 $moduleinfo->markingworkflow = true; 153 $moduleinfo->markingallocation = true; 154 $moduleinfo->assignsubmission_onlinetext_enabled = true; 155 $moduleinfo->assignsubmission_file_enabled = true; 156 $moduleinfo->assignsubmission_file_maxfiles = 1; 157 $moduleinfo->assignsubmission_file_maxsizebytes = 1000000; 158 $moduleinfo->assignsubmission_comments_enabled = true; 159 $moduleinfo->assignfeedback_comments_enabled = true; 160 $moduleinfo->assignfeedback_offline_enabled = true; 161 $moduleinfo->assignfeedback_file_enabled = true; 162 163 // Advanced grading. 164 $gradingmethods = grading_manager::available_methods(); 165 $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods)); 166 } 167 168 /** 169 * Execute test asserts on the saved DB data by create_module($assign). 170 * 171 * @param object $moduleinfo - the specific assign module values that were used to create an assign module. 172 * @param object $dbmodinstance - the DB values of the created assign module. 173 */ 174 private function assign_create_run_asserts($moduleinfo, $dbmodinstance) { 175 global $DB; 176 177 $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription); 178 $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts); 179 $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement); 180 $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications); 181 $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate); 182 $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate); 183 $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate); 184 $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission); 185 $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit); 186 $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid); 187 $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking); 188 $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow); 189 $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation); 190 // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries. 191 192 // Advanced grading. 193 $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id); 194 $contextmodule = context_module::instance($cm->id); 195 $advancedgradingmethod = $DB->get_record('grading_areas', 196 array('contextid' => $contextmodule->id, 197 'activemethod' => $moduleinfo->advancedgradingmethod_submissions)); 198 $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod); 199 } 200 201 /** 202 * Run some asserts test for a specific module for the function create_module(). 203 * 204 * The function has been created (and is called) for $this->test_create_module(). 205 * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts. 206 * So if you want, you can overwrite the default values/asserts in the respective functions. 207 * @param string $modulename Name of the module ('forum', 'assign', 'book'...). 208 */ 209 private function create_specific_module_test($modulename) { 210 global $DB, $CFG; 211 212 $this->resetAfterTest(true); 213 214 $this->setAdminUser(); 215 216 // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard. 217 require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php'); 218 219 // Enable avaibility. 220 // If not enabled all conditional fields will be ignored. 221 set_config('enableavailability', 1); 222 223 // Enable course completion. 224 // If not enabled all completion settings will be ignored. 225 set_config('enablecompletion', COMPLETION_ENABLED); 226 227 // Enable forum RSS feeds. 228 set_config('enablerssfeeds', 1); 229 set_config('forum_enablerssfeeds', 1); 230 231 $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED), 232 array('createsections'=>true)); 233 234 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 235 236 // Create assign module instance for test. 237 $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); 238 $params['course'] = $course->id; 239 $instance = $generator->create_instance($params); 240 $assigncm = get_coursemodule_from_instance('assign', $instance->id); 241 242 // Module test values. 243 $moduleinfo = new stdClass(); 244 245 // Always mandatory generic values to any module. 246 $moduleinfo->modulename = $modulename; 247 $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database. 248 $moduleinfo->course = $course->id; 249 $moduleinfo->groupingid = $grouping->id; 250 $moduleinfo->visible = true; 251 $moduleinfo->visibleoncoursepage = true; 252 253 // Sometimes optional generic values for some modules. 254 $moduleinfo->name = 'My test module'; 255 $moduleinfo->showdescription = 1; // standard boolean 256 require_once($CFG->libdir . '/gradelib.php'); 257 $gradecats = grade_get_categories_menu($moduleinfo->course, false); 258 $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats 259 $moduleinfo->gradecat = $gradecatid; 260 $moduleinfo->groupmode = VISIBLEGROUPS; 261 $moduleinfo->cmidnumber = 'idnumber_XXX'; 262 263 // Completion common to all module. 264 $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC; 265 $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED; 266 $moduleinfo->completiongradeitemnumber = 1; 267 $moduleinfo->completionpassgrade = 0; 268 $moduleinfo->completionexpected = time() + (7 * 24 * 3600); 269 270 // Conditional activity. 271 $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' . 272 '{"type":"date","d":">=","t":' . time() . '},' . 273 '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' . 274 ']}'; 275 $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself. 276 $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80)); 277 $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@')); 278 $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none 279 280 // Grading and Advanced grading. 281 require_once($CFG->dirroot . '/rating/lib.php'); 282 $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE; 283 $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number. 284 $moduleinfo->assesstimestart = time(); 285 $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600); 286 287 // RSS. 288 $moduleinfo->rsstype = 2; 289 $moduleinfo->rssarticles = 10; 290 291 // Optional intro editor (depends of module). 292 $draftid_editor = 0; 293 file_prepare_draft_area($draftid_editor, null, null, null, null); 294 $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor); 295 296 // Following is the advanced grading method area called 'submissions' for the 'assign' module. 297 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 298 $moduleinfo->grade = 100; 299 } 300 301 // Plagiarism form values. 302 // No plagiarism plugin installed by default. Use this space to make your own test. 303 304 // Values specific to the module. 305 $modulesetvalues = $modulename.'_create_set_values'; 306 $this->$modulesetvalues($moduleinfo); 307 308 // Create the module. 309 $result = create_module($moduleinfo); 310 311 // Retrieve the module info. 312 $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance)); 313 $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance); 314 // We passed the course section number to create_courses but $dbcm contain the section id. 315 // We need to retrieve the db course section number. 316 $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section)); 317 // Retrieve the grade item. 318 $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course, 319 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename)); 320 321 // Compare the values common to all module instances. 322 $this->assertEquals($moduleinfo->modulename, $dbcm->modname); 323 $this->assertEquals($moduleinfo->section, $section->section); 324 $this->assertEquals($moduleinfo->course, $dbcm->course); 325 $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid); 326 $this->assertEquals($moduleinfo->visible, $dbcm->visible); 327 $this->assertEquals($moduleinfo->completion, $dbcm->completion); 328 $this->assertEquals($moduleinfo->completionview, $dbcm->completionview); 329 $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber); 330 $this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade); 331 $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected); 332 $this->assertEquals($moduleinfo->availability, $dbcm->availability); 333 $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription); 334 $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode); 335 $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber); 336 $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid); 337 338 // Optional grade testing. 339 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 340 $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade); 341 } 342 343 // Some optional (but quite common) to some module. 344 $this->assertEquals($moduleinfo->name, $dbmodinstance->name); 345 $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro); 346 $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat); 347 348 // Test specific to the module. 349 $modulerunasserts = $modulename.'_create_run_asserts'; 350 $this->$modulerunasserts($moduleinfo, $dbmodinstance); 351 return $moduleinfo; 352 } 353 354 /** 355 * Create module associated blog and tags. 356 * 357 * @param object $course Course. 358 * @param object $modulecontext The context of the module. 359 */ 360 private function create_module_asscociated_blog($course, $modulecontext) { 361 global $DB, $CFG; 362 363 // Create default group. 364 $group = new stdClass(); 365 $group->courseid = $course->id; 366 $group->name = 'Group'; 367 $group->id = $DB->insert_record('groups', $group); 368 369 // Create default user. 370 $user = $this->getDataGenerator()->create_user(array( 371 'username' => 'testuser', 372 'firstname' => 'Firsname', 373 'lastname' => 'Lastname' 374 )); 375 376 // Create default post. 377 $post = new stdClass(); 378 $post->userid = $user->id; 379 $post->groupid = $group->id; 380 $post->content = 'test post content text'; 381 $post->module = 'blog'; 382 $post->id = $DB->insert_record('post', $post); 383 384 // Create default tag. 385 $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id, 386 'rawname' => 'Testtagname', 'isstandard' => 1)); 387 // Apply the tag to the blog. 388 $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user', 389 'component' => 'core', 'itemid' => $post->id, 'ordering' => 0)); 390 391 require_once($CFG->dirroot . '/blog/locallib.php'); 392 $blog = new blog_entry($post->id); 393 $blog->add_association($modulecontext->id); 394 395 return $blog; 396 } 397 398 /** 399 * Test create_module() for multiple modules defined in the $modules array (first declaration of the function). 400 */ 401 public function test_create_module() { 402 // Add the module name you want to test here. 403 // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts(). 404 $modules = array('forum', 'assign'); 405 // Run all tests. 406 foreach ($modules as $modulename) { 407 $this->create_specific_module_test($modulename); 408 } 409 } 410 411 /** 412 * Test update_module() for multiple modules defined in the $modules array (first declaration of the function). 413 */ 414 public function test_update_module() { 415 // Add the module name you want to test here. 416 // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts(). 417 $modules = array('forum'); 418 // Run all tests. 419 foreach ($modules as $modulename) { 420 $this->update_specific_module_test($modulename); 421 } 422 } 423 424 /** 425 * Set forum specific test values for calling update_module(). 426 * 427 * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference. 428 */ 429 private function forum_update_set_values(&$moduleinfo) { 430 // Completion specific to forum - optional. 431 $moduleinfo->completionposts = 3; 432 $moduleinfo->completiondiscussions = 1; 433 $moduleinfo->completionreplies = 2; 434 435 // Specific values to the Forum module. 436 $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE; 437 $moduleinfo->type = 'single'; 438 $moduleinfo->trackingtype = FORUM_TRACKING_FORCED; 439 $moduleinfo->maxbytes = 10240; 440 $moduleinfo->maxattachments = 2; 441 442 // Post threshold for blocking - specific to forum. 443 $moduleinfo->blockperiod = 60*60*24; 444 $moduleinfo->blockafter = 10; 445 $moduleinfo->warnafter = 5; 446 447 // Grading of whole forum settings. 448 $moduleinfo->grade_forum = 0; 449 } 450 451 /** 452 * Execute test asserts on the saved DB data by update_module($forum). 453 * 454 * @param object $moduleinfo - the specific forum values that were used to update a forum. 455 * @param object $dbmodinstance - the DB values of the updated forum. 456 */ 457 private function forum_update_run_asserts($moduleinfo, $dbmodinstance) { 458 // Compare values specific to forums. 459 $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe); 460 $this->assertEquals($moduleinfo->type, $dbmodinstance->type); 461 $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed); 462 $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts); 463 $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions); 464 $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies); 465 $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale); 466 $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart); 467 $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish); 468 $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype); 469 $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles); 470 $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype); 471 $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes); 472 $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments); 473 $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod); 474 $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter); 475 $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter); 476 } 477 478 479 480 /** 481 * Test a specific type of module. 482 * 483 * @param string $modulename - the module name to test 484 */ 485 private function update_specific_module_test($modulename) { 486 global $DB, $CFG; 487 488 $this->resetAfterTest(true); 489 490 $this->setAdminUser(); 491 492 // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard. 493 require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php'); 494 495 // Enable avaibility. 496 // If not enabled all conditional fields will be ignored. 497 set_config('enableavailability', 1); 498 499 // Enable course completion. 500 // If not enabled all completion settings will be ignored. 501 set_config('enablecompletion', COMPLETION_ENABLED); 502 503 // Enable forum RSS feeds. 504 set_config('enablerssfeeds', 1); 505 set_config('forum_enablerssfeeds', 1); 506 507 $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED), 508 array('createsections'=>true)); 509 510 $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id)); 511 512 // Create assign module instance for testing gradeitem. 513 $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); 514 $params['course'] = $course->id; 515 $instance = $generator->create_instance($params); 516 $assigncm = get_coursemodule_from_instance('assign', $instance->id); 517 518 // Create the test forum to update. 519 $initvalues = new stdClass(); 520 $initvalues->introformat = FORMAT_HTML; 521 $initvalues->course = $course->id; 522 $forum = self::getDataGenerator()->create_module('forum', $initvalues); 523 524 // Retrieve course module. 525 $cm = get_coursemodule_from_instance('forum', $forum->id); 526 527 // Module test values. 528 $moduleinfo = new stdClass(); 529 530 // Always mandatory generic values to any module. 531 $moduleinfo->coursemodule = $cm->id; 532 $moduleinfo->modulename = $modulename; 533 $moduleinfo->course = $course->id; 534 $moduleinfo->groupingid = $grouping->id; 535 $moduleinfo->visible = true; 536 $moduleinfo->visibleoncoursepage = true; 537 538 // Sometimes optional generic values for some modules. 539 $moduleinfo->name = 'My test module'; 540 $moduleinfo->showdescription = 1; // standard boolean 541 require_once($CFG->libdir . '/gradelib.php'); 542 $gradecats = grade_get_categories_menu($moduleinfo->course, false); 543 $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats 544 $moduleinfo->gradecat = $gradecatid; 545 $moduleinfo->groupmode = VISIBLEGROUPS; 546 $moduleinfo->cmidnumber = 'idnumber_XXX'; 547 548 // Completion common to all module. 549 $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC; 550 $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED; 551 $moduleinfo->completiongradeitemnumber = 1; 552 $moduleinfo->completionpassgrade = 0; 553 $moduleinfo->completionexpected = time() + (7 * 24 * 3600); 554 $moduleinfo->completionunlocked = 1; 555 556 // Conditional activity. 557 $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself. 558 $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json( 559 array(\availability_date\condition::get_json('>=', time()), 560 \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)), 561 \availability_grade\condition::get_json($coursegradeitem->id, 10, 80), 562 \availability_profile\condition::get_json(false, 'email', 'contains', '@'), 563 \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&')); 564 565 // Grading and Advanced grading. 566 require_once($CFG->dirroot . '/rating/lib.php'); 567 $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE; 568 $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number. 569 $moduleinfo->assesstimestart = time(); 570 $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600); 571 572 // RSS. 573 $moduleinfo->rsstype = 2; 574 $moduleinfo->rssarticles = 10; 575 576 // Optional intro editor (depends of module). 577 $draftid_editor = 0; 578 file_prepare_draft_area($draftid_editor, null, null, null, null); 579 $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor); 580 581 // Following is the advanced grading method area called 'submissions' for the 'assign' module. 582 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 583 $moduleinfo->grade = 100; 584 } 585 // Plagiarism form values. 586 // No plagiarism plugin installed by default. Use this space to make your own test. 587 588 // Values specific to the module. 589 $modulesetvalues = $modulename.'_update_set_values'; 590 $this->$modulesetvalues($moduleinfo); 591 592 // Create the module. 593 $result = update_module($moduleinfo); 594 595 // Retrieve the module info. 596 $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance)); 597 $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance); 598 // Retrieve the grade item. 599 $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course, 600 'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename)); 601 602 // Compare the values common to all module instances. 603 $this->assertEquals($moduleinfo->modulename, $dbcm->modname); 604 $this->assertEquals($moduleinfo->course, $dbcm->course); 605 $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid); 606 $this->assertEquals($moduleinfo->visible, $dbcm->visible); 607 $this->assertEquals($moduleinfo->completion, $dbcm->completion); 608 $this->assertEquals($moduleinfo->completionview, $dbcm->completionview); 609 $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber); 610 $this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade); 611 $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected); 612 $this->assertEquals($moduleinfo->availability, $dbcm->availability); 613 $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription); 614 $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode); 615 $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber); 616 $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid); 617 618 // Optional grade testing. 619 if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) { 620 $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade); 621 } 622 623 // Some optional (but quite common) to some module. 624 $this->assertEquals($moduleinfo->name, $dbmodinstance->name); 625 $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro); 626 $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat); 627 628 // Test specific to the module. 629 $modulerunasserts = $modulename.'_update_run_asserts'; 630 $this->$modulerunasserts($moduleinfo, $dbmodinstance); 631 return $moduleinfo; 632 } 633 634 /** 635 * Data provider for course_delete module 636 * 637 * @return array An array of arrays contain test data 638 */ 639 public function provider_course_delete_module() { 640 $data = array(); 641 642 $data['assign'] = array('assign', array('duedate' => time())); 643 $data['quiz'] = array('quiz', array('duedate' => time())); 644 645 return $data; 646 } 647 648 /** 649 * Test the create_course function 650 */ 651 public function test_create_course() { 652 global $DB; 653 $this->resetAfterTest(true); 654 $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0"); 655 656 $course = new stdClass(); 657 $course->fullname = 'Apu loves Unit Təsts'; 658 $course->shortname = 'Spread the lŭve'; 659 $course->idnumber = '123'; 660 $course->summary = 'Awesome!'; 661 $course->summaryformat = FORMAT_PLAIN; 662 $course->format = 'topics'; 663 $course->newsitems = 0; 664 $course->category = $defaultcategory; 665 $original = (array) $course; 666 667 $created = create_course($course); 668 $context = context_course::instance($created->id); 669 670 // Compare original and created. 671 $this->assertEquals($original, array_intersect_key((array) $created, $original)); 672 673 // Ensure default section is created. 674 $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0)); 675 $this->assertTrue($sectioncreated); 676 677 // Ensure that the shortname isn't duplicated. 678 try { 679 $created = create_course($course); 680 $this->fail('Exception expected'); 681 } catch (moodle_exception $e) { 682 $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage()); 683 } 684 685 // Ensure that the idnumber isn't duplicated. 686 $course->shortname .= '1'; 687 try { 688 $created = create_course($course); 689 $this->fail('Exception expected'); 690 } catch (moodle_exception $e) { 691 $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage()); 692 } 693 } 694 695 public function test_create_course_with_generator() { 696 global $DB; 697 $this->resetAfterTest(true); 698 $course = $this->getDataGenerator()->create_course(); 699 700 // Ensure default section is created. 701 $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0)); 702 $this->assertTrue($sectioncreated); 703 } 704 705 public function test_create_course_sections() { 706 global $DB; 707 $this->resetAfterTest(true); 708 709 $numsections = 5; 710 $course = $this->getDataGenerator()->create_course( 711 array('shortname' => 'GrowingCourse', 712 'fullname' => 'Growing Course', 713 'numsections' => $numsections), 714 array('createsections' => true)); 715 716 // Ensure all 6 (0-5) sections were created and course content cache works properly 717 $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all()); 718 $this->assertEquals(range(0, $numsections), $sectionscreated); 719 720 // this will do nothing, section already exists 721 $this->assertFalse(course_create_sections_if_missing($course, $numsections)); 722 723 // this will create new section 724 $this->assertTrue(course_create_sections_if_missing($course, $numsections + 1)); 725 726 // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly 727 $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all()); 728 $this->assertEquals(range(0, $numsections + 1), $sectionscreated); 729 } 730 731 public function test_update_course() { 732 global $DB; 733 734 $this->resetAfterTest(); 735 736 $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0'); 737 738 $course = new stdClass(); 739 $course->fullname = 'Apu loves Unit Təsts'; 740 $course->shortname = 'test1'; 741 $course->idnumber = '1'; 742 $course->summary = 'Awesome!'; 743 $course->summaryformat = FORMAT_PLAIN; 744 $course->format = 'topics'; 745 $course->newsitems = 0; 746 $course->numsections = 5; 747 $course->category = $defaultcategory; 748 749 $created = create_course($course); 750 // Ensure the checks only work on idnumber/shortname that are not already ours. 751 update_course($created); 752 753 $course->shortname = 'test2'; 754 $course->idnumber = '2'; 755 756 $created2 = create_course($course); 757 758 // Test duplicate idnumber. 759 $created2->idnumber = '1'; 760 try { 761 update_course($created2); 762 $this->fail('Expected exception when trying to update a course with duplicate idnumber'); 763 } catch (moodle_exception $e) { 764 $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage()); 765 } 766 767 // Test duplicate shortname. 768 $created2->idnumber = '2'; 769 $created2->shortname = 'test1'; 770 try { 771 update_course($created2); 772 $this->fail('Expected exception when trying to update a course with a duplicate shortname'); 773 } catch (moodle_exception $e) { 774 $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage()); 775 } 776 } 777 778 public function test_update_course_section_time_modified() { 779 global $DB; 780 781 $this->resetAfterTest(); 782 783 // Create the course with sections. 784 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true)); 785 $sections = $DB->get_records('course_sections', array('course' => $course->id)); 786 787 // Get the last section's time modified value. 788 $section = array_pop($sections); 789 $oldtimemodified = $section->timemodified; 790 791 // Update the section. 792 $this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp. 793 course_update_section($course, $section, array()); 794 795 // Check that the time has changed. 796 $section = $DB->get_record('course_sections', array('id' => $section->id)); 797 $newtimemodified = $section->timemodified; 798 $this->assertGreaterThan($oldtimemodified, $newtimemodified); 799 } 800 801 /** 802 * Relative dates mode settings provider for course creation. 803 */ 804 public function create_course_relative_dates_provider() { 805 return [ 806 [0, 0, 0], 807 [0, 1, 0], 808 [1, 0, 0], 809 [1, 1, 1], 810 ]; 811 } 812 813 /** 814 * Test create_course by attempting to change the relative dates mode. 815 * 816 * @dataProvider create_course_relative_dates_provider 817 * @param int $setting The value for the 'enablecourserelativedates' admin setting. 818 * @param int $mode The value for the course's 'relativedatesmode' field. 819 * @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation. 820 */ 821 public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue) { 822 global $DB; 823 824 $this->resetAfterTest(); 825 826 set_config('enablecourserelativedates', $setting); 827 828 // Generate a course with relative dates mode set to $mode. 829 $course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]); 830 831 // Verify that the relative dates match what's expected. 832 $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]); 833 $this->assertEquals($expectedvalue, $relativedatesmode); 834 } 835 836 /** 837 * Test update_course by attempting to change the relative dates mode. 838 */ 839 public function test_relative_dates_mode_for_course_update() { 840 global $DB; 841 842 $this->resetAfterTest(); 843 844 set_config('enablecourserelativedates', 1); 845 846 // Generate a course with relative dates mode set to 1. 847 $course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]); 848 849 // Attempt to update the course with a changed relativedatesmode. 850 $course->relativedatesmode = 0; 851 update_course($course); 852 853 // Verify that the relative dates mode has not changed. 854 $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]); 855 $this->assertEquals(1, $relativedatesmode); 856 } 857 858 public function test_course_add_cm_to_section() { 859 global $DB; 860 $this->resetAfterTest(true); 861 862 // Create course with 1 section. 863 $course = $this->getDataGenerator()->create_course( 864 array('shortname' => 'GrowingCourse', 865 'fullname' => 'Growing Course', 866 'numsections' => 1), 867 array('createsections' => true)); 868 869 // Trash modinfo. 870 rebuild_course_cache($course->id, true); 871 872 // Create some cms for testing. 873 $cmids = array(); 874 for ($i=0; $i<4; $i++) { 875 $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id)); 876 } 877 878 // Add it to section that exists. 879 course_add_cm_to_section($course, $cmids[0], 1); 880 881 // Check it got added to sequence. 882 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1)); 883 $this->assertEquals($cmids[0], $sequence); 884 885 // Add a second, this time using courseid variant of parameters. 886 $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id)); 887 course_add_cm_to_section($course->id, $cmids[1], 1); 888 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1)); 889 $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence); 890 891 // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly). 892 $newcacherev = $DB->get_field('course', 'cacherev', ['id' => $course->id]); 893 $this->assertGreaterThan($coursecacherev, $newcacherev); 894 $this->assertEmpty(cache::make('core', 'coursemodinfo')->get_versioned($course->id, $newcacherev)); 895 896 // Add one to section that doesn't exist (this might rebuild modinfo). 897 course_add_cm_to_section($course, $cmids[2], 2); 898 $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id))); 899 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2)); 900 $this->assertEquals($cmids[2], $sequence); 901 902 // Add using the 'before' option. 903 course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]); 904 $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id))); 905 $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2)); 906 $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence); 907 } 908 909 public function test_reorder_sections() { 910 global $DB; 911 $this->resetAfterTest(true); 912 913 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 914 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 915 $oldsections = array(); 916 $sections = array(); 917 foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) { 918 $oldsections[$section->section] = $section->id; 919 $sections[$section->id] = $section->section; 920 } 921 ksort($oldsections); 922 923 $neworder = reorder_sections($sections, 2, 4); 924 $neworder = array_keys($neworder); 925 $this->assertEquals($oldsections[0], $neworder[0]); 926 $this->assertEquals($oldsections[1], $neworder[1]); 927 $this->assertEquals($oldsections[2], $neworder[4]); 928 $this->assertEquals($oldsections[3], $neworder[2]); 929 $this->assertEquals($oldsections[4], $neworder[3]); 930 $this->assertEquals($oldsections[5], $neworder[5]); 931 $this->assertEquals($oldsections[6], $neworder[6]); 932 933 $neworder = reorder_sections($sections, 4, 2); 934 $neworder = array_keys($neworder); 935 $this->assertEquals($oldsections[0], $neworder[0]); 936 $this->assertEquals($oldsections[1], $neworder[1]); 937 $this->assertEquals($oldsections[2], $neworder[3]); 938 $this->assertEquals($oldsections[3], $neworder[4]); 939 $this->assertEquals($oldsections[4], $neworder[2]); 940 $this->assertEquals($oldsections[5], $neworder[5]); 941 $this->assertEquals($oldsections[6], $neworder[6]); 942 943 $neworder = reorder_sections(1, 2, 4); 944 $this->assertFalse($neworder); 945 } 946 947 public function test_move_section_down() { 948 global $DB; 949 $this->resetAfterTest(true); 950 951 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 952 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 953 $oldsections = array(); 954 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 955 $oldsections[$section->section] = $section->id; 956 } 957 ksort($oldsections); 958 959 // Test move section down.. 960 move_section_to($course, 2, 4); 961 $sections = array(); 962 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 963 $sections[$section->section] = $section->id; 964 } 965 ksort($sections); 966 967 $this->assertEquals($oldsections[0], $sections[0]); 968 $this->assertEquals($oldsections[1], $sections[1]); 969 $this->assertEquals($oldsections[2], $sections[4]); 970 $this->assertEquals($oldsections[3], $sections[2]); 971 $this->assertEquals($oldsections[4], $sections[3]); 972 $this->assertEquals($oldsections[5], $sections[5]); 973 $this->assertEquals($oldsections[6], $sections[6]); 974 } 975 976 public function test_move_section_up() { 977 global $DB; 978 $this->resetAfterTest(true); 979 980 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 981 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 982 $oldsections = array(); 983 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 984 $oldsections[$section->section] = $section->id; 985 } 986 ksort($oldsections); 987 988 // Test move section up.. 989 move_section_to($course, 6, 4); 990 $sections = array(); 991 foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) { 992 $sections[$section->section] = $section->id; 993 } 994 ksort($sections); 995 996 $this->assertEquals($oldsections[0], $sections[0]); 997 $this->assertEquals($oldsections[1], $sections[1]); 998 $this->assertEquals($oldsections[2], $sections[2]); 999 $this->assertEquals($oldsections[3], $sections[3]); 1000 $this->assertEquals($oldsections[4], $sections[5]); 1001 $this->assertEquals($oldsections[5], $sections[6]); 1002 $this->assertEquals($oldsections[6], $sections[4]); 1003 } 1004 1005 public function test_move_section_marker() { 1006 global $DB; 1007 $this->resetAfterTest(true); 1008 1009 $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true)); 1010 $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true)); 1011 1012 // Set course marker to the section we are going to move.. 1013 course_set_marker($course->id, 2); 1014 // Verify that the course marker is set correctly. 1015 $course = $DB->get_record('course', array('id' => $course->id)); 1016 $this->assertEquals(2, $course->marker); 1017 1018 // Test move the marked section down.. 1019 move_section_to($course, 2, 4); 1020 1021 // Verify that the course marker has been moved along with the section.. 1022 $course = $DB->get_record('course', array('id' => $course->id)); 1023 $this->assertEquals(4, $course->marker); 1024 1025 // Test move the marked section up.. 1026 move_section_to($course, 4, 3); 1027 1028 // Verify that the course marker has been moved along with the section.. 1029 $course = $DB->get_record('course', array('id' => $course->id)); 1030 $this->assertEquals(3, $course->marker); 1031 1032 // Test moving a non-marked section above the marked section.. 1033 move_section_to($course, 4, 2); 1034 1035 // Verify that the course marker has been moved down to accomodate.. 1036 $course = $DB->get_record('course', array('id' => $course->id)); 1037 $this->assertEquals(4, $course->marker); 1038 1039 // Test moving a non-marked section below the marked section.. 1040 move_section_to($course, 3, 6); 1041 1042 // Verify that the course marker has been up to accomodate.. 1043 $course = $DB->get_record('course', array('id' => $course->id)); 1044 $this->assertEquals(3, $course->marker); 1045 } 1046 1047 /** 1048 * Test move_section_to method with caching 1049 * 1050 * @covers ::move_section_to 1051 * @return void 1052 */ 1053 public function test_move_section_with_section_cache(): void { 1054 $this->resetAfterTest(); 1055 $this->setAdminUser(); 1056 $cache = cache::make('core', 'coursemodinfo'); 1057 1058 // Generate the course and pre-requisite module. 1059 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]); 1060 // Reset course cache. 1061 rebuild_course_cache($course->id, true); 1062 1063 // Build course cache. 1064 get_fast_modinfo($course->id); 1065 // Get the course modinfo cache. 1066 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1067 // Get the section cache. 1068 $sectioncaches = $coursemodinfo->sectioncache; 1069 1070 // Make sure that we will have 4 section caches here. 1071 $this->assertCount(4, $sectioncaches); 1072 $this->assertArrayHasKey(0, $sectioncaches); 1073 $this->assertArrayHasKey(1, $sectioncaches); 1074 $this->assertArrayHasKey(2, $sectioncaches); 1075 $this->assertArrayHasKey(3, $sectioncaches); 1076 1077 // Move section. 1078 move_section_to($course, 2, 3); 1079 // Get the course modinfo cache. 1080 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1081 // Get the section cache. 1082 $sectioncaches = $coursemodinfo->sectioncache; 1083 1084 // Make sure that we will have 2 section caches left. 1085 $this->assertCount(2, $sectioncaches); 1086 $this->assertArrayHasKey(0, $sectioncaches); 1087 $this->assertArrayHasKey(1, $sectioncaches); 1088 $this->assertArrayNotHasKey(2, $sectioncaches); 1089 $this->assertArrayNotHasKey(3, $sectioncaches); 1090 } 1091 1092 /** 1093 * Test move_section_to method. 1094 * Make sure that we only update the moving sections, not all the sections in the current course. 1095 * 1096 * @covers ::move_section_to 1097 * @return void 1098 */ 1099 public function test_move_section_to(): void { 1100 global $DB, $CFG; 1101 $this->resetAfterTest(); 1102 $this->setAdminUser(); 1103 1104 // Generate the course and pre-requisite module. 1105 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]); 1106 1107 ob_start(); 1108 $DB->set_debug(true); 1109 // Move section. 1110 move_section_to($course, 2, 3); 1111 $DB->set_debug(false); 1112 $debuginfo = ob_get_contents(); 1113 ob_end_clean(); 1114 $sectionmovequerycount = substr_count($debuginfo, 'UPDATE ' . $CFG->phpunit_prefix . 'course_sections SET'); 1115 // We are updating the course_section table in steps to avoid breaking database uniqueness constraint. 1116 // So the queries will be doubled. See: course/lib.php:1423 1117 // Make sure that we only need 4 queries to update the position of section 2 and section 3. 1118 $this->assertEquals(4, $sectionmovequerycount); 1119 } 1120 1121 public function test_course_can_delete_section() { 1122 global $DB; 1123 $this->resetAfterTest(true); 1124 1125 $generator = $this->getDataGenerator(); 1126 1127 $courseweeks = $generator->create_course( 1128 array('numsections' => 5, 'format' => 'weeks'), 1129 array('createsections' => true)); 1130 $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1)); 1131 $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2)); 1132 1133 $coursetopics = $generator->create_course( 1134 array('numsections' => 5, 'format' => 'topics'), 1135 array('createsections' => true)); 1136 1137 $coursesingleactivity = $generator->create_course( 1138 array('format' => 'singleactivity'), 1139 array('createsections' => true)); 1140 1141 // Enrol student and teacher. 1142 $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); 1143 $student = $generator->create_user(); 1144 $teacher = $generator->create_user(); 1145 1146 $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']); 1147 $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']); 1148 1149 $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']); 1150 $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']); 1151 1152 $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']); 1153 $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']); 1154 1155 // Teacher should be able to delete sections (except for 0) in topics and weeks format. 1156 $this->setUser($teacher); 1157 1158 // For topics and weeks formats will return false for section 0 and true for any other section. 1159 $this->assertFalse(course_can_delete_section($courseweeks, 0)); 1160 $this->assertTrue(course_can_delete_section($courseweeks, 1)); 1161 1162 $this->assertFalse(course_can_delete_section($coursetopics, 0)); 1163 $this->assertTrue(course_can_delete_section($coursetopics, 1)); 1164 1165 // For singleactivity course format no section can be deleted. 1166 $this->assertFalse(course_can_delete_section($coursesingleactivity, 0)); 1167 $this->assertFalse(course_can_delete_section($coursesingleactivity, 1)); 1168 1169 // Now let's revoke a capability from teacher to manage activity in section 1. 1170 $modulecontext = context_module::instance($assign1->cmid); 1171 assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'], 1172 $modulecontext); 1173 $this->assertFalse(course_can_delete_section($courseweeks, 1)); 1174 $this->assertTrue(course_can_delete_section($courseweeks, 2)); 1175 1176 // Student does not have permissions to delete sections. 1177 $this->setUser($student); 1178 $this->assertFalse(course_can_delete_section($courseweeks, 1)); 1179 $this->assertFalse(course_can_delete_section($coursetopics, 1)); 1180 $this->assertFalse(course_can_delete_section($coursesingleactivity, 1)); 1181 } 1182 1183 public function test_course_delete_section() { 1184 global $DB; 1185 $this->resetAfterTest(true); 1186 1187 $generator = $this->getDataGenerator(); 1188 1189 $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'), 1190 array('createsections' => true)); 1191 $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0)); 1192 $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1)); 1193 $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2)); 1194 $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2)); 1195 $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3)); 1196 $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5)); 1197 $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6)); 1198 1199 $this->setAdminUser(); 1200 1201 // Attempt to delete non-existing section. 1202 $this->assertFalse(course_delete_section($course, 10, false)); 1203 $this->assertFalse(course_delete_section($course, 9, true)); 1204 1205 // Attempt to delete 0-section. 1206 $this->assertFalse(course_delete_section($course, 0, true)); 1207 $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid))); 1208 1209 // Delete last section. 1210 $this->assertTrue(course_delete_section($course, 6, true)); 1211 $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid))); 1212 $this->assertEquals(5, course_get_format($course)->get_last_section_number()); 1213 1214 // Delete empty section. 1215 $this->assertTrue(course_delete_section($course, 4, false)); 1216 $this->assertEquals(4, course_get_format($course)->get_last_section_number()); 1217 1218 // Delete section in the middle (2). 1219 $this->assertFalse(course_delete_section($course, 2, false)); 1220 $this->assertEquals(4, course_get_format($course)->get_last_section_number()); 1221 $this->assertTrue(course_delete_section($course, 2, true)); 1222 $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid))); 1223 $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid))); 1224 $this->assertEquals(3, course_get_format($course)->get_last_section_number()); 1225 $this->assertEquals(array(0 => array($assign0->cmid), 1226 1 => array($assign1->cmid), 1227 2 => array($assign3->cmid), 1228 3 => array($assign5->cmid)), get_fast_modinfo($course)->sections); 1229 1230 // Remove marked section. 1231 course_set_marker($course->id, 1); 1232 $this->assertTrue(course_get_format($course)->is_section_current(1)); 1233 $this->assertTrue(course_delete_section($course, 1, true)); 1234 $this->assertFalse(course_get_format($course)->is_section_current(1)); 1235 } 1236 1237 public function test_get_course_display_name_for_list() { 1238 global $CFG; 1239 $this->resetAfterTest(true); 1240 1241 $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life')); 1242 1243 $CFG->courselistshortnames = 0; 1244 $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course)); 1245 1246 $CFG->courselistshortnames = 1; 1247 $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course)); 1248 } 1249 1250 public function test_move_module_in_course() { 1251 global $DB; 1252 1253 $this->resetAfterTest(true); 1254 // Setup fixture 1255 $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true)); 1256 $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id)); 1257 1258 $cms = get_fast_modinfo($course)->get_cms(); 1259 $cm = reset($cms); 1260 1261 $newsection = get_fast_modinfo($course)->get_section_info(3); 1262 $oldsectionid = $cm->section; 1263 1264 // Perform the move 1265 moveto_module($cm, $newsection); 1266 1267 $cms = get_fast_modinfo($course)->get_cms(); 1268 $cm = reset($cms); 1269 1270 // Check that the cached modinfo contains the correct section info 1271 $modinfo = get_fast_modinfo($course); 1272 $this->assertTrue(empty($modinfo->sections[0])); 1273 $this->assertFalse(empty($modinfo->sections[3])); 1274 1275 // Check that the old section's sequence no longer contains this ID 1276 $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid)); 1277 $oldsequences = explode(',', $newsection->sequence); 1278 $this->assertFalse(in_array($cm->id, $oldsequences)); 1279 1280 // Check that the new section's sequence now contains this ID 1281 $newsection = $DB->get_record('course_sections', array('id' => $newsection->id)); 1282 $newsequences = explode(',', $newsection->sequence); 1283 $this->assertTrue(in_array($cm->id, $newsequences)); 1284 1285 // Check that the section number has been changed in the cm 1286 $this->assertEquals($newsection->id, $cm->section); 1287 1288 1289 // Perform a second move as some issues were only seen on the second move 1290 $newsection = get_fast_modinfo($course)->get_section_info(2); 1291 $oldsectionid = $cm->section; 1292 moveto_module($cm, $newsection); 1293 1294 $cms = get_fast_modinfo($course)->get_cms(); 1295 $cm = reset($cms); 1296 1297 // Check that the cached modinfo contains the correct section info 1298 $modinfo = get_fast_modinfo($course); 1299 $this->assertTrue(empty($modinfo->sections[0])); 1300 $this->assertFalse(empty($modinfo->sections[2])); 1301 1302 // Check that the old section's sequence no longer contains this ID 1303 $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid)); 1304 $oldsequences = explode(',', $newsection->sequence); 1305 $this->assertFalse(in_array($cm->id, $oldsequences)); 1306 1307 // Check that the new section's sequence now contains this ID 1308 $newsection = $DB->get_record('course_sections', array('id' => $newsection->id)); 1309 $newsequences = explode(',', $newsection->sequence); 1310 $this->assertTrue(in_array($cm->id, $newsequences)); 1311 } 1312 1313 public function test_module_visibility() { 1314 $this->setAdminUser(); 1315 $this->resetAfterTest(true); 1316 1317 // Create course and modules. 1318 $course = $this->getDataGenerator()->create_course(array('numsections' => 5)); 1319 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 1320 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id)); 1321 $modules = compact('forum', 'assign'); 1322 1323 // Hiding the modules. 1324 foreach ($modules as $mod) { 1325 set_coursemodule_visible($mod->cmid, 0); 1326 $this->check_module_visibility($mod, 0, 0); 1327 } 1328 1329 // Showing the modules. 1330 foreach ($modules as $mod) { 1331 set_coursemodule_visible($mod->cmid, 1); 1332 $this->check_module_visibility($mod, 1, 1); 1333 } 1334 } 1335 1336 /** 1337 * Test rebuildcache = false behaviour. 1338 * 1339 * When we pass rebuildcache = false to set_coursemodule_visible, the corusemodinfo cache will still contain 1340 * the original visibility until we trigger a rebuild. 1341 * 1342 * @return void 1343 * @covers ::set_coursemodule_visible 1344 */ 1345 public function test_module_visibility_no_rebuild(): void { 1346 $this->setAdminUser(); 1347 $this->resetAfterTest(true); 1348 1349 // Create course and modules. 1350 $course = $this->getDataGenerator()->create_course(['numsections' => 5]); 1351 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 1352 $assign = $this->getDataGenerator()->create_module('assign', ['duedate' => time(), 'course' => $course->id]); 1353 $modules = compact('forum', 'assign'); 1354 1355 // Hiding the modules. 1356 foreach ($modules as $mod) { 1357 set_coursemodule_visible($mod->cmid, 0, 1, false); 1358 // The modinfo cache still has the original visibility until we manually trigger a rebuild. 1359 $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid); 1360 $this->assertEquals(1, $cm->visible); 1361 } 1362 1363 rebuild_course_cache($course->id); 1364 1365 foreach ($modules as $mod) { 1366 $this->check_module_visibility($mod, 0, 0); 1367 } 1368 1369 // Showing the modules. 1370 foreach ($modules as $mod) { 1371 set_coursemodule_visible($mod->cmid, 1, 1, false); 1372 $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid); 1373 $this->assertEquals(0, $cm->visible); 1374 } 1375 1376 rebuild_course_cache($course->id); 1377 1378 foreach ($modules as $mod) { 1379 $this->check_module_visibility($mod, 1, 1); 1380 } 1381 } 1382 1383 public function test_section_visibility_events() { 1384 $this->setAdminUser(); 1385 $this->resetAfterTest(true); 1386 1387 $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true)); 1388 $sectionnumber = 1; 1389 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 1390 array('section' => $sectionnumber)); 1391 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 1392 'course' => $course->id), array('section' => $sectionnumber)); 1393 $sink = $this->redirectEvents(); 1394 set_section_visible($course->id, $sectionnumber, 0); 1395 $events = $sink->get_events(); 1396 1397 // Extract the number of events related to what we are testing, other events 1398 // such as course_section_updated could have been triggered. 1399 $count = 0; 1400 foreach ($events as $event) { 1401 if ($event instanceof \core\event\course_module_updated) { 1402 $count++; 1403 } 1404 } 1405 $this->assertSame(2, $count); 1406 $sink->close(); 1407 } 1408 1409 public function test_section_visibility() { 1410 $this->setAdminUser(); 1411 $this->resetAfterTest(true); 1412 1413 // Create course. 1414 $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true)); 1415 1416 $sink = $this->redirectEvents(); 1417 1418 // Testing an empty section. 1419 $sectionnumber = 1; 1420 set_section_visible($course->id, $sectionnumber, 0); 1421 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1422 $this->assertEquals($section_info->visible, 0); 1423 set_section_visible($course->id, $sectionnumber, 1); 1424 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1425 $this->assertEquals($section_info->visible, 1); 1426 1427 // Checking that an event was fired. 1428 $events = $sink->get_events(); 1429 $this->assertInstanceOf('\core\event\course_section_updated', $events[0]); 1430 $sink->close(); 1431 1432 // Testing a section with visible modules. 1433 $sectionnumber = 2; 1434 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 1435 array('section' => $sectionnumber)); 1436 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 1437 'course' => $course->id), array('section' => $sectionnumber)); 1438 $modules = compact('forum', 'assign'); 1439 set_section_visible($course->id, $sectionnumber, 0); 1440 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1441 $this->assertEquals($section_info->visible, 0); 1442 foreach ($modules as $mod) { 1443 $this->check_module_visibility($mod, 0, 1); 1444 } 1445 set_section_visible($course->id, $sectionnumber, 1); 1446 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1447 $this->assertEquals($section_info->visible, 1); 1448 foreach ($modules as $mod) { 1449 $this->check_module_visibility($mod, 1, 1); 1450 } 1451 1452 // Testing a section with hidden modules, which should stay hidden. 1453 $sectionnumber = 3; 1454 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 1455 array('section' => $sectionnumber)); 1456 $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 1457 'course' => $course->id), array('section' => $sectionnumber)); 1458 $modules = compact('forum', 'assign'); 1459 foreach ($modules as $mod) { 1460 set_coursemodule_visible($mod->cmid, 0); 1461 $this->check_module_visibility($mod, 0, 0); 1462 } 1463 set_section_visible($course->id, $sectionnumber, 0); 1464 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1465 $this->assertEquals($section_info->visible, 0); 1466 foreach ($modules as $mod) { 1467 $this->check_module_visibility($mod, 0, 0); 1468 } 1469 set_section_visible($course->id, $sectionnumber, 1); 1470 $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber); 1471 $this->assertEquals($section_info->visible, 1); 1472 foreach ($modules as $mod) { 1473 $this->check_module_visibility($mod, 0, 0); 1474 } 1475 } 1476 1477 /** 1478 * Helper function to assert that a module has correctly been made visible, or hidden. 1479 * 1480 * @param stdClass $mod module information 1481 * @param int $visibility the current state of the module 1482 * @param int $visibleold the current state of the visibleold property 1483 * @return void 1484 */ 1485 public function check_module_visibility($mod, $visibility, $visibleold) { 1486 global $DB; 1487 $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid); 1488 $this->assertEquals($visibility, $cm->visible); 1489 $this->assertEquals($visibleold, $cm->visibleold); 1490 1491 // Check the module grade items. 1492 $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname, 1493 'iteminstance' => $cm->instance, 'courseid' => $cm->course)); 1494 if ($grade_items) { 1495 foreach ($grade_items as $grade_item) { 1496 if ($visibility) { 1497 $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible"); 1498 } else { 1499 $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden"); 1500 } 1501 } 1502 } 1503 1504 // Check the events visibility. 1505 if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) { 1506 foreach ($events as $event) { 1507 $calevent = new calendar_event($event); 1508 $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility"); 1509 } 1510 } 1511 } 1512 1513 public function test_course_page_type_list() { 1514 global $DB; 1515 $this->resetAfterTest(true); 1516 1517 // Create a category. 1518 $category = new stdClass(); 1519 $category->name = 'Test Category'; 1520 1521 $testcategory = $this->getDataGenerator()->create_category($category); 1522 1523 // Create a course. 1524 $course = new stdClass(); 1525 $course->fullname = 'Apu loves Unit Təsts'; 1526 $course->shortname = 'Spread the lŭve'; 1527 $course->idnumber = '123'; 1528 $course->summary = 'Awesome!'; 1529 $course->summaryformat = FORMAT_PLAIN; 1530 $course->format = 'topics'; 1531 $course->newsitems = 0; 1532 $course->numsections = 5; 1533 $course->category = $testcategory->id; 1534 1535 $testcourse = $this->getDataGenerator()->create_course($course); 1536 1537 // Create contexts. 1538 $coursecontext = context_course::instance($testcourse->id); 1539 $parentcontext = $coursecontext->get_parent_context(); // Not actually used. 1540 $pagetype = 'page-course-x'; // Not used either. 1541 $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext); 1542 1543 // Page type lists for normal courses. 1544 $testpagetypelist1 = array(); 1545 $testpagetypelist1['*'] = 'Any page'; 1546 $testpagetypelist1['course-*'] = 'Any course page'; 1547 $testpagetypelist1['course-view-*'] = 'Any type of course main page'; 1548 1549 $this->assertEquals($testpagetypelist1, $pagetypelist); 1550 1551 // Get the context for the front page course. 1552 $sitecoursecontext = context_course::instance(SITEID); 1553 $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext); 1554 1555 // Page type list for the front page course. 1556 $testpagetypelist2 = array('*' => 'Any page'); 1557 $this->assertEquals($testpagetypelist2, $pagetypelist); 1558 1559 // Make sure that providing no current context to the function doesn't result in an error. 1560 // Calls made from generate_page_type_patterns() may provide null values. 1561 $pagetypelist = course_page_type_list($pagetype, null, null); 1562 $this->assertEquals($pagetypelist, $testpagetypelist1); 1563 } 1564 1565 public function test_compare_activities_by_time_desc() { 1566 1567 // Let's create some test data. 1568 $activitiesivities = array(); 1569 $x = new stdClass(); 1570 $x->timestamp = null; 1571 $activities[] = $x; 1572 1573 $x = new stdClass(); 1574 $x->timestamp = 1; 1575 $activities[] = $x; 1576 1577 $x = new stdClass(); 1578 $x->timestamp = 3; 1579 $activities[] = $x; 1580 1581 $x = new stdClass(); 1582 $x->timestamp = 0; 1583 $activities[] = $x; 1584 1585 $x = new stdClass(); 1586 $x->timestamp = 5; 1587 $activities[] = $x; 1588 1589 $x = new stdClass(); 1590 $activities[] = $x; 1591 1592 $x = new stdClass(); 1593 $x->timestamp = 5; 1594 $activities[] = $x; 1595 1596 // Do the sorting. 1597 usort($activities, 'compare_activities_by_time_desc'); 1598 1599 // Let's check the result. 1600 $last = 10; 1601 foreach($activities as $activity) { 1602 if (empty($activity->timestamp)) { 1603 $activity->timestamp = 0; 1604 } 1605 $this->assertLessThanOrEqual($last, $activity->timestamp); 1606 } 1607 } 1608 1609 public function test_compare_activities_by_time_asc() { 1610 1611 // Let's create some test data. 1612 $activities = array(); 1613 $x = new stdClass(); 1614 $x->timestamp = null; 1615 $activities[] = $x; 1616 1617 $x = new stdClass(); 1618 $x->timestamp = 1; 1619 $activities[] = $x; 1620 1621 $x = new stdClass(); 1622 $x->timestamp = 3; 1623 $activities[] = $x; 1624 1625 $x = new stdClass(); 1626 $x->timestamp = 0; 1627 $activities[] = $x; 1628 1629 $x = new stdClass(); 1630 $x->timestamp = 5; 1631 $activities[] = $x; 1632 1633 $x = new stdClass(); 1634 $activities[] = $x; 1635 1636 $x = new stdClass(); 1637 $x->timestamp = 5; 1638 $activities[] = $x; 1639 1640 // Do the sorting. 1641 usort($activities, 'compare_activities_by_time_asc'); 1642 1643 // Let's check the result. 1644 $last = 0; 1645 foreach($activities as $activity) { 1646 if (empty($activity->timestamp)) { 1647 $activity->timestamp = 0; 1648 } 1649 $this->assertGreaterThanOrEqual($last, $activity->timestamp); 1650 } 1651 } 1652 1653 /** 1654 * Tests moving a module between hidden/visible sections and 1655 * verifies that the course/module visiblity seettings are 1656 * retained. 1657 */ 1658 public function test_moveto_module_between_hidden_sections() { 1659 global $DB; 1660 1661 $this->resetAfterTest(true); 1662 1663 $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true)); 1664 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 1665 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 1666 $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id)); 1667 1668 // Set the page as hidden 1669 set_coursemodule_visible($page->cmid, 0); 1670 1671 // Set sections 3 as hidden. 1672 set_section_visible($course->id, 3, 0); 1673 1674 $modinfo = get_fast_modinfo($course); 1675 1676 $hiddensection = $modinfo->get_section_info(3); 1677 // New section is definitely not visible: 1678 $this->assertEquals($hiddensection->visible, 0); 1679 1680 $forumcm = $modinfo->cms[$forum->cmid]; 1681 $pagecm = $modinfo->cms[$page->cmid]; 1682 1683 // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state. 1684 $this->assertEquals(0, moveto_module($forumcm, $hiddensection)); 1685 $this->assertEquals(0, moveto_module($pagecm, $hiddensection)); 1686 1687 $modinfo = get_fast_modinfo($course); 1688 1689 // Verify that forum and page have been moved to the hidden section and quiz has not. 1690 $this->assertContainsEquals($forum->cmid, $modinfo->sections[3]); 1691 $this->assertContainsEquals($page->cmid, $modinfo->sections[3]); 1692 $this->assertNotContainsEquals($quiz->cmid, $modinfo->sections[3]); 1693 1694 // Verify that forum has been made invisible. 1695 $forumcm = $modinfo->cms[$forum->cmid]; 1696 $this->assertEquals($forumcm->visible, 0); 1697 // Verify that old state has been retained. 1698 $this->assertEquals($forumcm->visibleold, 1); 1699 1700 // Verify that page has stayed invisible. 1701 $pagecm = $modinfo->cms[$page->cmid]; 1702 $this->assertEquals($pagecm->visible, 0); 1703 // Verify that old state has been retained. 1704 $this->assertEquals($pagecm->visibleold, 0); 1705 1706 // Verify that quiz has been unaffected. 1707 $quizcm = $modinfo->cms[$quiz->cmid]; 1708 $this->assertEquals($quizcm->visible, 1); 1709 1710 // Move forum and page back to visible section. 1711 // Make sure the visibility is restored to the original value (visible for forum and hidden for page). 1712 $visiblesection = $modinfo->get_section_info(2); 1713 $this->assertEquals(1, moveto_module($forumcm, $visiblesection)); 1714 $this->assertEquals(0, moveto_module($pagecm, $visiblesection)); 1715 1716 $modinfo = get_fast_modinfo($course); 1717 1718 // Double check that forum has been made visible. 1719 $forumcm = $modinfo->cms[$forum->cmid]; 1720 $this->assertEquals($forumcm->visible, 1); 1721 1722 // Double check that page has stayed invisible. 1723 $pagecm = $modinfo->cms[$page->cmid]; 1724 $this->assertEquals($pagecm->visible, 0); 1725 1726 // Move the page in the same section (this is what mod duplicate does). 1727 // Visibility of page remains 0. 1728 $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm)); 1729 1730 // Double check that the the page is still hidden. 1731 $modinfo = get_fast_modinfo($course); 1732 $pagecm = $modinfo->cms[$page->cmid]; 1733 $this->assertEquals($pagecm->visible, 0); 1734 } 1735 1736 /** 1737 * Tests moving a module around in the same section. moveto_module() 1738 * is called this way in modduplicate. 1739 */ 1740 public function test_moveto_module_in_same_section() { 1741 global $DB; 1742 1743 $this->resetAfterTest(true); 1744 1745 $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true)); 1746 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); 1747 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 1748 1749 // Simulate inconsistent visible/visibleold values (MDL-38713). 1750 $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST); 1751 $cm->visible = 0; 1752 $cm->visibleold = 1; 1753 $DB->update_record('course_modules', $cm); 1754 1755 $modinfo = get_fast_modinfo($course); 1756 $forumcm = $modinfo->cms[$forum->cmid]; 1757 $pagecm = $modinfo->cms[$page->cmid]; 1758 1759 // Verify that page is hidden. 1760 $this->assertEquals($pagecm->visible, 0); 1761 1762 // Verify section 0 is where all mods added. 1763 $section = $modinfo->get_section_info(0); 1764 $this->assertEquals($section->id, $forumcm->section); 1765 $this->assertEquals($section->id, $pagecm->section); 1766 1767 1768 // Move the page inside the hidden section. Make sure it is hidden. 1769 $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm)); 1770 1771 // Double check that the the page is still hidden. 1772 $modinfo = get_fast_modinfo($course); 1773 $pagecm = $modinfo->cms[$page->cmid]; 1774 $this->assertEquals($pagecm->visible, 0); 1775 } 1776 1777 /** 1778 * Tests the function that deletes a course module 1779 * 1780 * @param string $type The type of module for the test 1781 * @param array $options The options for the module creation 1782 * @dataProvider provider_course_delete_module 1783 */ 1784 public function test_course_delete_module($type, $options) { 1785 global $DB; 1786 1787 $this->resetAfterTest(true); 1788 $this->setAdminUser(); 1789 1790 // Create course and modules. 1791 $course = $this->getDataGenerator()->create_course(array('numsections' => 5)); 1792 $options['course'] = $course->id; 1793 1794 // Generate an assignment with due date (will generate a course event). 1795 $module = $this->getDataGenerator()->create_module($type, $options); 1796 1797 // Get the module context. 1798 $modcontext = context_module::instance($module->cmid); 1799 1800 $assocblog = $this->create_module_asscociated_blog($course, $modcontext); 1801 1802 // Verify context exists. 1803 $this->assertInstanceOf('context_module', $modcontext); 1804 1805 // Make module specific messes. 1806 switch ($type) { 1807 case 'assign': 1808 // Add some tags to this assignment. 1809 core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3')); 1810 core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5')); 1811 1812 // Confirm the tag instances were added. 1813 $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id); 1814 $this->assertEquals(3, $DB->count_records('tag_instance', $criteria)); 1815 $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id); 1816 $this->assertEquals(3, $DB->count_records('tag_instance', $criteria)); 1817 1818 // Verify event assignment event has been generated. 1819 $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type)); 1820 $this->assertEquals(1, $eventcount); 1821 1822 break; 1823 case 'quiz': 1824 $qgen = $this->getDataGenerator()->get_plugin_generator('core_question'); 1825 $qcat = $qgen->create_question_category(array('contextid' => $modcontext->id)); 1826 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)); 1827 $qgen->create_question('shortanswer', null, array('category' => $qcat->id)); 1828 break; 1829 default: 1830 break; 1831 } 1832 1833 // Run delete.. 1834 course_delete_module($module->cmid); 1835 1836 // Verify the context has been removed. 1837 $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING)); 1838 1839 // Verify the course_module record has been deleted. 1840 $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid)); 1841 $this->assertEmpty($cmcount); 1842 1843 // Verify the blog_association record has been deleted. 1844 $this->assertCount(0, $DB->get_records('blog_association', 1845 array('contextid' => $modcontext->id))); 1846 1847 // Verify the blog post record has been deleted. 1848 $this->assertCount(0, $DB->get_records('post', 1849 array('id' => $assocblog->id))); 1850 1851 // Verify the tag instance record has been deleted. 1852 $this->assertCount(0, $DB->get_records('tag_instance', 1853 array('itemid' => $assocblog->id))); 1854 1855 // Test clean up of module specific messes. 1856 switch ($type) { 1857 case 'assign': 1858 // Verify event assignment events have been removed. 1859 $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type)); 1860 $this->assertEmpty($eventcount); 1861 1862 // Verify the tag instances were deleted. 1863 $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id); 1864 $this->assertEquals(0, $DB->count_records('tag_instance', $criteria)); 1865 1866 $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id); 1867 $this->assertEquals(0, $DB->count_records('tag_instance', $criteria)); 1868 break; 1869 case 'quiz': 1870 // Verify category deleted. 1871 $criteria = array('contextid' => $modcontext->id); 1872 $this->assertEquals(0, $DB->count_records('question_categories', $criteria)); 1873 1874 // Verify questions deleted. 1875 $criteria = [$qcat->id]; 1876 $sql = 'SELECT COUNT(q.id) 1877 FROM {question} q 1878 JOIN {question_versions} qv ON qv.questionid = q.id 1879 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid 1880 WHERE qbe.questioncategoryid = ?'; 1881 $this->assertEquals(0, $DB->count_records_sql($sql, $criteria)); 1882 break; 1883 default: 1884 break; 1885 } 1886 } 1887 1888 /** 1889 * Test that triggering a course_created event works as expected. 1890 */ 1891 public function test_course_created_event() { 1892 global $DB; 1893 1894 $this->resetAfterTest(); 1895 1896 // Catch the events. 1897 $sink = $this->redirectEvents(); 1898 1899 // Create the course with an id number which is used later when generating a course via the imsenterprise plugin. 1900 $data = new stdClass(); 1901 $data->idnumber = 'idnumber'; 1902 $course = $this->getDataGenerator()->create_course($data); 1903 // Get course from DB for comparison. 1904 $course = $DB->get_record('course', array('id' => $course->id)); 1905 1906 // Capture the event. 1907 $events = $sink->get_events(); 1908 $sink->close(); 1909 1910 // Validate the event. 1911 $event = $events[0]; 1912 $this->assertInstanceOf('\core\event\course_created', $event); 1913 $this->assertEquals('course', $event->objecttable); 1914 $this->assertEquals($course->id, $event->objectid); 1915 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 1916 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id)); 1917 $this->assertEquals('course_created', $event->get_legacy_eventname()); 1918 $this->assertEventLegacyData($course, $event); 1919 $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')'); 1920 $this->assertEventLegacyLogData($expectedlog, $event); 1921 1922 // Now we want to trigger creating a course via the imsenterprise. 1923 // Delete the course we created earlier, as we want the imsenterprise plugin to create this. 1924 // We do not want print out any of the text this function generates while doing this, which is why 1925 // we are using ob_start() and ob_end_clean(). 1926 ob_start(); 1927 delete_course($course); 1928 ob_end_clean(); 1929 1930 // Create the XML file we want to use. 1931 $course->category = (array)$course->category; 1932 $imstestcase = new imsenterprise_test(); 1933 $imstestcase->imsplugin = enrol_get_plugin('imsenterprise'); 1934 $imstestcase->set_test_config(); 1935 $imstestcase->set_xml_file(false, array($course)); 1936 1937 // Capture the event. 1938 $sink = $this->redirectEvents(); 1939 $imstestcase->imsplugin->cron(); 1940 $events = $sink->get_events(); 1941 $sink->close(); 1942 $event = null; 1943 foreach ($events as $eventinfo) { 1944 if ($eventinfo instanceof \core\event\course_created ) { 1945 $event = $eventinfo; 1946 break; 1947 } 1948 } 1949 1950 // Validate the event triggered is \core\event\course_created. There is no need to validate the other values 1951 // as they have already been validated in the previous steps. Here we only want to make sure that when the 1952 // imsenterprise plugin creates a course an event is triggered. 1953 $this->assertInstanceOf('\core\event\course_created', $event); 1954 $this->assertEventContextNotUsed($event); 1955 } 1956 1957 /** 1958 * Test that triggering a course_updated event works as expected. 1959 */ 1960 public function test_course_updated_event() { 1961 global $DB; 1962 1963 $this->resetAfterTest(); 1964 1965 // Create a course. 1966 $course = $this->getDataGenerator()->create_course(); 1967 1968 // Create a category we are going to move this course to. 1969 $category = $this->getDataGenerator()->create_category(); 1970 1971 // Create a hidden category we are going to move this course to. 1972 $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0)); 1973 1974 // Update course and catch course_updated event. 1975 $sink = $this->redirectEvents(); 1976 update_course($course); 1977 $events = $sink->get_events(); 1978 $sink->close(); 1979 1980 // Get updated course information from the DB. 1981 $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 1982 // Validate event. 1983 $event = array_shift($events); 1984 $this->assertInstanceOf('\core\event\course_updated', $event); 1985 $this->assertEquals('course', $event->objecttable); 1986 $this->assertEquals($updatedcourse->id, $event->objectid); 1987 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 1988 $url = new moodle_url('/course/edit.php', array('id' => $event->objectid)); 1989 $this->assertEquals($url, $event->get_url()); 1990 $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid)); 1991 $this->assertEquals('course_updated', $event->get_legacy_eventname()); 1992 $this->assertEventLegacyData($updatedcourse, $event); 1993 $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id); 1994 $this->assertEventLegacyLogData($expectedlog, $event); 1995 1996 // Move course and catch course_updated event. 1997 $sink = $this->redirectEvents(); 1998 move_courses(array($course->id), $category->id); 1999 $events = $sink->get_events(); 2000 $sink->close(); 2001 2002 // Return the moved course information from the DB. 2003 $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 2004 // Validate event. 2005 $event = array_shift($events); 2006 $this->assertInstanceOf('\core\event\course_updated', $event); 2007 $this->assertEquals('course', $event->objecttable); 2008 $this->assertEquals($movedcourse->id, $event->objectid); 2009 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2010 $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id)); 2011 $this->assertEquals('course_updated', $event->get_legacy_eventname()); 2012 $this->assertEventLegacyData($movedcourse, $event); 2013 $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id); 2014 $this->assertEventLegacyLogData($expectedlog, $event); 2015 2016 // Move course to hidden category and catch course_updated event. 2017 $sink = $this->redirectEvents(); 2018 move_courses(array($course->id), $categoryhidden->id); 2019 $events = $sink->get_events(); 2020 $sink->close(); 2021 2022 // Return the moved course information from the DB. 2023 $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 2024 // Validate event. 2025 $event = array_shift($events); 2026 $this->assertInstanceOf('\core\event\course_updated', $event); 2027 $this->assertEquals('course', $event->objecttable); 2028 $this->assertEquals($movedcoursehidden->id, $event->objectid); 2029 $this->assertEquals(context_course::instance($course->id), $event->get_context()); 2030 $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id)); 2031 $this->assertEquals('course_updated', $event->get_legacy_eventname()); 2032 $this->assertEventLegacyData($movedcoursehidden, $event); 2033 $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id); 2034 $this->assertEventLegacyLogData($expectedlog, $event); 2035 $this->assertEventContextNotUsed($event); 2036 } 2037 2038 /** 2039 * Test that triggering a course_updated event logs changes. 2040 */ 2041 public function test_course_updated_event_with_changes() { 2042 global $DB; 2043 2044 $this->resetAfterTest(); 2045 2046 // Create a course. 2047 $course = $this->getDataGenerator()->create_course((object)['visible' => 1]); 2048 2049 $editedcourse = $DB->get_record('course', ['id' => $course->id]); 2050 $editedcourse->visible = 0; 2051 2052 // Update course and catch course_updated event. 2053 $sink = $this->redirectEvents(); 2054 update_course($editedcourse); 2055 $events = $sink->get_events(); 2056 $sink->close(); 2057 2058 $event = array_shift($events); 2059 $this->assertInstanceOf('\core\event\course_updated', $event); 2060 $otherdata = [ 2061 'shortname' => $course->shortname, 2062 'fullname' => $course->fullname, 2063 'updatedfields' => [ 2064 'visible' => 0 2065 ] 2066 ]; 2067 $this->assertEquals($otherdata, $event->other); 2068 2069 } 2070 2071 /** 2072 * Test that triggering a course_deleted event works as expected. 2073 */ 2074 public function test_course_deleted_event() { 2075 $this->resetAfterTest(); 2076 2077 // Create the course. 2078 $course = $this->getDataGenerator()->create_course(); 2079 2080 // Save the course context before we delete the course. 2081 $coursecontext = context_course::instance($course->id); 2082 2083 // Catch the update event. 2084 $sink = $this->redirectEvents(); 2085 2086 // Call delete_course() which will trigger the course_deleted event and the course_content_deleted 2087 // event. This function prints out data to the screen, which we do not want during a PHPUnit test, 2088 // so use ob_start and ob_end_clean to prevent this. 2089 ob_start(); 2090 delete_course($course); 2091 ob_end_clean(); 2092 2093 // Capture the event. 2094 $events = $sink->get_events(); 2095 $sink->close(); 2096 2097 // Validate the event. 2098 $event = array_pop($events); 2099 $this->assertInstanceOf('\core\event\course_deleted', $event); 2100 $this->assertEquals('course', $event->objecttable); 2101 $this->assertEquals($course->id, $event->objectid); 2102 $this->assertEquals($coursecontext->id, $event->contextid); 2103 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id)); 2104 $this->assertEquals('course_deleted', $event->get_legacy_eventname()); 2105 $eventdata = $event->get_data(); 2106 $this->assertSame($course->idnumber, $eventdata['other']['idnumber']); 2107 $this->assertSame($course->fullname, $eventdata['other']['fullname']); 2108 $this->assertSame($course->shortname, $eventdata['other']['shortname']); 2109 2110 // The legacy data also passed the context in the course object and substitutes timemodified with the current date. 2111 $expectedlegacy = clone($course); 2112 $expectedlegacy->context = $coursecontext; 2113 $expectedlegacy->timemodified = $event->timecreated; 2114 $this->assertEventLegacyData($expectedlegacy, $event); 2115 2116 // Validate legacy log data. 2117 $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')'); 2118 $this->assertEventLegacyLogData($expectedlog, $event); 2119 $this->assertEventContextNotUsed($event); 2120 } 2121 2122 /** 2123 * Test that triggering a course_content_deleted event works as expected. 2124 */ 2125 public function test_course_content_deleted_event() { 2126 global $DB; 2127 2128 $this->resetAfterTest(); 2129 2130 // Create the course. 2131 $course = $this->getDataGenerator()->create_course(); 2132 2133 // Get the course from the DB. The data generator adds some extra properties, such as 2134 // numsections, to the course object which will fail the assertions later on. 2135 $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST); 2136 2137 // Save the course context before we delete the course. 2138 $coursecontext = context_course::instance($course->id); 2139 2140 // Catch the update event. 2141 $sink = $this->redirectEvents(); 2142 2143 remove_course_contents($course->id, false); 2144 2145 // Capture the event. 2146 $events = $sink->get_events(); 2147 $sink->close(); 2148 2149 // Validate the event. 2150 $event = array_pop($events); 2151 $this->assertInstanceOf('\core\event\course_content_deleted', $event); 2152 $this->assertEquals('course', $event->objecttable); 2153 $this->assertEquals($course->id, $event->objectid); 2154 $this->assertEquals($coursecontext->id, $event->contextid); 2155 $this->assertEquals($course, $event->get_record_snapshot('course', $course->id)); 2156 $this->assertEquals('course_content_removed', $event->get_legacy_eventname()); 2157 // The legacy data also passed the context and options in the course object. 2158 $course->context = $coursecontext; 2159 $course->options = array(); 2160 $this->assertEventLegacyData($course, $event); 2161 $this->assertEventContextNotUsed($event); 2162 } 2163 2164 /** 2165 * Test that triggering a course_category_deleted event works as expected. 2166 */ 2167 public function test_course_category_deleted_event() { 2168 $this->resetAfterTest(); 2169 2170 // Create a category. 2171 $category = $this->getDataGenerator()->create_category(); 2172 2173 // Save the original record/context before it is deleted. 2174 $categoryrecord = $category->get_db_record(); 2175 $categorycontext = context_coursecat::instance($category->id); 2176 2177 // Catch the update event. 2178 $sink = $this->redirectEvents(); 2179 2180 // Delete the category. 2181 $category->delete_full(); 2182 2183 // Capture the event. 2184 $events = $sink->get_events(); 2185 $sink->close(); 2186 2187 // Validate the event. 2188 $event = $events[0]; 2189 $this->assertInstanceOf('\core\event\course_category_deleted', $event); 2190 $this->assertEquals('course_categories', $event->objecttable); 2191 $this->assertEquals($category->id, $event->objectid); 2192 $this->assertEquals($categorycontext->id, $event->contextid); 2193 $this->assertEquals([ 2194 'name' => $category->name, 2195 ], $event->other); 2196 $this->assertEquals($categoryrecord, $event->get_record_snapshot($event->objecttable, $event->objectid)); 2197 $this->assertEquals('course_category_deleted', $event->get_legacy_eventname()); 2198 $this->assertEquals(null, $event->get_url()); 2199 $this->assertEventLegacyData($category, $event); 2200 $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')'); 2201 $this->assertEventLegacyLogData($expectedlog, $event); 2202 2203 // Create two categories. 2204 $category = $this->getDataGenerator()->create_category(); 2205 $category2 = $this->getDataGenerator()->create_category(); 2206 2207 // Save the original record/context before it is moved and then deleted. 2208 $category2record = $category2->get_db_record(); 2209 $category2context = context_coursecat::instance($category2->id); 2210 2211 // Catch the update event. 2212 $sink = $this->redirectEvents(); 2213 2214 // Move the category. 2215 $category2->delete_move($category->id); 2216 2217 // Capture the event. 2218 $events = $sink->get_events(); 2219 $sink->close(); 2220 2221 // Validate the event. 2222 $event = $events[0]; 2223 $this->assertInstanceOf('\core\event\course_category_deleted', $event); 2224 $this->assertEquals('course_categories', $event->objecttable); 2225 $this->assertEquals($category2->id, $event->objectid); 2226 $this->assertEquals($category2context->id, $event->contextid); 2227 $this->assertEquals([ 2228 'name' => $category2->name, 2229 'contentmovedcategoryid' => $category->id, 2230 ], $event->other); 2231 $this->assertEquals($category2record, $event->get_record_snapshot($event->objecttable, $event->objectid)); 2232 $this->assertEquals('course_category_deleted', $event->get_legacy_eventname()); 2233 $this->assertEventLegacyData($category2, $event); 2234 $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')'); 2235 $this->assertEventLegacyLogData($expectedlog, $event); 2236 $this->assertEventContextNotUsed($event); 2237 } 2238 2239 /** 2240 * Test that triggering a course_backup_created event works as expected. 2241 */ 2242 public function test_course_backup_created_event() { 2243 global $CFG; 2244 2245 // Get the necessary files to perform backup and restore. 2246 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 2247 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 2248 2249 $this->resetAfterTest(); 2250 2251 // Set to admin user. 2252 $this->setAdminUser(); 2253 2254 // The user id is going to be 2 since we are the admin user. 2255 $userid = 2; 2256 2257 // Create a course. 2258 $course = $this->getDataGenerator()->create_course(); 2259 2260 // Create backup file and save it to the backup location. 2261 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, 2262 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid); 2263 $sink = $this->redirectEvents(); 2264 $bc->execute_plan(); 2265 2266 // Capture the event. 2267 $events = $sink->get_events(); 2268 $sink->close(); 2269 2270 // Validate the event. 2271 $event = array_pop($events); 2272 $this->assertInstanceOf('\core\event\course_backup_created', $event); 2273 $this->assertEquals('course', $event->objecttable); 2274 $this->assertEquals($bc->get_courseid(), $event->objectid); 2275 $this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid); 2276 2277 $url = new moodle_url('/course/view.php', array('id' => $event->objectid)); 2278 $this->assertEquals($url, $event->get_url()); 2279 $this->assertEventContextNotUsed($event); 2280 2281 // Destroy the resource controller since we are done using it. 2282 $bc->destroy(); 2283 } 2284 2285 /** 2286 * Test that triggering a course_restored event works as expected. 2287 */ 2288 public function test_course_restored_event() { 2289 global $CFG; 2290 2291 // Get the necessary files to perform backup and restore. 2292 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); 2293 require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php'); 2294 2295 $this->resetAfterTest(); 2296 2297 // Set to admin user. 2298 $this->setAdminUser(); 2299 2300 // The user id is going to be 2 since we are the admin user. 2301 $userid = 2; 2302 2303 // Create a course. 2304 $course = $this->getDataGenerator()->create_course(); 2305 2306 // Create backup file and save it to the backup location. 2307 $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, 2308 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid); 2309 $bc->execute_plan(); 2310 $results = $bc->get_results(); 2311 $file = $results['backup_destination']; 2312 $fp = get_file_packer('application/vnd.moodle.backup'); 2313 $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event'; 2314 $file->extract_to_pathname($fp, $filepath); 2315 $bc->destroy(); 2316 2317 // Now we want to catch the restore course event. 2318 $sink = $this->redirectEvents(); 2319 2320 // Now restore the course to trigger the event. 2321 $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO, 2322 backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE); 2323 $rc->execute_precheck(); 2324 $rc->execute_plan(); 2325 2326 // Capture the event. 2327 $events = $sink->get_events(); 2328 $sink->close(); 2329 2330 // Validate the event. 2331 $event = array_pop($events); 2332 $this->assertInstanceOf('\core\event\course_restored', $event); 2333 $this->assertEquals('course', $event->objecttable); 2334 $this->assertEquals($rc->get_courseid(), $event->objectid); 2335 $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid); 2336 $this->assertEquals('course_restored', $event->get_legacy_eventname()); 2337 $legacydata = (object) array( 2338 'courseid' => $rc->get_courseid(), 2339 'userid' => $rc->get_userid(), 2340 'type' => $rc->get_type(), 2341 'target' => $rc->get_target(), 2342 'mode' => $rc->get_mode(), 2343 'operation' => $rc->get_operation(), 2344 'samesite' => $rc->is_samesite() 2345 ); 2346 $url = new moodle_url('/course/view.php', array('id' => $event->objectid)); 2347 $this->assertEquals($url, $event->get_url()); 2348 $this->assertEventLegacyData($legacydata, $event); 2349 $this->assertEventContextNotUsed($event); 2350 2351 // Destroy the resource controller since we are done using it. 2352 $rc->destroy(); 2353 } 2354 2355 /** 2356 * Test that triggering a course_section_updated event works as expected. 2357 */ 2358 public function test_course_section_updated_event() { 2359 global $DB; 2360 2361 $this->resetAfterTest(); 2362 2363 // Create the course with sections. 2364 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true)); 2365 $sections = $DB->get_records('course_sections', array('course' => $course->id)); 2366 2367 $coursecontext = context_course::instance($course->id); 2368 2369 $section = array_pop($sections); 2370 $section->name = 'Test section'; 2371 $section->summary = 'Test section summary'; 2372 $DB->update_record('course_sections', $section); 2373 2374 // Trigger an event for course section update. 2375 $event = \core\event\course_section_updated::create( 2376 array( 2377 'objectid' => $section->id, 2378 'courseid' => $course->id, 2379 'context' => context_course::instance($course->id), 2380 'other' => array( 2381 'sectionnum' => $section->section 2382 ) 2383 ) 2384 ); 2385 $event->add_record_snapshot('course_sections', $section); 2386 // Trigger and catch event. 2387 $sink = $this->redirectEvents(); 2388 $event->trigger(); 2389 $events = $sink->get_events(); 2390 $sink->close(); 2391 2392 // Validate the event. 2393 $event = $events[0]; 2394 $this->assertInstanceOf('\core\event\course_section_updated', $event); 2395 $this->assertEquals('course_sections', $event->objecttable); 2396 $this->assertEquals($section->id, $event->objectid); 2397 $this->assertEquals($course->id, $event->courseid); 2398 $this->assertEquals($coursecontext->id, $event->contextid); 2399 $this->assertEquals($section->section, $event->other['sectionnum']); 2400 $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'"; 2401 $this->assertEquals($expecteddesc, $event->get_description()); 2402 $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid)); 2403 $this->assertEquals($url, $event->get_url()); 2404 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid)); 2405 $id = $section->id; 2406 $sectionnum = $section->section; 2407 $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum); 2408 $this->assertEventLegacyLogData($expectedlegacydata, $event); 2409 $this->assertEventContextNotUsed($event); 2410 } 2411 2412 /** 2413 * Test that triggering a course_section_deleted event works as expected. 2414 */ 2415 public function test_course_section_deleted_event() { 2416 global $USER, $DB; 2417 $this->resetAfterTest(); 2418 $sink = $this->redirectEvents(); 2419 2420 // Create the course with sections. 2421 $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true)); 2422 $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section'); 2423 $coursecontext = context_course::instance($course->id); 2424 $section = array_pop($sections); 2425 course_delete_section($course, $section); 2426 $events = $sink->get_events(); 2427 $event = array_pop($events); // Delete section event. 2428 $sink->close(); 2429 2430 // Validate event data. 2431 $this->assertInstanceOf('\core\event\course_section_deleted', $event); 2432 $this->assertEquals('course_sections', $event->objecttable); 2433 $this->assertEquals($section->id, $event->objectid); 2434 $this->assertEquals($course->id, $event->courseid); 2435 $this->assertEquals($coursecontext->id, $event->contextid); 2436 $this->assertEquals($section->section, $event->other['sectionnum']); 2437 $expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " . 2438 "(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'"; 2439 $this->assertEquals($expecteddesc, $event->get_description()); 2440 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid)); 2441 $this->assertNull($event->get_url()); 2442 2443 // Test legacy data. 2444 $sectionnum = $section->section; 2445 $expectedlegacydata = array($course->id, "course", "delete section", 'view.php?id=' . $course->id, $sectionnum); 2446 $this->assertEventLegacyLogData($expectedlegacydata, $event); 2447 $this->assertEventContextNotUsed($event); 2448 } 2449 2450 public function test_course_integrity_check() { 2451 global $DB; 2452 2453 $this->resetAfterTest(true); 2454 $course = $this->getDataGenerator()->create_course(array('numsections' => 1), 2455 array('createsections'=>true)); 2456 2457 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), 2458 array('section' => 0)); 2459 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id), 2460 array('section' => 0)); 2461 $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id), 2462 array('section' => 0)); 2463 $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid)); 2464 2465 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2466 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2467 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2468 $this->assertEquals($correctseq, $section0->sequence); 2469 $this->assertEmpty($section1->sequence); 2470 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2471 $this->assertEquals($section0->id, $cms[$page->cmid]->section); 2472 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2473 $this->assertEmpty(course_integrity_check($course->id)); 2474 2475 // Now let's make manual change in DB and let course_integrity_check() fix it: 2476 2477 // 1. Module appears twice in one section. 2478 $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid)); 2479 $this->assertEquals( 2480 array('Failed integrity check for course ['. $course->id. 2481 ']. Sequence for course section ['. $section0->id. '] is "'. 2482 $section0->sequence. ','. $page->cmid. '", must be "'. 2483 $section0->sequence. '"'), 2484 course_integrity_check($course->id)); 2485 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2486 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2487 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2488 $this->assertEquals($correctseq, $section0->sequence); 2489 $this->assertEmpty($section1->sequence); 2490 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2491 $this->assertEquals($section0->id, $cms[$page->cmid]->section); 2492 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2493 2494 // 2. Module appears in two sections (last section wins). 2495 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid)); 2496 // First message about double mentioning in sequence, second message about wrong section field for $page. 2497 $this->assertEquals(array( 2498 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2499 '] must be removed from sequence of section ['. $section0->id. 2500 '] because it is also present in sequence of section ['. $section1->id. ']', 2501 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2502 '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'), 2503 course_integrity_check($course->id)); 2504 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2505 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2506 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2507 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2508 $this->assertEquals(''. $page->cmid, $section1->sequence); 2509 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2510 $this->assertEquals($section1->id, $cms[$page->cmid]->section); 2511 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2512 2513 // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false). 2514 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => '')); 2515 $this->assertEmpty(course_integrity_check($course->id)); // Not an error! 2516 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2517 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2518 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2519 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2520 $this->assertEmpty($section1->sequence); 2521 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2522 $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed. 2523 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2524 2525 // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true). 2526 $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['. 2527 $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'), 2528 course_integrity_check($course->id, null, null, true)); // Error! 2529 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2530 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2531 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2532 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2533 $this->assertEquals(''. $page->cmid, $section1->sequence); // Yay, module added to section. 2534 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2535 $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed. 2536 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2537 2538 // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true). 2539 $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765)); 2540 $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => '')); 2541 $this->assertEquals(array( 2542 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2543 '] is missing from sequence of section ['. $section0->id. ']', 2544 'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid. 2545 '] points to section [8765] instead of ['. $section0->id. ']'), 2546 course_integrity_check($course->id, null, null, true)); 2547 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2548 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2549 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2550 $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section. 2551 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2552 $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0. 2553 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2554 2555 // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true). 2556 $DB->delete_records('course_modules', array('id' => $page->cmid)); 2557 $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['. 2558 $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'), 2559 course_integrity_check($course->id, null, null, true)); 2560 $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0)); 2561 $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1)); 2562 $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section'); 2563 $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence); 2564 $this->assertEmpty($section1->sequence); 2565 $this->assertEquals($section0->id, $cms[$forum->cmid]->section); 2566 $this->assertEquals($section0->id, $cms[$quiz->cmid]->section); 2567 $this->assertEquals(2, count($cms)); 2568 } 2569 2570 /** 2571 * Tests for event related to course module creation. 2572 */ 2573 public function test_course_module_created_event() { 2574 global $USER; 2575 2576 $this->resetAfterTest(); 2577 $this->setAdminUser(); 2578 2579 // Create an assign module. 2580 $sink = $this->redirectEvents(); 2581 $course = $this->getDataGenerator()->create_course(); 2582 $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]); 2583 $events = $sink->get_events(); 2584 $eventscount = 0; 2585 2586 // Validate event data. 2587 foreach ($events as $event) { 2588 if ($event instanceof \core\event\course_module_created) { 2589 $eventscount++; 2590 2591 $this->assertEquals($module->cmid, $event->objectid); 2592 $this->assertEquals($USER->id, $event->userid); 2593 $this->assertEquals('course_modules', $event->objecttable); 2594 $url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid)); 2595 $this->assertEquals($url, $event->get_url()); 2596 2597 // Test legacy data. 2598 $this->assertSame('mod_created', $event->get_legacy_eventname()); 2599 $eventdata = new stdClass(); 2600 $eventdata->modulename = 'assign'; 2601 $eventdata->name = $module->name; 2602 $eventdata->cmid = $module->cmid; 2603 $eventdata->courseid = $module->course; 2604 $eventdata->userid = $USER->id; 2605 $this->assertEventLegacyData($eventdata, $event); 2606 2607 $arr = array( 2608 array($module->course, "course", "add mod", "../mod/assign/view.php?id=$module->cmid", "assign $module->id"), 2609 array($module->course, "assign", "add", "view.php?id=$module->cmid", $module->id, $module->cmid) 2610 ); 2611 $this->assertEventLegacyLogData($arr, $event); 2612 $this->assertEventContextNotUsed($event); 2613 } 2614 } 2615 // Only one \core\event\course_module_created event should be triggered. 2616 $this->assertEquals(1, $eventscount); 2617 2618 // Let us see if duplicating an activity results in a nice course module created event. 2619 $sink->clear(); 2620 $course = get_course($module->course); 2621 $cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST); 2622 $newcm = duplicate_module($course, $cm); 2623 $events = $sink->get_events(); 2624 $eventscount = 0; 2625 $sink->close(); 2626 2627 foreach ($events as $event) { 2628 if ($event instanceof \core\event\course_module_created) { 2629 $eventscount++; 2630 // Validate event data. 2631 $this->assertInstanceOf('\core\event\course_module_created', $event); 2632 $this->assertEquals($newcm->id, $event->objectid); 2633 $this->assertEquals($USER->id, $event->userid); 2634 $this->assertEquals($course->id, $event->courseid); 2635 $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id)); 2636 $this->assertEquals($url, $event->get_url()); 2637 } 2638 } 2639 2640 // Only one \core\event\course_module_created event should be triggered. 2641 $this->assertEquals(1, $eventscount); 2642 } 2643 2644 /** 2645 * Tests for event validations related to course module creation. 2646 */ 2647 public function test_course_module_created_event_exceptions() { 2648 2649 $this->resetAfterTest(); 2650 2651 // Generate data. 2652 $modinfo = $this->create_specific_module_test('assign'); 2653 $context = context_module::instance($modinfo->coursemodule); 2654 2655 // Test not setting instanceid. 2656 try { 2657 $event = \core\event\course_module_created::create(array( 2658 'courseid' => $modinfo->course, 2659 'context' => $context, 2660 'objectid' => $modinfo->coursemodule, 2661 'other' => array( 2662 'modulename' => 'assign', 2663 'name' => 'My assignment', 2664 ) 2665 )); 2666 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without 2667 other['instanceid']"); 2668 } catch (coding_exception $e) { 2669 $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage()); 2670 } 2671 2672 // Test not setting modulename. 2673 try { 2674 $event = \core\event\course_module_created::create(array( 2675 'courseid' => $modinfo->course, 2676 'context' => $context, 2677 'objectid' => $modinfo->coursemodule, 2678 'other' => array( 2679 'instanceid' => $modinfo->instance, 2680 'name' => 'My assignment', 2681 ) 2682 )); 2683 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without 2684 other['modulename']"); 2685 } catch (coding_exception $e) { 2686 $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage()); 2687 } 2688 2689 // Test not setting name. 2690 2691 try { 2692 $event = \core\event\course_module_created::create(array( 2693 'courseid' => $modinfo->course, 2694 'context' => $context, 2695 'objectid' => $modinfo->coursemodule, 2696 'other' => array( 2697 'modulename' => 'assign', 2698 'instanceid' => $modinfo->instance, 2699 ) 2700 )); 2701 $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without 2702 other['name']"); 2703 } catch (coding_exception $e) { 2704 $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage()); 2705 } 2706 2707 } 2708 2709 /** 2710 * Tests for event related to course module updates. 2711 */ 2712 public function test_course_module_updated_event() { 2713 global $USER, $DB; 2714 $this->resetAfterTest(); 2715 2716 // Update a forum module. 2717 $sink = $this->redirectEvents(); 2718 $modinfo = $this->update_specific_module_test('forum'); 2719 $events = $sink->get_events(); 2720 $eventscount = 0; 2721 $sink->close(); 2722 2723 $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST); 2724 $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST); 2725 2726 // Validate event data. 2727 foreach ($events as $event) { 2728 if ($event instanceof \core\event\course_module_updated) { 2729 $eventscount++; 2730 2731 $this->assertEquals($cm->id, $event->objectid); 2732 $this->assertEquals($USER->id, $event->userid); 2733 $this->assertEquals('course_modules', $event->objecttable); 2734 $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id)); 2735 $this->assertEquals($url, $event->get_url()); 2736 2737 // Test legacy data. 2738 $this->assertSame('mod_updated', $event->get_legacy_eventname()); 2739 $eventdata = new stdClass(); 2740 $eventdata->modulename = 'forum'; 2741 $eventdata->name = $mod->name; 2742 $eventdata->cmid = $cm->id; 2743 $eventdata->courseid = $cm->course; 2744 $eventdata->userid = $USER->id; 2745 $this->assertEventLegacyData($eventdata, $event); 2746 2747 $arr = array( 2748 array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"), 2749 array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id) 2750 ); 2751 $this->assertEventLegacyLogData($arr, $event); 2752 $this->assertEventContextNotUsed($event); 2753 } 2754 } 2755 2756 // Only one \core\event\course_module_updated event should be triggered. 2757 $this->assertEquals(1, $eventscount); 2758 } 2759 2760 /** 2761 * Tests for create_from_cm method. 2762 */ 2763 public function test_course_module_create_from_cm() { 2764 $this->resetAfterTest(); 2765 $this->setAdminUser(); 2766 2767 // Create course and modules. 2768 $course = $this->getDataGenerator()->create_course(array('numsections' => 5)); 2769 2770 // Generate an assignment. 2771 $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id)); 2772 2773 // Get the module context. 2774 $modcontext = context_module::instance($assign->cmid); 2775 2776 // Get course module. 2777 $cm = get_coursemodule_from_id(null, $assign->cmid, $course->id, false, MUST_EXIST); 2778 2779 // Create an event from course module. 2780 $event = \core\event\course_module_updated::create_from_cm($cm, $modcontext); 2781 2782 // Trigger the events. 2783 $sink = $this->redirectEvents(); 2784 $event->trigger(); 2785 $events = $sink->get_events(); 2786 $event2 = array_pop($events); 2787 2788 // Test event data. 2789 $this->assertInstanceOf('\core\event\course_module_updated', $event); 2790 $this->assertEquals($cm->id, $event2->objectid); 2791 $this->assertEquals($modcontext, $event2->get_context()); 2792 $this->assertEquals($cm->modname, $event2->other['modulename']); 2793 $this->assertEquals($cm->instance, $event2->other['instanceid']); 2794 $this->assertEquals($cm->name, $event2->other['name']); 2795 $this->assertEventContextNotUsed($event2); 2796 $this->assertSame('mod_updated', $event2->get_legacy_eventname()); 2797 $arr = array( 2798 array($cm->course, "course", "update mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance"), 2799 array($cm->course, "assign", "update", "view.php?id=$cm->id", $cm->instance, $cm->id) 2800 ); 2801 $this->assertEventLegacyLogData($arr, $event); 2802 } 2803 2804 /** 2805 * Tests for event validations related to course module update. 2806 */ 2807 public function test_course_module_updated_event_exceptions() { 2808 2809 $this->resetAfterTest(); 2810 2811 // Generate data. 2812 $modinfo = $this->create_specific_module_test('assign'); 2813 $context = context_module::instance($modinfo->coursemodule); 2814 2815 // Test not setting instanceid. 2816 try { 2817 $event = \core\event\course_module_updated::create(array( 2818 'courseid' => $modinfo->course, 2819 'context' => $context, 2820 'objectid' => $modinfo->coursemodule, 2821 'other' => array( 2822 'modulename' => 'assign', 2823 'name' => 'My assignment', 2824 ) 2825 )); 2826 $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without 2827 other['instanceid']"); 2828 } catch (coding_exception $e) { 2829 $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage()); 2830 } 2831 2832 // Test not setting modulename. 2833 try { 2834 $event = \core\event\course_module_updated::create(array( 2835 'courseid' => $modinfo->course, 2836 'context' => $context, 2837 'objectid' => $modinfo->coursemodule, 2838 'other' => array( 2839 'instanceid' => $modinfo->instance, 2840 'name' => 'My assignment', 2841 ) 2842 )); 2843 $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without 2844 other['modulename']"); 2845 } catch (coding_exception $e) { 2846 $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage()); 2847 } 2848 2849 // Test not setting name. 2850 2851 try { 2852 $event = \core\event\course_module_updated::create(array( 2853 'courseid' => $modinfo->course, 2854 'context' => $context, 2855 'objectid' => $modinfo->coursemodule, 2856 'other' => array( 2857 'modulename' => 'assign', 2858 'instanceid' => $modinfo->instance, 2859 ) 2860 )); 2861 $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without 2862 other['name']"); 2863 } catch (coding_exception $e) { 2864 $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage()); 2865 } 2866 2867 } 2868 2869 /** 2870 * Tests for event related to course module delete. 2871 */ 2872 public function test_course_module_deleted_event() { 2873 global $USER, $DB; 2874 $this->resetAfterTest(); 2875 2876 // Create and delete a module. 2877 $sink = $this->redirectEvents(); 2878 $modinfo = $this->create_specific_module_test('forum'); 2879 $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST); 2880 course_delete_module($modinfo->coursemodule); 2881 $events = $sink->get_events(); 2882 $event = array_pop($events); // delete module event.; 2883 $sink->close(); 2884 2885 // Validate event data. 2886 $this->assertInstanceOf('\core\event\course_module_deleted', $event); 2887 $this->assertEquals($cm->id, $event->objectid); 2888 $this->assertEquals($USER->id, $event->userid); 2889 $this->assertEquals('course_modules', $event->objecttable); 2890 $this->assertEquals(null, $event->get_url()); 2891 $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id)); 2892 2893 // Test legacy data. 2894 $this->assertSame('mod_deleted', $event->get_legacy_eventname()); 2895 $eventdata = new stdClass(); 2896 $eventdata->modulename = 'forum'; 2897 $eventdata->cmid = $cm->id; 2898 $eventdata->courseid = $cm->course; 2899 $eventdata->userid = $USER->id; 2900 $this->assertEventLegacyData($eventdata, $event); 2901 2902 $arr = array($cm->course, 'course', "delete mod", "view.php?id=$cm->course", "forum $cm->instance", $cm->id); 2903 $this->assertEventLegacyLogData($arr, $event); 2904 2905 } 2906 2907 /** 2908 * Tests for event validations related to course module deletion. 2909 */ 2910 public function test_course_module_deleted_event_exceptions() { 2911 2912 $this->resetAfterTest(); 2913 2914 // Generate data. 2915 $modinfo = $this->create_specific_module_test('assign'); 2916 $context = context_module::instance($modinfo->coursemodule); 2917 2918 // Test not setting instanceid. 2919 try { 2920 $event = \core\event\course_module_deleted::create(array( 2921 'courseid' => $modinfo->course, 2922 'context' => $context, 2923 'objectid' => $modinfo->coursemodule, 2924 'other' => array( 2925 'modulename' => 'assign', 2926 'name' => 'My assignment', 2927 ) 2928 )); 2929 $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without 2930 other['instanceid']"); 2931 } catch (coding_exception $e) { 2932 $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage()); 2933 } 2934 2935 // Test not setting modulename. 2936 try { 2937 $event = \core\event\course_module_deleted::create(array( 2938 'courseid' => $modinfo->course, 2939 'context' => $context, 2940 'objectid' => $modinfo->coursemodule, 2941 'other' => array( 2942 'instanceid' => $modinfo->instance, 2943 'name' => 'My assignment', 2944 ) 2945 )); 2946 $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without 2947 other['modulename']"); 2948 } catch (coding_exception $e) { 2949 $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage()); 2950 } 2951 } 2952 2953 /** 2954 * Returns a user object and its assigned new role. 2955 * 2956 * @param testing_data_generator $generator 2957 * @param $contextid 2958 * @return array The user object and the role ID 2959 */ 2960 protected function get_user_objects(testing_data_generator $generator, $contextid) { 2961 global $USER; 2962 2963 if (empty($USER->id)) { 2964 $user = $generator->create_user(); 2965 $this->setUser($user); 2966 } 2967 $roleid = create_role('Test role', 'testrole', 'Test role description'); 2968 if (!is_array($contextid)) { 2969 $contextid = array($contextid); 2970 } 2971 foreach ($contextid as $cid) { 2972 $assignid = role_assign($roleid, $user->id, $cid); 2973 } 2974 return array($user, $roleid); 2975 } 2976 2977 /** 2978 * Test course move after course. 2979 */ 2980 public function test_course_change_sortorder_after_course() { 2981 global $DB; 2982 2983 $this->resetAfterTest(true); 2984 2985 $generator = $this->getDataGenerator(); 2986 $category = $generator->create_category(); 2987 $course3 = $generator->create_course(array('category' => $category->id)); 2988 $course2 = $generator->create_course(array('category' => $category->id)); 2989 $course1 = $generator->create_course(array('category' => $category->id)); 2990 $context = $category->get_context(); 2991 2992 list($user, $roleid) = $this->get_user_objects($generator, $context->id); 2993 $caps = course_capability_assignment::allow('moodle/category:manage', $roleid, $context->id); 2994 2995 $courses = $category->get_courses(); 2996 $this->assertIsArray($courses); 2997 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses)); 2998 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 2999 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3000 3001 // Test moving down. 3002 $this->assertTrue(course_change_sortorder_after_course($course1->id, $course3->id)); 3003 $courses = $category->get_courses(); 3004 $this->assertIsArray($courses); 3005 $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses)); 3006 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3007 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3008 3009 // Test moving up. 3010 $this->assertTrue(course_change_sortorder_after_course($course1->id, $course2->id)); 3011 $courses = $category->get_courses(); 3012 $this->assertIsArray($courses); 3013 $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses)); 3014 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3015 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3016 3017 // Test moving to the top. 3018 $this->assertTrue(course_change_sortorder_after_course($course1->id, 0)); 3019 $courses = $category->get_courses(); 3020 $this->assertIsArray($courses); 3021 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses)); 3022 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3023 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3024 } 3025 3026 /** 3027 * Tests changing the visibility of a course. 3028 */ 3029 public function test_course_change_visibility() { 3030 global $DB; 3031 3032 $this->resetAfterTest(true); 3033 3034 $generator = $this->getDataGenerator(); 3035 $category = $generator->create_category(); 3036 $course = $generator->create_course(array('category' => $category->id)); 3037 3038 $this->assertEquals('1', $course->visible); 3039 $this->assertEquals('1', $course->visibleold); 3040 3041 $this->assertTrue(course_change_visibility($course->id, false)); 3042 $course = $DB->get_record('course', array('id' => $course->id)); 3043 $this->assertEquals('0', $course->visible); 3044 $this->assertEquals('0', $course->visibleold); 3045 3046 $this->assertTrue(course_change_visibility($course->id, true)); 3047 $course = $DB->get_record('course', array('id' => $course->id)); 3048 $this->assertEquals('1', $course->visible); 3049 $this->assertEquals('1', $course->visibleold); 3050 } 3051 3052 /** 3053 * Tests moving the course up and down by one. 3054 */ 3055 public function test_course_change_sortorder_by_one() { 3056 global $DB; 3057 3058 $this->resetAfterTest(true); 3059 3060 $generator = $this->getDataGenerator(); 3061 $category = $generator->create_category(); 3062 $course3 = $generator->create_course(array('category' => $category->id)); 3063 $course2 = $generator->create_course(array('category' => $category->id)); 3064 $course1 = $generator->create_course(array('category' => $category->id)); 3065 3066 $courses = $category->get_courses(); 3067 $this->assertIsArray($courses); 3068 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses)); 3069 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3070 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3071 3072 // Test moving down. 3073 $course1 = get_course($course1->id); 3074 $this->assertTrue(course_change_sortorder_by_one($course1, false)); 3075 $courses = $category->get_courses(); 3076 $this->assertIsArray($courses); 3077 $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses)); 3078 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3079 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3080 3081 // Test moving up. 3082 $course1 = get_course($course1->id); 3083 $this->assertTrue(course_change_sortorder_by_one($course1, true)); 3084 $courses = $category->get_courses(); 3085 $this->assertIsArray($courses); 3086 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses)); 3087 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3088 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3089 3090 // Test moving the top course up one. 3091 $course1 = get_course($course1->id); 3092 $this->assertFalse(course_change_sortorder_by_one($course1, true)); 3093 // Check nothing changed. 3094 $courses = $category->get_courses(); 3095 $this->assertIsArray($courses); 3096 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses)); 3097 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3098 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3099 3100 // Test moving the bottom course up down. 3101 $course3 = get_course($course3->id); 3102 $this->assertFalse(course_change_sortorder_by_one($course3, false)); 3103 // Check nothing changed. 3104 $courses = $category->get_courses(); 3105 $this->assertIsArray($courses); 3106 $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses)); 3107 $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id'); 3108 $this->assertEquals(array_keys($dbcourses), array_keys($courses)); 3109 } 3110 3111 public function test_view_resources_list() { 3112 $this->resetAfterTest(); 3113 3114 $course = self::getDataGenerator()->create_course(); 3115 $coursecontext = context_course::instance($course->id); 3116 3117 $event = \core\event\course_resources_list_viewed::create(array('context' => context_course::instance($course->id))); 3118 $event->set_legacy_logdata(array('book', 'page', 'resource')); 3119 $sink = $this->redirectEvents(); 3120 $event->trigger(); 3121 $events = $sink->get_events(); 3122 $sink->close(); 3123 3124 // Validate the event. 3125 $event = $events[0]; 3126 $this->assertInstanceOf('\core\event\course_resources_list_viewed', $event); 3127 $this->assertEquals(null, $event->objecttable); 3128 $this->assertEquals(null, $event->objectid); 3129 $this->assertEquals($course->id, $event->courseid); 3130 $this->assertEquals($coursecontext->id, $event->contextid); 3131 $expectedlegacydata = array( 3132 array($course->id, "book", "view all", 'index.php?id=' . $course->id, ''), 3133 array($course->id, "page", "view all", 'index.php?id=' . $course->id, ''), 3134 array($course->id, "resource", "view all", 'index.php?id=' . $course->id, ''), 3135 ); 3136 $this->assertEventLegacyLogData($expectedlegacydata, $event); 3137 $this->assertEventContextNotUsed($event); 3138 } 3139 3140 /** 3141 * Test duplicate_module() 3142 */ 3143 public function test_duplicate_module() { 3144 $this->setAdminUser(); 3145 $this->resetAfterTest(); 3146 $course = self::getDataGenerator()->create_course(); 3147 $res = self::getDataGenerator()->create_module('resource', array('course' => $course)); 3148 $cm = get_coursemodule_from_id('resource', $res->cmid, 0, false, MUST_EXIST); 3149 3150 $newcm = duplicate_module($course, $cm); 3151 3152 // Make sure they are the same, except obvious id changes. 3153 foreach ($cm as $prop => $value) { 3154 if ($prop == 'id' || $prop == 'url' || $prop == 'instance' || $prop == 'added') { 3155 // Ignore obviously different properties. 3156 continue; 3157 } 3158 if ($prop == 'name') { 3159 // We expect ' (copy)' to be added to the original name since MDL-59227. 3160 $value = get_string('duplicatedmodule', 'moodle', $value); 3161 } 3162 $this->assertEquals($value, $newcm->$prop); 3163 } 3164 } 3165 3166 /** 3167 * Tests that when creating or updating a module, if the availability settings 3168 * are present but set to an empty tree, availability is set to null in 3169 * database. 3170 */ 3171 public function test_empty_availability_settings() { 3172 global $DB; 3173 $this->setAdminUser(); 3174 $this->resetAfterTest(); 3175 3176 // Enable availability. 3177 set_config('enableavailability', 1); 3178 3179 // Test add. 3180 $emptyavailability = json_encode(\core_availability\tree::get_root_json(array())); 3181 $course = self::getDataGenerator()->create_course(); 3182 $label = self::getDataGenerator()->create_module('label', array( 3183 'course' => $course, 'availability' => $emptyavailability)); 3184 $this->assertNull($DB->get_field('course_modules', 'availability', 3185 array('id' => $label->cmid))); 3186 3187 // Test update. 3188 $formdata = $DB->get_record('course_modules', array('id' => $label->cmid)); 3189 unset($formdata->availability); 3190 $formdata->availabilityconditionsjson = $emptyavailability; 3191 $formdata->modulename = 'label'; 3192 $formdata->coursemodule = $label->cmid; 3193 $draftid = 0; 3194 file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id, 3195 'mod_label', 'intro', 0); 3196 $formdata->introeditor = array( 3197 'itemid' => $draftid, 3198 'text' => '<p>Yo</p>', 3199 'format' => FORMAT_HTML); 3200 update_module($formdata); 3201 $this->assertNull($DB->get_field('course_modules', 'availability', 3202 array('id' => $label->cmid))); 3203 } 3204 3205 /** 3206 * Test update_inplace_editable() 3207 */ 3208 public function test_update_module_name_inplace() { 3209 global $CFG, $DB, $PAGE; 3210 require_once($CFG->dirroot . '/lib/external/externallib.php'); 3211 3212 $this->setUser($this->getDataGenerator()->create_user()); 3213 3214 $this->resetAfterTest(true); 3215 $course = $this->getDataGenerator()->create_course(); 3216 $forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name')); 3217 3218 // Call service for core_course component without necessary permissions. 3219 try { 3220 core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name'); 3221 $this->fail('Exception expected'); 3222 } catch (moodle_exception $e) { 3223 $this->assertEquals('Course or activity not accessible. (Not enrolled)', 3224 $e->getMessage()); 3225 } 3226 3227 // Change to admin user and make sure that cm name can be updated using web service update_inplace_editable(). 3228 $this->setAdminUser(); 3229 $res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name'); 3230 $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res); 3231 $this->assertEquals('New forum name', $res['value']); 3232 $this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id))); 3233 } 3234 3235 /** 3236 * Testing function course_get_tagged_course_modules - search tagged course modules 3237 */ 3238 public function test_course_get_tagged_course_modules() { 3239 global $DB; 3240 $this->resetAfterTest(); 3241 $course3 = $this->getDataGenerator()->create_course(); 3242 $course2 = $this->getDataGenerator()->create_course(); 3243 $course1 = $this->getDataGenerator()->create_course(); 3244 $cm11 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id, 3245 'tags' => 'Cat, Dog')); 3246 $cm12 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 3247 'tags' => 'Cat, Mouse', 'visible' => 0)); 3248 $cm13 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 3249 'tags' => 'Cat, Mouse, Dog')); 3250 $cm21 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id, 3251 'tags' => 'Cat, Mouse')); 3252 $cm31 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id, 3253 'tags' => 'Cat, Mouse')); 3254 3255 // Admin is able to view everything. 3256 $this->setAdminUser(); 3257 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3258 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0); 3259 $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content); 3260 $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content); 3261 $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content); 3262 $this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content); 3263 $this->assertMatchesRegularExpression('/'.$cm31->name.'/', $res->content); 3264 // Results from course1 are returned before results from course2. 3265 $this->assertTrue(strpos($res->content, $cm11->name) < strpos($res->content, $cm21->name)); 3266 3267 // Ordinary user is not able to see anything. 3268 $user = $this->getDataGenerator()->create_user(); 3269 $this->setUser($user); 3270 3271 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3272 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0); 3273 $this->assertNull($res); 3274 3275 // Enrol user as student in course1 and course2. 3276 $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); 3277 $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['student']); 3278 $this->getDataGenerator()->enrol_user($user->id, $course2->id, $roleids['student']); 3279 core_tag_index_builder::reset_caches(); 3280 3281 // Searching in the course context returns visible modules in this course. 3282 $context = context_course::instance($course1->id); 3283 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3284 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0); 3285 $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content); 3286 $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content); 3287 $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content); 3288 $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content); 3289 $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); 3290 3291 // Searching FROM the course context returns visible modules in all courses. 3292 $context = context_course::instance($course2->id); 3293 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3294 /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0); 3295 $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content); 3296 $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content); 3297 $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content); 3298 $this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content); 3299 $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3. 3300 // Results from course2 are returned before results from course1. 3301 $this->assertTrue(strpos($res->content, $cm21->name) < strpos($res->content, $cm11->name)); 3302 3303 // Enrol user in course1 as a teacher - now he should be able to see hidden module. 3304 $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['editingteacher']); 3305 get_fast_modinfo(0,0,true); 3306 3307 $context = context_course::instance($course1->id); 3308 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3309 /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0); 3310 $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content); 3311 3312 // Create more modules and try pagination. 3313 $cm14 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id, 3314 'tags' => 'Cat, Dog')); 3315 $cm15 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 3316 'tags' => 'Cat, Mouse', 'visible' => 0)); 3317 $cm16 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id, 3318 'tags' => 'Cat, Mouse, Dog')); 3319 3320 $context = context_course::instance($course1->id); 3321 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3322 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0); 3323 $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content); 3324 $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content); 3325 $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content); 3326 $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content); 3327 $this->assertMatchesRegularExpression('/'.$cm14->name.'/', $res->content); 3328 $this->assertMatchesRegularExpression('/'.$cm15->name.'/', $res->content); 3329 $this->assertDoesNotMatchRegularExpression('/'.$cm16->name.'/', $res->content); 3330 $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3. 3331 $this->assertEmpty($res->prevpageurl); 3332 $this->assertNotEmpty($res->nextpageurl); 3333 3334 $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'), 3335 /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */1); 3336 $this->assertDoesNotMatchRegularExpression('/'.$cm11->name.'/', $res->content); 3337 $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content); 3338 $this->assertDoesNotMatchRegularExpression('/'.$cm13->name.'/', $res->content); 3339 $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content); 3340 $this->assertDoesNotMatchRegularExpression('/'.$cm14->name.'/', $res->content); 3341 $this->assertDoesNotMatchRegularExpression('/'.$cm15->name.'/', $res->content); 3342 $this->assertMatchesRegularExpression('/'.$cm16->name.'/', $res->content); 3343 $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3. 3344 $this->assertNotEmpty($res->prevpageurl); 3345 $this->assertEmpty($res->nextpageurl); 3346 } 3347 3348 /** 3349 * Test course_get_user_navigation_options for frontpage. 3350 */ 3351 public function test_course_get_user_navigation_options_for_frontpage() { 3352 global $CFG, $SITE, $DB; 3353 $this->resetAfterTest(); 3354 $context = context_system::instance(); 3355 $course = clone $SITE; 3356 $this->setAdminUser(); 3357 3358 $navoptions = course_get_user_navigation_options($context, $course); 3359 $this->assertTrue($navoptions->blogs); 3360 $this->assertTrue($navoptions->notes); 3361 $this->assertTrue($navoptions->participants); 3362 $this->assertTrue($navoptions->badges); 3363 $this->assertTrue($navoptions->tags); 3364 $this->assertFalse($navoptions->search); 3365 $this->assertTrue($navoptions->competencies); 3366 3367 // Enable global search now. 3368 $CFG->enableglobalsearch = 1; 3369 $navoptions = course_get_user_navigation_options($context, $course); 3370 $this->assertTrue($navoptions->search); 3371 3372 // Disable competencies. 3373 $oldcompetencies = get_config('core_competency', 'enabled'); 3374 set_config('enabled', false, 'core_competency'); 3375 $navoptions = course_get_user_navigation_options($context, $course); 3376 $this->assertFalse($navoptions->competencies); 3377 set_config('enabled', $oldcompetencies, 'core_competency'); 3378 3379 // Now try with a standard user. 3380 $user = $this->getDataGenerator()->create_user(); 3381 $this->setUser($user); 3382 $navoptions = course_get_user_navigation_options($context, $course); 3383 $this->assertTrue($navoptions->blogs); 3384 $this->assertFalse($navoptions->notes); 3385 $this->assertFalse($navoptions->participants); 3386 $this->assertTrue($navoptions->badges); 3387 $this->assertTrue($navoptions->tags); 3388 $this->assertTrue($navoptions->search); 3389 } 3390 3391 /** 3392 * Test course_get_user_navigation_options for managers in a normal course. 3393 */ 3394 public function test_course_get_user_navigation_options_for_managers() { 3395 global $CFG; 3396 $this->resetAfterTest(); 3397 $course = $this->getDataGenerator()->create_course(); 3398 $context = context_course::instance($course->id); 3399 $this->setAdminUser(); 3400 3401 $navoptions = course_get_user_navigation_options($context); 3402 $this->assertTrue($navoptions->blogs); 3403 $this->assertTrue($navoptions->notes); 3404 $this->assertTrue($navoptions->participants); 3405 $this->assertTrue($navoptions->badges); 3406 } 3407 3408 /** 3409 * Test course_get_user_navigation_options for students in a normal course. 3410 */ 3411 public function test_course_get_user_navigation_options_for_students() { 3412 global $DB, $CFG; 3413 $this->resetAfterTest(); 3414 $course = $this->getDataGenerator()->create_course(); 3415 $context = context_course::instance($course->id); 3416 3417 $user = $this->getDataGenerator()->create_user(); 3418 $roleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 3419 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid); 3420 3421 $this->setUser($user); 3422 3423 $navoptions = course_get_user_navigation_options($context); 3424 $this->assertTrue($navoptions->blogs); 3425 $this->assertFalse($navoptions->notes); 3426 $this->assertTrue($navoptions->participants); 3427 $this->assertFalse($navoptions->badges); 3428 3429 // Disable some options. 3430 $CFG->badges_allowcoursebadges = 0; 3431 $CFG->enableblogs = 0; 3432 // Disable view participants capability. 3433 assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $context); 3434 3435 $navoptions = course_get_user_navigation_options($context); 3436 $this->assertFalse($navoptions->blogs); 3437 $this->assertFalse($navoptions->notes); 3438 $this->assertFalse($navoptions->participants); 3439 $this->assertFalse($navoptions->badges); 3440 3441 // Re-enable some options to check badges are displayed as expected. 3442 $CFG->badges_allowcoursebadges = 1; 3443 assign_capability('moodle/badges:createbadge', CAP_ALLOW, $roleid, $context); 3444 3445 $navoptions = course_get_user_navigation_options($context); 3446 $this->assertTrue($navoptions->badges); 3447 } 3448 3449 /** 3450 * Test course_get_user_administration_options for frontpage. 3451 */ 3452 public function test_course_get_user_administration_options_for_frontpage() { 3453 global $CFG, $SITE; 3454 $this->resetAfterTest(); 3455 $course = clone $SITE; 3456 $context = context_course::instance($course->id); 3457 $this->setAdminUser(); 3458 3459 $adminoptions = course_get_user_administration_options($course, $context); 3460 $this->assertTrue($adminoptions->update); 3461 $this->assertTrue($adminoptions->filters); 3462 $this->assertTrue($adminoptions->reports); 3463 $this->assertTrue($adminoptions->backup); 3464 $this->assertTrue($adminoptions->restore); 3465 $this->assertFalse($adminoptions->files); 3466 $this->assertFalse($adminoptions->tags); 3467 3468 // Now try with a standard user. 3469 $user = $this->getDataGenerator()->create_user(); 3470 $this->setUser($user); 3471 $adminoptions = course_get_user_administration_options($course, $context); 3472 $this->assertFalse($adminoptions->update); 3473 $this->assertFalse($adminoptions->filters); 3474 $this->assertFalse($adminoptions->reports); 3475 $this->assertFalse($adminoptions->backup); 3476 $this->assertFalse($adminoptions->restore); 3477 $this->assertFalse($adminoptions->files); 3478 $this->assertFalse($adminoptions->tags); 3479 3480 } 3481 3482 /** 3483 * Test course_get_user_administration_options for managers in a normal course. 3484 */ 3485 public function test_course_get_user_administration_options_for_managers() { 3486 global $CFG; 3487 $this->resetAfterTest(); 3488 $course = $this->getDataGenerator()->create_course(); 3489 $context = context_course::instance($course->id); 3490 $this->setAdminUser(); 3491 3492 $adminoptions = course_get_user_administration_options($course, $context); 3493 $this->assertTrue($adminoptions->update); 3494 $this->assertTrue($adminoptions->filters); 3495 $this->assertTrue($adminoptions->reports); 3496 $this->assertTrue($adminoptions->backup); 3497 $this->assertTrue($adminoptions->restore); 3498 $this->assertFalse($adminoptions->files); 3499 $this->assertTrue($adminoptions->tags); 3500 $this->assertTrue($adminoptions->gradebook); 3501 $this->assertFalse($adminoptions->outcomes); 3502 $this->assertTrue($adminoptions->badges); 3503 $this->assertTrue($adminoptions->import); 3504 $this->assertTrue($adminoptions->reset); 3505 $this->assertTrue($adminoptions->roles); 3506 } 3507 3508 /** 3509 * Test course_get_user_administration_options for students in a normal course. 3510 */ 3511 public function test_course_get_user_administration_options_for_students() { 3512 global $DB, $CFG; 3513 $this->resetAfterTest(); 3514 $course = $this->getDataGenerator()->create_course(); 3515 $context = context_course::instance($course->id); 3516 3517 $user = $this->getDataGenerator()->create_user(); 3518 $roleid = $DB->get_field('role', 'id', array('shortname' => 'student')); 3519 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid); 3520 3521 $this->setUser($user); 3522 $adminoptions = course_get_user_administration_options($course, $context); 3523 3524 $this->assertFalse($adminoptions->update); 3525 $this->assertFalse($adminoptions->filters); 3526 $this->assertFalse($adminoptions->reports); 3527 $this->assertFalse($adminoptions->backup); 3528 $this->assertFalse($adminoptions->restore); 3529 $this->assertFalse($adminoptions->files); 3530 $this->assertFalse($adminoptions->tags); 3531 $this->assertFalse($adminoptions->gradebook); 3532 $this->assertFalse($adminoptions->outcomes); 3533 $this->assertTrue($adminoptions->badges); 3534 $this->assertFalse($adminoptions->import); 3535 $this->assertFalse($adminoptions->reset); 3536 $this->assertFalse($adminoptions->roles); 3537 3538 $CFG->enablebadges = false; 3539 $adminoptions = course_get_user_administration_options($course, $context); 3540 $this->assertFalse($adminoptions->badges); 3541 } 3542 3543 /** 3544 * Test test_update_course_frontpage_category. 3545 */ 3546 public function test_update_course_frontpage_category() { 3547 // Fetch front page course. 3548 $course = get_course(SITEID); 3549 // Test update information on front page course. 3550 $course->category = 99; 3551 $this->expectException('moodle_exception'); 3552 $this->expectExceptionMessage(get_string('invalidcourse', 'error')); 3553 update_course($course); 3554 } 3555 3556 /** 3557 * test_course_enddate 3558 * 3559 * @dataProvider course_enddate_provider 3560 * @param int $startdate 3561 * @param int $enddate 3562 * @param string $errorcode 3563 */ 3564 public function test_course_enddate($startdate, $enddate, $errorcode) { 3565 3566 $this->resetAfterTest(true); 3567 3568 $record = array('startdate' => $startdate, 'enddate' => $enddate); 3569 try { 3570 $course1 = $this->getDataGenerator()->create_course($record); 3571 if ($errorcode !== false) { 3572 $this->fail('Expected exception with "' . $errorcode . '" error code in create_create'); 3573 } 3574 } catch (moodle_exception $e) { 3575 if ($errorcode === false) { 3576 $this->fail('Got "' . $errorcode . '" exception error code and no exception was expected'); 3577 } 3578 if ($e->errorcode != $errorcode) { 3579 $this->fail('Got "' . $e->errorcode. '" exception error code and "' . $errorcode . '" was expected'); 3580 } 3581 return; 3582 } 3583 3584 $this->assertEquals($startdate, $course1->startdate); 3585 $this->assertEquals($enddate, $course1->enddate); 3586 } 3587 3588 /** 3589 * Provider for test_course_enddate. 3590 * 3591 * @return array 3592 */ 3593 public function course_enddate_provider() { 3594 // Each provided example contains startdate, enddate and the expected exception error code if there is any. 3595 return [ 3596 [ 3597 111, 3598 222, 3599 false 3600 ], [ 3601 222, 3602 111, 3603 'enddatebeforestartdate' 3604 ], [ 3605 111, 3606 0, 3607 false 3608 ], [ 3609 0, 3610 222, 3611 'nostartdatenoenddate' 3612 ] 3613 ]; 3614 } 3615 3616 3617 /** 3618 * test_course_dates_reset 3619 * 3620 * @dataProvider course_dates_reset_provider 3621 * @param int $startdate 3622 * @param int $enddate 3623 * @param int $resetstartdate 3624 * @param int $resetenddate 3625 * @param int $resultingstartdate 3626 * @param int $resultingenddate 3627 */ 3628 public function test_course_dates_reset($startdate, $enddate, $resetstartdate, $resetenddate, $resultingstartdate, $resultingenddate) { 3629 global $CFG, $DB; 3630 3631 require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php'); 3632 3633 $this->resetAfterTest(true); 3634 3635 $this->setAdminUser(); 3636 3637 $CFG->enablecompletion = true; 3638 3639 $this->setTimezone('UTC'); 3640 3641 $record = array('startdate' => $startdate, 'enddate' => $enddate, 'enablecompletion' => 1); 3642 $originalcourse = $this->getDataGenerator()->create_course($record); 3643 $coursecriteria = new completion_criteria_date(array('course' => $originalcourse->id, 'timeend' => $startdate + DAYSECS)); 3644 $coursecriteria->insert(); 3645 3646 $activitycompletiondate = $startdate + DAYSECS; 3647 $data = $this->getDataGenerator()->create_module('data', array('course' => $originalcourse->id), 3648 array('completion' => 1, 'completionexpected' => $activitycompletiondate)); 3649 3650 $resetdata = new stdClass(); 3651 $resetdata->id = $originalcourse->id; 3652 $resetdata->reset_start_date_old = $originalcourse->startdate; 3653 $resetdata->reset_start_date = $resetstartdate; 3654 $resetdata->reset_end_date = $resetenddate; 3655 $resetdata->reset_end_date_old = $record['enddate']; 3656 reset_course_userdata($resetdata); 3657 3658 $course = $DB->get_record('course', array('id' => $originalcourse->id)); 3659 3660 $this->assertEquals($resultingstartdate, $course->startdate); 3661 $this->assertEquals($resultingenddate, $course->enddate); 3662 3663 $coursecompletioncriteria = completion_criteria_date::fetch(array('course' => $originalcourse->id)); 3664 $this->assertEquals($resultingstartdate + DAYSECS, $coursecompletioncriteria->timeend); 3665 3666 $this->assertEquals($resultingstartdate + DAYSECS, $DB->get_field('course_modules', 'completionexpected', 3667 array('id' => $data->cmid))); 3668 } 3669 3670 /** 3671 * Provider for test_course_dates_reset. 3672 * 3673 * @return array 3674 */ 3675 public function course_dates_reset_provider() { 3676 3677 // Each example contains the following: 3678 // - course startdate 3679 // - course enddate 3680 // - startdate to reset to (false if not reset) 3681 // - enddate to reset to (false if not reset) 3682 // - resulting startdate 3683 // - resulting enddate 3684 $time = 1445644800; 3685 return [ 3686 // No date changes. 3687 [ 3688 $time, 3689 $time + DAYSECS, 3690 false, 3691 false, 3692 $time, 3693 $time + DAYSECS 3694 ], 3695 // End date changes to a valid value. 3696 [ 3697 $time, 3698 $time + DAYSECS, 3699 false, 3700 $time + DAYSECS + 111, 3701 $time, 3702 $time + DAYSECS + 111 3703 ], 3704 // Start date changes to a valid value. End date does not get updated because it does not have value. 3705 [ 3706 $time, 3707 0, 3708 $time + DAYSECS, 3709 false, 3710 $time + DAYSECS, 3711 0 3712 ], 3713 // Start date changes to a valid value. End date gets updated accordingly. 3714 [ 3715 $time, 3716 $time + DAYSECS, 3717 $time + WEEKSECS, 3718 false, 3719 $time + WEEKSECS, 3720 $time + WEEKSECS + DAYSECS 3721 ], 3722 // Start date and end date change to a valid value. 3723 [ 3724 $time, 3725 $time + DAYSECS, 3726 $time + WEEKSECS, 3727 $time + YEARSECS, 3728 $time + WEEKSECS, 3729 $time + YEARSECS 3730 ] 3731 ]; 3732 } 3733 3734 /** 3735 * Test reset_course_userdata() 3736 * - with reset_roles_overrides enabled 3737 * - with selective role unenrolments 3738 */ 3739 public function test_course_roles_reset() { 3740 global $DB; 3741 3742 $this->resetAfterTest(true); 3743 3744 $generator = $this->getDataGenerator(); 3745 3746 // Create test course and user, enrol one in the other. 3747 $course = $generator->create_course(); 3748 $user = $generator->create_user(); 3749 $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST); 3750 $generator->enrol_user($user->id, $course->id, $roleid); 3751 3752 // Test case with reset_roles_overrides enabled. 3753 // Override course so it does NOT allow students 'mod/forum:viewdiscussion'. 3754 $coursecontext = context_course::instance($course->id); 3755 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id); 3756 3757 // Check expected capabilities so far. 3758 $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user)); 3759 3760 // Oops, preventing student from viewing forums was a mistake, let's reset the course. 3761 $resetdata = new stdClass(); 3762 $resetdata->id = $course->id; 3763 $resetdata->reset_roles_overrides = true; 3764 reset_course_userdata($resetdata); 3765 3766 // Check new expected capabilities - override at the course level should be reset. 3767 $this->assertTrue(has_capability('mod/forum:viewdiscussion', $coursecontext, $user)); 3768 3769 // Test case with selective role unenrolments. 3770 $roles = array(); 3771 $roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST); 3772 $roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST); 3773 3774 // We enrol a user with student and teacher roles. 3775 $generator->enrol_user($user->id, $course->id, $roles['student']); 3776 $generator->enrol_user($user->id, $course->id, $roles['teacher']); 3777 3778 // When we reset only student role, we expect to keep teacher role. 3779 $resetdata = new stdClass(); 3780 $resetdata->id = $course->id; 3781 $resetdata->unenrol_users = array($roles['student']); 3782 reset_course_userdata($resetdata); 3783 3784 $usersroles = enrol_get_course_users_roles($course->id); 3785 $this->assertArrayHasKey($user->id, $usersroles); 3786 $this->assertArrayHasKey($roles['teacher'], $usersroles[$user->id]); 3787 $this->assertArrayNotHasKey($roles['student'], $usersroles[$user->id]); 3788 $this->assertCount(1, $usersroles[$user->id]); 3789 3790 // We reenrol user as student. 3791 $generator->enrol_user($user->id, $course->id, $roles['student']); 3792 3793 // When we reset student and teacher roles, we expect no roles left. 3794 $resetdata = new stdClass(); 3795 $resetdata->id = $course->id; 3796 $resetdata->unenrol_users = array($roles['student'], $roles['teacher']); 3797 reset_course_userdata($resetdata); 3798 3799 $usersroles = enrol_get_course_users_roles($course->id); 3800 $this->assertEmpty($usersroles); 3801 } 3802 3803 public function test_course_check_module_updates_since() { 3804 global $CFG, $DB, $USER; 3805 require_once($CFG->dirroot . '/mod/glossary/lib.php'); 3806 require_once($CFG->dirroot . '/rating/lib.php'); 3807 require_once($CFG->dirroot . '/comment/lib.php'); 3808 3809 $this->resetAfterTest(true); 3810 3811 $CFG->enablecompletion = true; 3812 $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); 3813 $glossary = $this->getDataGenerator()->create_module('glossary', array( 3814 'course' => $course->id, 3815 'completion' => COMPLETION_TRACKING_AUTOMATIC, 3816 'completionview' => 1, 3817 'allowcomments' => 1, 3818 'assessed' => RATING_AGGREGATE_AVERAGE, 3819 'scale' => 100 3820 )); 3821 $glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary'); 3822 $context = context_module::instance($glossary->cmid); 3823 $modinfo = get_fast_modinfo($course); 3824 $cm = $modinfo->get_cm($glossary->cmid); 3825 $user = $this->getDataGenerator()->create_user(); 3826 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 3827 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); 3828 $from = time(); 3829 3830 $teacher = $this->getDataGenerator()->create_user(); 3831 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 3832 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); 3833 3834 assign_capability('mod/glossary:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id, true); 3835 3836 // Check nothing changed right now. 3837 $updates = course_check_module_updates_since($cm, $from); 3838 $this->assertFalse($updates->configuration->updated); 3839 $this->assertFalse($updates->completion->updated); 3840 $this->assertFalse($updates->gradeitems->updated); 3841 $this->assertFalse($updates->comments->updated); 3842 $this->assertFalse($updates->ratings->updated); 3843 $this->assertFalse($updates->introfiles->updated); 3844 $this->assertFalse($updates->outcomes->updated); 3845 3846 $this->waitForSecond(); 3847 3848 // Do some changes. 3849 $this->setUser($user); 3850 $entry = $glossarygenerator->create_content($glossary); 3851 3852 $this->setUser($teacher); 3853 // Name. 3854 set_coursemodule_name($glossary->cmid, 'New name'); 3855 3856 // Add some ratings. 3857 $rm = new rating_manager(); 3858 $result = $rm->add_rating($cm, $context, 'mod_glossary', 'entry', $entry->id, 100, 50, $user->id, RATING_AGGREGATE_AVERAGE); 3859 3860 // Change grades. 3861 $glossary->cmidnumber = $glossary->cmid; 3862 glossary_update_grades($glossary, $user->id); 3863 3864 $this->setUser($user); 3865 // Completion status. 3866 glossary_view($glossary, $course, $cm, $context, 'letter'); 3867 3868 // Add one comment. 3869 $args = new stdClass; 3870 $args->context = $context; 3871 $args->course = $course; 3872 $args->cm = $cm; 3873 $args->area = 'glossary_entry'; 3874 $args->itemid = $entry->id; 3875 $args->client_id = 1; 3876 $args->component = 'mod_glossary'; 3877 $manager = new comment($args); 3878 $manager->add('blah blah blah'); 3879 3880 // Check upgrade status. 3881 $updates = course_check_module_updates_since($cm, $from); 3882 $this->assertTrue($updates->configuration->updated); 3883 $this->assertTrue($updates->completion->updated); 3884 $this->assertTrue($updates->gradeitems->updated); 3885 $this->assertTrue($updates->comments->updated); 3886 $this->assertTrue($updates->ratings->updated); 3887 $this->assertFalse($updates->introfiles->updated); 3888 $this->assertFalse($updates->outcomes->updated); 3889 } 3890 3891 public function test_async_module_deletion_hook_implemented() { 3892 // Async module deletion depends on the 'true' being returned by at least one plugin implementing the hook, 3893 // 'course_module_adhoc_deletion_recommended'. In core, is implemented by the course recyclebin, which will only return 3894 // true if the recyclebin plugin is enabled. To make sure async deletion occurs, this test force-enables the recyclebin. 3895 global $DB, $USER; 3896 $this->resetAfterTest(true); 3897 $this->setAdminUser(); 3898 3899 // Ensure recyclebin is enabled. 3900 set_config('coursebinenable', true, 'tool_recyclebin'); 3901 3902 // Create course, module and context. 3903 $course = $this->getDataGenerator()->create_course(['numsections' => 5]); 3904 $module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]); 3905 $modcontext = context_module::instance($module->cmid); 3906 3907 // Verify context exists. 3908 $this->assertInstanceOf('context_module', $modcontext); 3909 3910 // Check events generated on the course_delete_module call. 3911 $sink = $this->redirectEvents(); 3912 3913 // Try to delete the module using the async flag. 3914 course_delete_module($module->cmid, true); // Try to delete the module asynchronously. 3915 3916 // Verify that no event has been generated yet. 3917 $events = $sink->get_events(); 3918 $event = array_pop($events); 3919 $sink->close(); 3920 $this->assertEmpty($event); 3921 3922 // Grab the record, in it's final state before hard deletion, for comparison with the event snapshot. 3923 // We need to do this because the 'deletioninprogress' flag has changed from '0' to '1'. 3924 $cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST); 3925 3926 // Verify the course_module is marked as 'deletioninprogress'. 3927 $this->assertNotEquals($cm, false); 3928 $this->assertEquals($cm->deletioninprogress, '1'); 3929 3930 // Verify the context has not yet been removed. 3931 $this->assertEquals($modcontext, context_module::instance($module->cmid, IGNORE_MISSING)); 3932 3933 // Set up a sink to catch the 'course_module_deleted' event. 3934 $sink = $this->redirectEvents(); 3935 3936 // Now, run the adhoc task which performs the hard deletion. 3937 phpunit_util::run_all_adhoc_tasks(); 3938 3939 // Fetch and validate the event data. 3940 $events = $sink->get_events(); 3941 $event = array_pop($events); 3942 $sink->close(); 3943 $this->assertInstanceOf('\core\event\course_module_deleted', $event); 3944 $this->assertEquals($module->cmid, $event->objectid); 3945 $this->assertEquals($USER->id, $event->userid); 3946 $this->assertEquals('course_modules', $event->objecttable); 3947 $this->assertEquals(null, $event->get_url()); 3948 $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid)); 3949 3950 // Verify the context has been removed. 3951 $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING)); 3952 3953 // Verify the course_module record has been deleted. 3954 $cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]); 3955 $this->assertEmpty($cmcount); 3956 } 3957 3958 public function test_async_module_deletion_hook_not_implemented() { 3959 // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns 3960 // 'true' from the 'course_module_adhoc_deletion_recommended' hook. 3961 // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it. 3962 global $DB, $USER; 3963 $this->resetAfterTest(true); 3964 $this->setAdminUser(); 3965 set_config('coursebinenable', false, 'tool_recyclebin'); 3966 3967 // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test. 3968 // If at least one plugin still returns true, then skip this test. 3969 if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) { 3970 foreach ($pluginsfunction as $plugintype => $plugins) { 3971 foreach ($plugins as $pluginfunction) { 3972 if ($pluginfunction()) { 3973 $this->markTestSkipped(); 3974 } 3975 } 3976 } 3977 } 3978 3979 // Create course, module and context. 3980 $course = $this->getDataGenerator()->create_course(['numsections' => 5]); 3981 $module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]); 3982 $modcontext = context_module::instance($module->cmid); 3983 $cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST); 3984 3985 // Verify context exists. 3986 $this->assertInstanceOf('context_module', $modcontext); 3987 3988 // Check events generated on the course_delete_module call. 3989 $sink = $this->redirectEvents(); 3990 3991 // Try to delete the module using the async flag. 3992 course_delete_module($module->cmid, true); // Try to delete the module asynchronously. 3993 3994 // Fetch and validate the event data. 3995 $events = $sink->get_events(); 3996 $event = array_pop($events); 3997 $sink->close(); 3998 $this->assertInstanceOf('\core\event\course_module_deleted', $event); 3999 $this->assertEquals($module->cmid, $event->objectid); 4000 $this->assertEquals($USER->id, $event->userid); 4001 $this->assertEquals('course_modules', $event->objecttable); 4002 $this->assertEquals(null, $event->get_url()); 4003 $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid)); 4004 4005 // Verify the context has been removed. 4006 $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING)); 4007 4008 // Verify the course_module record has been deleted. 4009 $cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]); 4010 $this->assertEmpty($cmcount); 4011 } 4012 4013 public function test_async_section_deletion_hook_implemented() { 4014 // Async section deletion (provided section contains modules), depends on the 'true' being returned by at least one plugin 4015 // implementing the 'course_module_adhoc_deletion_recommended' hook. In core, is implemented by the course recyclebin, 4016 // which will only return true if the plugin is enabled. To make sure async deletion occurs, this test enables recyclebin. 4017 global $DB, $USER; 4018 $this->resetAfterTest(true); 4019 $this->setAdminUser(); 4020 4021 // Ensure recyclebin is enabled. 4022 set_config('coursebinenable', true, 'tool_recyclebin'); 4023 4024 // Create course, module and context. 4025 $generator = $this->getDataGenerator(); 4026 $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]); 4027 $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); 4028 $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); 4029 $assign2 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); 4030 $assign3 = $generator->create_module('assign', ['course' => $course, 'section' => 0]); 4031 4032 // Delete empty section. No difference from normal, synchronous behaviour. 4033 $this->assertTrue(course_delete_section($course, 4, false, true)); 4034 $this->assertEquals(3, course_get_format($course)->get_last_section_number()); 4035 4036 // Delete a module in section 2 (using async). Need to verify this doesn't generate two tasks when we delete 4037 // the section in the next step. 4038 course_delete_module($assign2->cmid, true); 4039 4040 // Confirm that the module is pending deletion in its current section. 4041 $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison. 4042 $this->assertEquals(true, $DB->record_exists('course_modules', ['id' => $assign2->cmid, 'deletioninprogress' => 1, 4043 'section' => $section->id])); 4044 4045 // Now, delete section 2. 4046 $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change. 4047 4048 $sink = $this->redirectEvents(); // To capture the event. 4049 $this->assertTrue(course_delete_section($course, 2, true, true)); 4050 4051 // Now, confirm that: 4052 // a) the section's modules have been flagged for deletion and moved to section 0 and; 4053 // b) the section has been deleted and; 4054 // c) course_section_deleted event has been fired. The course_module_deleted events will only fire once they have been 4055 // removed from section 0 via the adhoc task. 4056 4057 // Modules should have been flagged for deletion and moved to section 0. 4058 $sectionid = $DB->get_field('course_sections', 'id', ['course' => $course->id, 'section' => 0]); 4059 $this->assertEquals(3, $DB->count_records('course_modules', ['section' => $sectionid, 'deletioninprogress' => 1])); 4060 4061 // Confirm the section has been deleted. 4062 $this->assertEquals(2, course_get_format($course)->get_last_section_number()); 4063 4064 // Check event fired. 4065 $events = $sink->get_events(); 4066 $event = array_pop($events); 4067 $sink->close(); 4068 $this->assertInstanceOf('\core\event\course_section_deleted', $event); 4069 $this->assertEquals($section->id, $event->objectid); 4070 $this->assertEquals($USER->id, $event->userid); 4071 $this->assertEquals('course_sections', $event->objecttable); 4072 $this->assertEquals(null, $event->get_url()); 4073 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id)); 4074 4075 // Now, run the adhoc task to delete the modules from section 0. 4076 $sink = $this->redirectEvents(); // To capture the events. 4077 phpunit_util::run_all_adhoc_tasks(); 4078 4079 // Confirm the modules have been deleted. 4080 list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid, $assign2->cmid]); 4081 $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids); 4082 $this->assertEmpty($cmcount); 4083 4084 // Confirm other modules in section 0 still remain. 4085 $this->assertEquals(1, $DB->count_records('course_modules', ['id' => $assign3->cmid])); 4086 4087 // Confirm that events were generated for all 3 of the modules. 4088 $events = $sink->get_events(); 4089 $sink->close(); 4090 $count = 0; 4091 while (!empty($events)) { 4092 $event = array_pop($events); 4093 if ($event instanceof \core\event\course_module_deleted && 4094 in_array($event->objectid, [$assign0->cmid, $assign1->cmid, $assign2->cmid])) { 4095 $count++; 4096 } 4097 } 4098 $this->assertEquals(3, $count); 4099 } 4100 4101 public function test_async_section_deletion_hook_not_implemented() { 4102 // If no plugins advocate async removal, then normal synchronous removal will take place. 4103 // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns 4104 // 'true' from the 'course_module_adhoc_deletion_recommended' hook. 4105 // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it. 4106 global $DB, $USER; 4107 $this->resetAfterTest(true); 4108 $this->setAdminUser(); 4109 set_config('coursebinenable', false, 'tool_recyclebin'); 4110 4111 // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test. 4112 // If at least one plugin still returns true, then skip this test. 4113 if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) { 4114 foreach ($pluginsfunction as $plugintype => $plugins) { 4115 foreach ($plugins as $pluginfunction) { 4116 if ($pluginfunction()) { 4117 $this->markTestSkipped(); 4118 } 4119 } 4120 } 4121 } 4122 4123 // Create course, module and context. 4124 $generator = $this->getDataGenerator(); 4125 $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]); 4126 $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); 4127 $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]); 4128 4129 // Delete empty section. No difference from normal, synchronous behaviour. 4130 $this->assertTrue(course_delete_section($course, 4, false, true)); 4131 $this->assertEquals(3, course_get_format($course)->get_last_section_number()); 4132 4133 // Delete section in the middle (2). 4134 $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison. 4135 $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change. 4136 4137 $sink = $this->redirectEvents(); // To capture the event. 4138 $this->assertTrue(course_delete_section($course, 2, true, true)); 4139 4140 // Now, confirm that: 4141 // a) The section's modules have deleted and; 4142 // b) the section has been deleted and; 4143 // c) course_section_deleted event has been fired and; 4144 // d) course_module_deleted events have both been fired. 4145 4146 // Confirm modules have been deleted. 4147 list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid]); 4148 $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids); 4149 $this->assertEmpty($cmcount); 4150 4151 // Confirm the section has been deleted. 4152 $this->assertEquals(2, course_get_format($course)->get_last_section_number()); 4153 4154 // Confirm the course_section_deleted event has been generated. 4155 $events = $sink->get_events(); 4156 $event = array_pop($events); 4157 $sink->close(); 4158 $this->assertInstanceOf('\core\event\course_section_deleted', $event); 4159 $this->assertEquals($section->id, $event->objectid); 4160 $this->assertEquals($USER->id, $event->userid); 4161 $this->assertEquals('course_sections', $event->objecttable); 4162 $this->assertEquals(null, $event->get_url()); 4163 $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id)); 4164 4165 // Confirm that the course_module_deleted events have both been generated. 4166 $count = 0; 4167 while (!empty($events)) { 4168 $event = array_pop($events); 4169 if ($event instanceof \core\event\course_module_deleted && 4170 in_array($event->objectid, [$assign0->cmid, $assign1->cmid])) { 4171 $count++; 4172 } 4173 } 4174 $this->assertEquals(2, $count); 4175 } 4176 4177 public function test_classify_course_for_timeline() { 4178 global $DB, $CFG; 4179 4180 require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php'); 4181 4182 set_config('enablecompletion', COMPLETION_ENABLED); 4183 set_config('coursegraceperiodbefore', 0); 4184 set_config('coursegraceperiodafter', 0); 4185 4186 $this->resetAfterTest(true); 4187 $this->setAdminUser(); 4188 4189 // Create courses for testing. 4190 $generator = $this->getDataGenerator(); 4191 $future = time() + 3600; 4192 $past = time() - 3600; 4193 $futurecourse = $generator->create_course(['startdate' => $future]); 4194 $pastcourse = $generator->create_course(['startdate' => $past - 60, 'enddate' => $past]); 4195 $completedcourse = $generator->create_course(['enablecompletion' => COMPLETION_ENABLED]); 4196 $inprogresscourse = $generator->create_course(); 4197 4198 // Set completion rules. 4199 $criteriadata = new stdClass(); 4200 $criteriadata->id = $completedcourse->id; 4201 4202 // Self completion. 4203 $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF; 4204 $class = 'completion_criteria_self'; 4205 $criterion = new $class(); 4206 $criterion->update_config($criteriadata); 4207 4208 $user = $this->getDataGenerator()->create_user(); 4209 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 4210 $this->getDataGenerator()->enrol_user($user->id, $futurecourse->id, $studentrole->id); 4211 $this->getDataGenerator()->enrol_user($user->id, $pastcourse->id, $studentrole->id); 4212 $this->getDataGenerator()->enrol_user($user->id, $completedcourse->id, $studentrole->id); 4213 $this->getDataGenerator()->enrol_user($user->id, $inprogresscourse->id, $studentrole->id); 4214 4215 $this->setUser($user); 4216 core_completion_external::mark_course_self_completed($completedcourse->id); 4217 $ccompletion = new completion_completion(array('course' => $completedcourse->id, 'userid' => $user->id)); 4218 $ccompletion->mark_complete(); 4219 4220 // Aggregate the completions. 4221 $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($pastcourse)); 4222 $this->assertEquals(COURSE_TIMELINE_FUTURE, course_classify_for_timeline($futurecourse)); 4223 $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse)); 4224 $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse)); 4225 4226 // Test grace period. 4227 set_config('coursegraceperiodafter', 1); 4228 set_config('coursegraceperiodbefore', 1); 4229 $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($pastcourse)); 4230 $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($futurecourse)); 4231 $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse)); 4232 $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse)); 4233 } 4234 4235 /** 4236 * Test the main function for updating all calendar events for a module. 4237 */ 4238 public function test_course_module_calendar_event_update_process() { 4239 global $DB; 4240 4241 $this->resetAfterTest(); 4242 $this->setAdminUser(); 4243 4244 $completionexpected = time(); 4245 $duedate = time(); 4246 4247 $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]); 4248 $assign = $this->getDataGenerator()->create_module('assign', [ 4249 'course' => $course, 4250 'completionexpected' => $completionexpected, 4251 'duedate' => $duedate 4252 ]); 4253 4254 $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id); 4255 $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]); 4256 // Check that both events are using the expected dates. 4257 foreach ($events as $event) { 4258 if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) { 4259 $this->assertEquals($completionexpected, $event->timestart); 4260 } 4261 if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) { 4262 $this->assertEquals($duedate, $event->timestart); 4263 } 4264 } 4265 4266 // We have to manually update the module and the course module. 4267 $newcompletionexpected = time() + DAYSECS * 60; 4268 $newduedate = time() + DAYSECS * 45; 4269 $newmodulename = 'Assign - new name'; 4270 4271 $moduleobject = (object)array('id' => $assign->id, 'duedate' => $newduedate, 'name' => $newmodulename); 4272 $DB->update_record('assign', $moduleobject); 4273 $cmobject = (object)array('id' => $cm->id, 'completionexpected' => $newcompletionexpected); 4274 $DB->update_record('course_modules', $cmobject); 4275 4276 $assign = $DB->get_record('assign', ['id' => $assign->id]); 4277 $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id); 4278 4279 course_module_calendar_event_update_process($assign, $cm); 4280 4281 $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]); 4282 // Now check that the details have been updated properly from the function. 4283 foreach ($events as $event) { 4284 if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) { 4285 $this->assertEquals($newcompletionexpected, $event->timestart); 4286 $this->assertEquals(get_string('completionexpectedfor', 'completion', (object)['instancename' => $newmodulename]), 4287 $event->name); 4288 } 4289 if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) { 4290 $this->assertEquals($newduedate, $event->timestart); 4291 $this->assertEquals(get_string('calendardue', 'assign', $newmodulename), $event->name); 4292 } 4293 } 4294 } 4295 4296 /** 4297 * Test the higher level checks for updating calendar events for an instance. 4298 */ 4299 public function test_course_module_update_calendar_events() { 4300 $this->resetAfterTest(); 4301 $this->setAdminUser(); 4302 4303 $completionexpected = time(); 4304 $duedate = time(); 4305 4306 $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]); 4307 $assign = $this->getDataGenerator()->create_module('assign', [ 4308 'course' => $course, 4309 'completionexpected' => $completionexpected, 4310 'duedate' => $duedate 4311 ]); 4312 4313 $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id); 4314 4315 // Both the instance and cm objects are missing. 4316 $this->assertFalse(course_module_update_calendar_events('assign')); 4317 // Just using the assign instance. 4318 $this->assertTrue(course_module_update_calendar_events('assign', $assign)); 4319 // Just using the course module object. 4320 $this->assertTrue(course_module_update_calendar_events('assign', null, $cm)); 4321 // Using both the assign instance and the course module object. 4322 $this->assertTrue(course_module_update_calendar_events('assign', $assign, $cm)); 4323 } 4324 4325 /** 4326 * Test the higher level checks for updating calendar events for a module. 4327 */ 4328 public function test_course_module_bulk_update_calendar_events() { 4329 $this->resetAfterTest(); 4330 $this->setAdminUser(); 4331 4332 $completionexpected = time(); 4333 $duedate = time(); 4334 4335 $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]); 4336 $course2 = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]); 4337 $assign = $this->getDataGenerator()->create_module('assign', [ 4338 'course' => $course, 4339 'completionexpected' => $completionexpected, 4340 'duedate' => $duedate 4341 ]); 4342 4343 // No assign instances in this course. 4344 $this->assertFalse(course_module_bulk_update_calendar_events('assign', $course2->id)); 4345 // No book instances for the site. 4346 $this->assertFalse(course_module_bulk_update_calendar_events('book')); 4347 // Update all assign instances. 4348 $this->assertTrue(course_module_bulk_update_calendar_events('assign')); 4349 // Update the assign instances for this course. 4350 $this->assertTrue(course_module_bulk_update_calendar_events('assign', $course->id)); 4351 } 4352 4353 /** 4354 * Test that a student can view participants in a course they are enrolled in. 4355 */ 4356 public function test_course_can_view_participants_as_student() { 4357 $this->resetAfterTest(); 4358 4359 $course = $this->getDataGenerator()->create_course(); 4360 $coursecontext = context_course::instance($course->id); 4361 4362 $user = $this->getDataGenerator()->create_user(); 4363 $this->getDataGenerator()->enrol_user($user->id, $course->id); 4364 4365 $this->setUser($user); 4366 4367 $this->assertTrue(course_can_view_participants($coursecontext)); 4368 } 4369 4370 /** 4371 * Test that a student in a course can not view participants on the site. 4372 */ 4373 public function test_course_can_view_participants_as_student_on_site() { 4374 $this->resetAfterTest(); 4375 4376 $course = $this->getDataGenerator()->create_course(); 4377 4378 $user = $this->getDataGenerator()->create_user(); 4379 $this->getDataGenerator()->enrol_user($user->id, $course->id); 4380 4381 $this->setUser($user); 4382 4383 $this->assertFalse(course_can_view_participants(context_system::instance())); 4384 } 4385 4386 /** 4387 * Test that an admin can view participants on the site. 4388 */ 4389 public function test_course_can_view_participants_as_admin_on_site() { 4390 $this->resetAfterTest(); 4391 4392 $this->setAdminUser(); 4393 4394 $this->assertTrue(course_can_view_participants(context_system::instance())); 4395 } 4396 4397 /** 4398 * Test teachers can view participants in a course they are enrolled in. 4399 */ 4400 public function test_course_can_view_participants_as_teacher() { 4401 global $DB; 4402 4403 $this->resetAfterTest(); 4404 4405 $course = $this->getDataGenerator()->create_course(); 4406 $coursecontext = context_course::instance($course->id); 4407 4408 $user = $this->getDataGenerator()->create_user(); 4409 $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 4410 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid); 4411 4412 $this->setUser($user); 4413 4414 $this->assertTrue(course_can_view_participants($coursecontext)); 4415 } 4416 4417 /** 4418 * Check the teacher can still view the participants page without the 'viewparticipants' cap. 4419 */ 4420 public function test_course_can_view_participants_as_teacher_without_view_participants_cap() { 4421 global $DB; 4422 4423 $this->resetAfterTest(); 4424 4425 $course = $this->getDataGenerator()->create_course(); 4426 $coursecontext = context_course::instance($course->id); 4427 4428 $user = $this->getDataGenerator()->create_user(); 4429 $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 4430 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid); 4431 4432 $this->setUser($user); 4433 4434 // Disable one of the capabilties. 4435 assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext); 4436 4437 // Should still be able to view the page as they have the 'moodle/course:enrolreview' cap. 4438 $this->assertTrue(course_can_view_participants($coursecontext)); 4439 } 4440 4441 /** 4442 * Check the teacher can still view the participants page without the 'moodle/course:enrolreview' cap. 4443 */ 4444 public function test_course_can_view_participants_as_teacher_without_enrol_review_cap() { 4445 global $DB; 4446 4447 $this->resetAfterTest(); 4448 4449 $course = $this->getDataGenerator()->create_course(); 4450 $coursecontext = context_course::instance($course->id); 4451 4452 $user = $this->getDataGenerator()->create_user(); 4453 $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 4454 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid); 4455 4456 $this->setUser($user); 4457 4458 // Disable one of the capabilties. 4459 assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext); 4460 4461 // Should still be able to view the page as they have the 'moodle/course:viewparticipants' cap. 4462 $this->assertTrue(course_can_view_participants($coursecontext)); 4463 } 4464 4465 /** 4466 * Check the teacher can not view the participants page without the required caps. 4467 */ 4468 public function test_course_can_view_participants_as_teacher_without_required_caps() { 4469 global $DB; 4470 4471 $this->resetAfterTest(); 4472 4473 $course = $this->getDataGenerator()->create_course(); 4474 $coursecontext = context_course::instance($course->id); 4475 4476 $user = $this->getDataGenerator()->create_user(); 4477 $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); 4478 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid); 4479 4480 $this->setUser($user); 4481 4482 // Disable the capabilities. 4483 assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext); 4484 assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext); 4485 4486 $this->assertFalse(course_can_view_participants($coursecontext)); 4487 } 4488 4489 /** 4490 * Check that an exception is not thrown if we can view the participants page. 4491 */ 4492 public function test_course_require_view_participants() { 4493 $this->resetAfterTest(); 4494 4495 $course = $this->getDataGenerator()->create_course(); 4496 $coursecontext = context_course::instance($course->id); 4497 4498 $user = $this->getDataGenerator()->create_user(); 4499 $this->getDataGenerator()->enrol_user($user->id, $course->id); 4500 4501 $this->setUser($user); 4502 4503 course_require_view_participants($coursecontext); 4504 } 4505 4506 /** 4507 * Check that an exception is thrown if we can't view the participants page. 4508 */ 4509 public function test_course_require_view_participants_as_student_on_site() { 4510 $this->resetAfterTest(); 4511 4512 $course = $this->getDataGenerator()->create_course(); 4513 4514 $user = $this->getDataGenerator()->create_user(); 4515 $this->getDataGenerator()->enrol_user($user->id, $course->id); 4516 4517 $this->setUser($user); 4518 4519 $this->expectException('required_capability_exception'); 4520 course_require_view_participants(context_system::instance()); 4521 } 4522 4523 /** 4524 * Testing the can_download_from_backup_filearea fn. 4525 */ 4526 public function test_can_download_from_backup_filearea() { 4527 global $DB; 4528 $this->resetAfterTest(); 4529 $course = $this->getDataGenerator()->create_course(); 4530 $context = context_course::instance($course->id); 4531 $user = $this->getDataGenerator()->create_user(); 4532 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 4533 $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id); 4534 4535 // The 'automated' backup area. Downloading from this area requires two capabilities. 4536 // If the user has only the 'backup:downloadfile' capability. 4537 unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context); 4538 assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context); 4539 $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user)); 4540 4541 // If the user has only the 'restore:userinfo' capability. 4542 unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context); 4543 assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context); 4544 $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user)); 4545 4546 // If the user has both capabilities. 4547 assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context); 4548 assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context); 4549 $this->assertTrue(can_download_from_backup_filearea('automated', $context, $user)); 4550 4551 // Is the user has neither of the capabilities. 4552 unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context); 4553 unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context); 4554 $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user)); 4555 4556 // The 'course ' and 'backup' backup file areas. These are governed by the same download capability. 4557 // User has the capability. 4558 unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context); 4559 assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context); 4560 $this->assertTrue(can_download_from_backup_filearea('course', $context, $user)); 4561 $this->assertTrue(can_download_from_backup_filearea('backup', $context, $user)); 4562 4563 // User doesn't have the capability. 4564 unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context); 4565 $this->assertFalse(can_download_from_backup_filearea('course', $context, $user)); 4566 $this->assertFalse(can_download_from_backup_filearea('backup', $context, $user)); 4567 4568 // A file area that doesn't exist. No permissions, regardless of capabilities. 4569 assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context); 4570 $this->assertFalse(can_download_from_backup_filearea('testing', $context, $user)); 4571 } 4572 4573 /** 4574 * Test cases for the course_classify_courses_for_timeline test. 4575 */ 4576 public function get_course_classify_courses_for_timeline_test_cases() { 4577 $now = time(); 4578 $day = 86400; 4579 4580 return [ 4581 'no courses' => [ 4582 'coursesdata' => [], 4583 'expected' => [ 4584 COURSE_TIMELINE_PAST => [], 4585 COURSE_TIMELINE_FUTURE => [], 4586 COURSE_TIMELINE_INPROGRESS => [] 4587 ] 4588 ], 4589 'only past' => [ 4590 'coursesdata' => [ 4591 [ 4592 'shortname' => 'past1', 4593 'startdate' => $now - ($day * 2), 4594 'enddate' => $now - $day 4595 ], 4596 [ 4597 'shortname' => 'past2', 4598 'startdate' => $now - ($day * 2), 4599 'enddate' => $now - $day 4600 ] 4601 ], 4602 'expected' => [ 4603 COURSE_TIMELINE_PAST => ['past1', 'past2'], 4604 COURSE_TIMELINE_FUTURE => [], 4605 COURSE_TIMELINE_INPROGRESS => [] 4606 ] 4607 ], 4608 'only in progress' => [ 4609 'coursesdata' => [ 4610 [ 4611 'shortname' => 'inprogress1', 4612 'startdate' => $now - $day, 4613 'enddate' => $now + $day 4614 ], 4615 [ 4616 'shortname' => 'inprogress2', 4617 'startdate' => $now - $day, 4618 'enddate' => $now + $day 4619 ] 4620 ], 4621 'expected' => [ 4622 COURSE_TIMELINE_PAST => [], 4623 COURSE_TIMELINE_FUTURE => [], 4624 COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2'] 4625 ] 4626 ], 4627 'only future' => [ 4628 'coursesdata' => [ 4629 [ 4630 'shortname' => 'future1', 4631 'startdate' => $now + $day 4632 ], 4633 [ 4634 'shortname' => 'future2', 4635 'startdate' => $now + $day 4636 ] 4637 ], 4638 'expected' => [ 4639 COURSE_TIMELINE_PAST => [], 4640 COURSE_TIMELINE_FUTURE => ['future1', 'future2'], 4641 COURSE_TIMELINE_INPROGRESS => [] 4642 ] 4643 ], 4644 'combination' => [ 4645 'coursesdata' => [ 4646 [ 4647 'shortname' => 'past1', 4648 'startdate' => $now - ($day * 2), 4649 'enddate' => $now - $day 4650 ], 4651 [ 4652 'shortname' => 'past2', 4653 'startdate' => $now - ($day * 2), 4654 'enddate' => $now - $day 4655 ], 4656 [ 4657 'shortname' => 'inprogress1', 4658 'startdate' => $now - $day, 4659 'enddate' => $now + $day 4660 ], 4661 [ 4662 'shortname' => 'inprogress2', 4663 'startdate' => $now - $day, 4664 'enddate' => $now + $day 4665 ], 4666 [ 4667 'shortname' => 'future1', 4668 'startdate' => $now + $day 4669 ], 4670 [ 4671 'shortname' => 'future2', 4672 'startdate' => $now + $day 4673 ] 4674 ], 4675 'expected' => [ 4676 COURSE_TIMELINE_PAST => ['past1', 'past2'], 4677 COURSE_TIMELINE_FUTURE => ['future1', 'future2'], 4678 COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2'] 4679 ] 4680 ], 4681 ]; 4682 } 4683 4684 /** 4685 * Test the course_classify_courses_for_timeline function. 4686 * 4687 * @dataProvider get_course_classify_courses_for_timeline_test_cases() 4688 * @param array $coursesdata Courses to create 4689 * @param array $expected Expected test results. 4690 */ 4691 public function test_course_classify_courses_for_timeline($coursesdata, $expected) { 4692 $this->resetAfterTest(); 4693 $generator = $this->getDataGenerator(); 4694 4695 $courses = array_map(function($coursedata) use ($generator) { 4696 return $generator->create_course($coursedata); 4697 }, $coursesdata); 4698 4699 sort($expected[COURSE_TIMELINE_PAST]); 4700 sort($expected[COURSE_TIMELINE_FUTURE]); 4701 sort($expected[COURSE_TIMELINE_INPROGRESS]); 4702 4703 $results = course_classify_courses_for_timeline($courses); 4704 4705 $actualpast = array_map(function($result) { 4706 return $result->shortname; 4707 }, $results[COURSE_TIMELINE_PAST]); 4708 4709 $actualfuture = array_map(function($result) { 4710 return $result->shortname; 4711 }, $results[COURSE_TIMELINE_FUTURE]); 4712 4713 $actualinprogress = array_map(function($result) { 4714 return $result->shortname; 4715 }, $results[COURSE_TIMELINE_INPROGRESS]); 4716 4717 sort($actualpast); 4718 sort($actualfuture); 4719 sort($actualinprogress); 4720 4721 $this->assertEquals($expected[COURSE_TIMELINE_PAST], $actualpast); 4722 $this->assertEquals($expected[COURSE_TIMELINE_FUTURE], $actualfuture); 4723 $this->assertEquals($expected[COURSE_TIMELINE_INPROGRESS], $actualinprogress); 4724 } 4725 4726 /** 4727 * Test cases for the course_get_enrolled_courses_for_logged_in_user tests. 4728 */ 4729 public function get_course_get_enrolled_courses_for_logged_in_user_test_cases() { 4730 $buildexpectedresult = function($limit, $offset) { 4731 $result = []; 4732 for ($i = $offset; $i < $offset + $limit; $i++) { 4733 $result[] = "testcourse{$i}"; 4734 } 4735 return $result; 4736 }; 4737 4738 return [ 4739 'zero records' => [ 4740 'dbquerylimit' => 3, 4741 'totalcourses' => 0, 4742 'limit' => 0, 4743 'offset' => 0, 4744 'expecteddbqueries' => 4, 4745 'expectedresult' => $buildexpectedresult(0, 0) 4746 ], 4747 'less than query limit' => [ 4748 'dbquerylimit' => 3, 4749 'totalcourses' => 2, 4750 'limit' => 0, 4751 'offset' => 0, 4752 'expecteddbqueries' => 2, 4753 'expectedresult' => $buildexpectedresult(2, 0) 4754 ], 4755 'more than query limit' => [ 4756 'dbquerylimit' => 3, 4757 'totalcourses' => 7, 4758 'limit' => 0, 4759 'offset' => 0, 4760 'expecteddbqueries' => 4, 4761 'expectedresult' => $buildexpectedresult(7, 0) 4762 ], 4763 'limit less than query limit' => [ 4764 'dbquerylimit' => 3, 4765 'totalcourses' => 7, 4766 'limit' => 2, 4767 'offset' => 0, 4768 'expecteddbqueries' => 2, 4769 'expectedresult' => $buildexpectedresult(2, 0) 4770 ], 4771 'limit less than query limit with offset' => [ 4772 'dbquerylimit' => 3, 4773 'totalcourses' => 7, 4774 'limit' => 2, 4775 'offset' => 2, 4776 'expecteddbqueries' => 2, 4777 'expectedresult' => $buildexpectedresult(2, 2) 4778 ], 4779 'limit less than total' => [ 4780 'dbquerylimit' => 3, 4781 'totalcourses' => 9, 4782 'limit' => 6, 4783 'offset' => 0, 4784 'expecteddbqueries' => 3, 4785 'expectedresult' => $buildexpectedresult(6, 0) 4786 ], 4787 'less results than limit' => [ 4788 'dbquerylimit' => 4, 4789 'totalcourses' => 9, 4790 'limit' => 20, 4791 'offset' => 0, 4792 'expecteddbqueries' => 4, 4793 'expectedresult' => $buildexpectedresult(9, 0) 4794 ], 4795 'less results than limit exact divisible' => [ 4796 'dbquerylimit' => 3, 4797 'totalcourses' => 9, 4798 'limit' => 20, 4799 'offset' => 0, 4800 'expecteddbqueries' => 5, 4801 'expectedresult' => $buildexpectedresult(9, 0) 4802 ], 4803 'less results than limit with offset' => [ 4804 'dbquerylimit' => 3, 4805 'totalcourses' => 9, 4806 'limit' => 10, 4807 'offset' => 5, 4808 'expecteddbqueries' => 3, 4809 'expectedresult' => $buildexpectedresult(4, 5) 4810 ], 4811 ]; 4812 } 4813 4814 /** 4815 * Test the course_get_enrolled_courses_for_logged_in_user function. 4816 * 4817 * @dataProvider get_course_get_enrolled_courses_for_logged_in_user_test_cases() 4818 * @param int $dbquerylimit Number of records to load per DB request 4819 * @param int $totalcourses Number of courses to create 4820 * @param int $limit Maximum number of results to get. 4821 * @param int $offset Skip this number of results from the start of the result set. 4822 * @param int $expecteddbqueries The number of DB queries expected during the test. 4823 * @param array $expectedresult Expected test results. 4824 */ 4825 public function test_course_get_enrolled_courses_for_logged_in_user( 4826 $dbquerylimit, 4827 $totalcourses, 4828 $limit, 4829 $offset, 4830 $expecteddbqueries, 4831 $expectedresult 4832 ) { 4833 global $DB; 4834 4835 $this->resetAfterTest(); 4836 $generator = $this->getDataGenerator(); 4837 $student = $generator->create_user(); 4838 4839 for ($i = 0; $i < $totalcourses; $i++) { 4840 $shortname = "testcourse{$i}"; 4841 $course = $generator->create_course(['shortname' => $shortname]); 4842 $generator->enrol_user($student->id, $course->id, 'student'); 4843 } 4844 4845 $this->setUser($student); 4846 4847 $initialquerycount = $DB->perf_get_queries(); 4848 $courses = course_get_enrolled_courses_for_logged_in_user($limit, $offset, 'shortname ASC', 'shortname', $dbquerylimit); 4849 4850 // Loop over the result set to force the lazy loading to kick in so that we can check the 4851 // number of DB queries. 4852 $actualresult = array_map(function($course) { 4853 return $course->shortname; 4854 }, iterator_to_array($courses, false)); 4855 4856 sort($expectedresult); 4857 4858 $this->assertEquals($expectedresult, $actualresult); 4859 $this->assertLessThanOrEqual($expecteddbqueries, $DB->perf_get_queries() - $initialquerycount); 4860 } 4861 4862 /** 4863 * Test cases for the course_filter_courses_by_timeline_classification tests. 4864 */ 4865 public function get_course_filter_courses_by_timeline_classification_test_cases() { 4866 $now = time(); 4867 $day = 86400; 4868 4869 $coursedata = [ 4870 [ 4871 'shortname' => 'apast', 4872 'startdate' => $now - ($day * 2), 4873 'enddate' => $now - $day 4874 ], 4875 [ 4876 'shortname' => 'bpast', 4877 'startdate' => $now - ($day * 2), 4878 'enddate' => $now - $day 4879 ], 4880 [ 4881 'shortname' => 'cpast', 4882 'startdate' => $now - ($day * 2), 4883 'enddate' => $now - $day 4884 ], 4885 [ 4886 'shortname' => 'dpast', 4887 'startdate' => $now - ($day * 2), 4888 'enddate' => $now - $day 4889 ], 4890 [ 4891 'shortname' => 'epast', 4892 'startdate' => $now - ($day * 2), 4893 'enddate' => $now - $day 4894 ], 4895 [ 4896 'shortname' => 'ainprogress', 4897 'startdate' => $now - $day, 4898 'enddate' => $now + $day 4899 ], 4900 [ 4901 'shortname' => 'binprogress', 4902 'startdate' => $now - $day, 4903 'enddate' => $now + $day 4904 ], 4905 [ 4906 'shortname' => 'cinprogress', 4907 'startdate' => $now - $day, 4908 'enddate' => $now + $day 4909 ], 4910 [ 4911 'shortname' => 'dinprogress', 4912 'startdate' => $now - $day, 4913 'enddate' => $now + $day 4914 ], 4915 [ 4916 'shortname' => 'einprogress', 4917 'startdate' => $now - $day, 4918 'enddate' => $now + $day 4919 ], 4920 [ 4921 'shortname' => 'afuture', 4922 'startdate' => $now + $day 4923 ], 4924 [ 4925 'shortname' => 'bfuture', 4926 'startdate' => $now + $day 4927 ], 4928 [ 4929 'shortname' => 'cfuture', 4930 'startdate' => $now + $day 4931 ], 4932 [ 4933 'shortname' => 'dfuture', 4934 'startdate' => $now + $day 4935 ], 4936 [ 4937 'shortname' => 'efuture', 4938 'startdate' => $now + $day 4939 ] 4940 ]; 4941 4942 // Raw enrolled courses result set should be returned in this order: 4943 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast, 4944 // dfuture, dinprogress, dpast, efuture, einprogress, epast 4945 // 4946 // By classification the offset values for each record should be: 4947 // COURSE_TIMELINE_FUTURE 4948 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture) 4949 // COURSE_TIMELINE_INPROGRESS 4950 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress) 4951 // COURSE_TIMELINE_PAST 4952 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast). 4953 return [ 4954 'empty set' => [ 4955 'coursedata' => [], 4956 'classification' => COURSE_TIMELINE_FUTURE, 4957 'limit' => 2, 4958 'offset' => 0, 4959 'expectedcourses' => [], 4960 'expectedprocessedcount' => 0 4961 ], 4962 // COURSE_TIMELINE_FUTURE. 4963 'future not limit no offset' => [ 4964 'coursedata' => $coursedata, 4965 'classification' => COURSE_TIMELINE_FUTURE, 4966 'limit' => 0, 4967 'offset' => 0, 4968 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 4969 'expectedprocessedcount' => 15 4970 ], 4971 'future no offset' => [ 4972 'coursedata' => $coursedata, 4973 'classification' => COURSE_TIMELINE_FUTURE, 4974 'limit' => 2, 4975 'offset' => 0, 4976 'expectedcourses' => ['afuture', 'bfuture'], 4977 'expectedprocessedcount' => 4 4978 ], 4979 'future offset' => [ 4980 'coursedata' => $coursedata, 4981 'classification' => COURSE_TIMELINE_FUTURE, 4982 'limit' => 2, 4983 'offset' => 2, 4984 'expectedcourses' => ['bfuture', 'cfuture'], 4985 'expectedprocessedcount' => 5 4986 ], 4987 'future exact limit' => [ 4988 'coursedata' => $coursedata, 4989 'classification' => COURSE_TIMELINE_FUTURE, 4990 'limit' => 5, 4991 'offset' => 0, 4992 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 4993 'expectedprocessedcount' => 13 4994 ], 4995 'future limit less results' => [ 4996 'coursedata' => $coursedata, 4997 'classification' => COURSE_TIMELINE_FUTURE, 4998 'limit' => 10, 4999 'offset' => 0, 5000 'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'], 5001 'expectedprocessedcount' => 15 5002 ], 5003 'future limit less results with offset' => [ 5004 'coursedata' => $coursedata, 5005 'classification' => COURSE_TIMELINE_FUTURE, 5006 'limit' => 10, 5007 'offset' => 5, 5008 'expectedcourses' => ['cfuture', 'dfuture', 'efuture'], 5009 'expectedprocessedcount' => 10 5010 ], 5011 ]; 5012 } 5013 5014 /** 5015 * Test the course_get_enrolled_courses_for_logged_in_user_from_search function. 5016 */ 5017 public function test_course_get_enrolled_courses_for_logged_in_user_from_search() { 5018 global $DB; 5019 5020 // Set up. 5021 5022 $this->resetAfterTest(); 5023 $generator = $this->getDataGenerator(); 5024 $student = $generator->create_user(); 5025 5026 $cat1 = core_course_category::create(['name' => 'Cat1']); 5027 $cat2 = core_course_category::create(['name' => 'Cat2', 'parent' => $cat1->id]); 5028 $c1 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => 'Magic', 'idnumber' => 'ID3']); 5029 $c2 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => 'Magic']); 5030 $c3 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test Magic']); 5031 $c4 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => 'Magic', 'idnumber' => 'ID4']); 5032 5033 $c5 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => 'Magic']); 5034 $c6 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => 'Magic']); 5035 $c7 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => 'Magic']); 5036 $c8 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => 'Magic']); 5037 5038 for ($i = 1; $i < 9; $i++) { 5039 $generator->enrol_user($student->id, ${"c$i"}->id, 'student'); 5040 } 5041 5042 $this->setUser($student); 5043 5044 $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search( 5045 0, 5046 0, 5047 'id ASC', 5048 null, 5049 COURSE_DB_QUERY_LIMIT, 5050 ['search' => 'test'], 5051 ['idonly' => true] 5052 ); 5053 5054 $actualresult = array_map(function($course) { 5055 return $course->id; 5056 }, iterator_to_array($returnedcourses, false)); 5057 5058 $this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c7->id, $c8->id], $actualresult); 5059 5060 // Test no courses matching the search. 5061 $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search( 5062 0, 5063 0, 5064 'id ASC', 5065 null, 5066 COURSE_DB_QUERY_LIMIT, 5067 ['search' => 'foobar'], 5068 ['idonly' => true] 5069 ); 5070 5071 $actualresult = array_map(function($course) { 5072 return $course->id; 5073 }, iterator_to_array($returnedcourses, false)); 5074 5075 $this->assertEquals([], $actualresult); 5076 5077 // Test returning all courses that have a mutual summary. 5078 $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search( 5079 0, 5080 0, 5081 'id ASC', 5082 null, 5083 COURSE_DB_QUERY_LIMIT, 5084 ['search' => 'Magic'], 5085 ['idonly' => true] 5086 ); 5087 5088 $actualresult = array_map(function($course) { 5089 return $course->id; 5090 }, iterator_to_array($returnedcourses, false)); 5091 5092 $this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c6->id, $c7->id, $c8->id], $actualresult); 5093 5094 // Test returning a unique course. 5095 $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search( 5096 0, 5097 0, 5098 'id ASC', 5099 null, 5100 COURSE_DB_QUERY_LIMIT, 5101 ['search' => 'Дискретная'], 5102 ['idonly' => true] 5103 ); 5104 5105 $actualresult = array_map(function($course) { 5106 return $course->id; 5107 }, iterator_to_array($returnedcourses, false)); 5108 5109 $this->assertEquals([$c6->id], $actualresult); 5110 } 5111 5112 /** 5113 * Test the course_filter_courses_by_timeline_classification function. 5114 * 5115 * @dataProvider get_course_filter_courses_by_timeline_classification_test_cases() 5116 * @param array $coursedata Course test data to create. 5117 * @param string $classification Timeline classification. 5118 * @param int $limit Maximum number of results to return. 5119 * @param int $offset Results to skip at the start of the result set. 5120 * @param string[] $expectedcourses Expected courses in results. 5121 * @param int $expectedprocessedcount Expected number of course records to be processed. 5122 */ 5123 public function test_course_filter_courses_by_timeline_classification( 5124 $coursedata, 5125 $classification, 5126 $limit, 5127 $offset, 5128 $expectedcourses, 5129 $expectedprocessedcount 5130 ) { 5131 $this->resetAfterTest(); 5132 $generator = $this->getDataGenerator(); 5133 5134 $courses = array_map(function($coursedata) use ($generator) { 5135 return $generator->create_course($coursedata); 5136 }, $coursedata); 5137 5138 $student = $generator->create_user(); 5139 5140 foreach ($courses as $course) { 5141 $generator->enrol_user($student->id, $course->id, 'student'); 5142 } 5143 5144 $this->setUser($student); 5145 5146 $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname'); 5147 list($result, $processedcount) = course_filter_courses_by_timeline_classification( 5148 $coursesgenerator, 5149 $classification, 5150 $limit 5151 ); 5152 5153 $actual = array_map(function($course) { 5154 return $course->shortname; 5155 }, $result); 5156 5157 $this->assertEquals($expectedcourses, $actual); 5158 $this->assertEquals($expectedprocessedcount, $processedcount); 5159 } 5160 5161 /** 5162 * Test cases for the course_filter_courses_by_timeline_classification tests. 5163 */ 5164 public function get_course_filter_courses_by_customfield_test_cases() { 5165 global $CFG; 5166 require_once($CFG->dirroot.'/blocks/myoverview/lib.php'); 5167 $coursedata = [ 5168 [ 5169 'shortname' => 'C1', 5170 'customfield_checkboxfield' => 1, 5171 'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'), 5172 'customfield_selectfield' => 1, 5173 'customfield_textfield' => 'fish', 5174 ], 5175 [ 5176 'shortname' => 'C2', 5177 'customfield_checkboxfield' => 0, 5178 'customfield_datefield' => strtotime('1980-08-05T13:00:00Z'), 5179 ], 5180 [ 5181 'shortname' => 'C3', 5182 'customfield_checkboxfield' => 0, 5183 'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'), 5184 'customfield_selectfield' => 2, 5185 'customfield_textfield' => 'dog', 5186 ], 5187 [ 5188 'shortname' => 'C4', 5189 'customfield_checkboxfield' => 1, 5190 'customfield_selectfield' => 3, 5191 'customfield_textfield' => 'cat', 5192 ], 5193 [ 5194 'shortname' => 'C5', 5195 'customfield_datefield' => strtotime('1980-08-06T13:00:00Z'), 5196 'customfield_selectfield' => 2, 5197 'customfield_textfield' => 'fish', 5198 ], 5199 ]; 5200 5201 return [ 5202 'empty set' => [ 5203 'coursedata' => [], 5204 'customfield' => 'checkboxfield', 5205 'customfieldvalue' => 1, 5206 'limit' => 10, 5207 'offset' => 0, 5208 'expectedcourses' => [], 5209 'expectedprocessedcount' => 0 5210 ], 5211 'checkbox yes' => [ 5212 'coursedata' => $coursedata, 5213 'customfield' => 'checkboxfield', 5214 'customfieldvalue' => 1, 5215 'limit' => 10, 5216 'offset' => 0, 5217 'expectedcourses' => ['C1', 'C4'], 5218 'expectedprocessedcount' => 5 5219 ], 5220 'checkbox no' => [ 5221 'coursedata' => $coursedata, 5222 'customfield' => 'checkboxfield', 5223 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY, 5224 'limit' => 10, 5225 'offset' => 0, 5226 'expectedcourses' => ['C2', 'C3', 'C5'], 5227 'expectedprocessedcount' => 5 5228 ], 5229 'date 1 Feb 2001' => [ 5230 'coursedata' => $coursedata, 5231 'customfield' => 'datefield', 5232 'customfieldvalue' => strtotime('2001-02-01T12:00:00Z'), 5233 'limit' => 10, 5234 'offset' => 0, 5235 'expectedcourses' => ['C1', 'C3'], 5236 'expectedprocessedcount' => 5 5237 ], 5238 'date 6 Aug 1980' => [ 5239 'coursedata' => $coursedata, 5240 'customfield' => 'datefield', 5241 'customfieldvalue' => strtotime('1980-08-06T13:00:00Z'), 5242 'limit' => 10, 5243 'offset' => 0, 5244 'expectedcourses' => ['C5'], 5245 'expectedprocessedcount' => 5 5246 ], 5247 'date no date' => [ 5248 'coursedata' => $coursedata, 5249 'customfield' => 'datefield', 5250 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY, 5251 'limit' => 10, 5252 'offset' => 0, 5253 'expectedcourses' => ['C4'], 5254 'expectedprocessedcount' => 5 5255 ], 5256 'select Option 1' => [ 5257 'coursedata' => $coursedata, 5258 'customfield' => 'selectfield', 5259 'customfieldvalue' => 1, 5260 'limit' => 10, 5261 'offset' => 0, 5262 'expectedcourses' => ['C1'], 5263 'expectedprocessedcount' => 5 5264 ], 5265 'select Option 2' => [ 5266 'coursedata' => $coursedata, 5267 'customfield' => 'selectfield', 5268 'customfieldvalue' => 2, 5269 'limit' => 10, 5270 'offset' => 0, 5271 'expectedcourses' => ['C3', 'C5'], 5272 'expectedprocessedcount' => 5 5273 ], 5274 'select no select' => [ 5275 'coursedata' => $coursedata, 5276 'customfield' => 'selectfield', 5277 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY, 5278 'limit' => 10, 5279 'offset' => 0, 5280 'expectedcourses' => ['C2'], 5281 'expectedprocessedcount' => 5 5282 ], 5283 'text fish' => [ 5284 'coursedata' => $coursedata, 5285 'customfield' => 'textfield', 5286 'customfieldvalue' => 'fish', 5287 'limit' => 10, 5288 'offset' => 0, 5289 'expectedcourses' => ['C1', 'C5'], 5290 'expectedprocessedcount' => 5 5291 ], 5292 'text dog' => [ 5293 'coursedata' => $coursedata, 5294 'customfield' => 'textfield', 5295 'customfieldvalue' => 'dog', 5296 'limit' => 10, 5297 'offset' => 0, 5298 'expectedcourses' => ['C3'], 5299 'expectedprocessedcount' => 5 5300 ], 5301 'text no text' => [ 5302 'coursedata' => $coursedata, 5303 'customfield' => 'textfield', 5304 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY, 5305 'limit' => 10, 5306 'offset' => 0, 5307 'expectedcourses' => ['C2'], 5308 'expectedprocessedcount' => 5 5309 ], 5310 'checkbox limit no' => [ 5311 'coursedata' => $coursedata, 5312 'customfield' => 'checkboxfield', 5313 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY, 5314 'limit' => 2, 5315 'offset' => 0, 5316 'expectedcourses' => ['C2', 'C3'], 5317 'expectedprocessedcount' => 3 5318 ], 5319 'checkbox limit offset no' => [ 5320 'coursedata' => $coursedata, 5321 'customfield' => 'checkboxfield', 5322 'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY, 5323 'limit' => 2, 5324 'offset' => 3, 5325 'expectedcourses' => ['C5'], 5326 'expectedprocessedcount' => 2 5327 ], 5328 ]; 5329 } 5330 5331 /** 5332 * Test the course_filter_courses_by_customfield function. 5333 * 5334 * @dataProvider get_course_filter_courses_by_customfield_test_cases() 5335 * @param array $coursedata Course test data to create. 5336 * @param string $customfield Shortname of the customfield. 5337 * @param string $customfieldvalue the value to filter by. 5338 * @param int $limit Maximum number of results to return. 5339 * @param int $offset Results to skip at the start of the result set. 5340 * @param string[] $expectedcourses Expected courses in results. 5341 * @param int $expectedprocessedcount Expected number of course records to be processed. 5342 */ 5343 public function test_course_filter_courses_by_customfield( 5344 $coursedata, 5345 $customfield, 5346 $customfieldvalue, 5347 $limit, 5348 $offset, 5349 $expectedcourses, 5350 $expectedprocessedcount 5351 ) { 5352 $this->resetAfterTest(); 5353 $generator = $this->getDataGenerator(); 5354 5355 // Create the custom fields. 5356 $generator->create_custom_field_category([ 5357 'name' => 'Course fields', 5358 'component' => 'core_course', 5359 'area' => 'course', 5360 'itemid' => 0, 5361 ]); 5362 $generator->create_custom_field([ 5363 'name' => 'Checkbox field', 5364 'category' => 'Course fields', 5365 'type' => 'checkbox', 5366 'shortname' => 'checkboxfield', 5367 ]); 5368 $generator->create_custom_field([ 5369 'name' => 'Date field', 5370 'category' => 'Course fields', 5371 'type' => 'date', 5372 'shortname' => 'datefield', 5373 'configdata' => '{"mindate":0, "maxdate":0}', 5374 ]); 5375 $generator->create_custom_field([ 5376 'name' => 'Select field', 5377 'category' => 'Course fields', 5378 'type' => 'select', 5379 'shortname' => 'selectfield', 5380 'configdata' => '{"options":"Option 1\nOption 2\nOption 3\nOption 4"}', 5381 ]); 5382 $generator->create_custom_field([ 5383 'name' => 'Text field', 5384 'category' => 'Course fields', 5385 'type' => 'text', 5386 'shortname' => 'textfield', 5387 ]); 5388 5389 $courses = array_map(function($coursedata) use ($generator) { 5390 return $generator->create_course($coursedata); 5391 }, $coursedata); 5392 5393 $student = $generator->create_user(); 5394 5395 foreach ($courses as $course) { 5396 $generator->enrol_user($student->id, $course->id, 'student'); 5397 } 5398 5399 $this->setUser($student); 5400 5401 $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname'); 5402 list($result, $processedcount) = course_filter_courses_by_customfield( 5403 $coursesgenerator, 5404 $customfield, 5405 $customfieldvalue, 5406 $limit 5407 ); 5408 5409 $actual = array_map(function($course) { 5410 return $course->shortname; 5411 }, $result); 5412 5413 $this->assertEquals($expectedcourses, $actual); 5414 $this->assertEquals($expectedprocessedcount, $processedcount); 5415 } 5416 5417 /** 5418 * Test cases for the course_filter_courses_by_timeline_classification w/ hidden courses tests. 5419 */ 5420 public function get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases() { 5421 $now = time(); 5422 $day = 86400; 5423 5424 $coursedata = [ 5425 [ 5426 'shortname' => 'apast', 5427 'startdate' => $now - ($day * 2), 5428 'enddate' => $now - $day 5429 ], 5430 [ 5431 'shortname' => 'bpast', 5432 'startdate' => $now - ($day * 2), 5433 'enddate' => $now - $day 5434 ], 5435 [ 5436 'shortname' => 'cpast', 5437 'startdate' => $now - ($day * 2), 5438 'enddate' => $now - $day 5439 ], 5440 [ 5441 'shortname' => 'dpast', 5442 'startdate' => $now - ($day * 2), 5443 'enddate' => $now - $day 5444 ], 5445 [ 5446 'shortname' => 'epast', 5447 'startdate' => $now - ($day * 2), 5448 'enddate' => $now - $day 5449 ], 5450 [ 5451 'shortname' => 'ainprogress', 5452 'startdate' => $now - $day, 5453 'enddate' => $now + $day 5454 ], 5455 [ 5456 'shortname' => 'binprogress', 5457 'startdate' => $now - $day, 5458 'enddate' => $now + $day 5459 ], 5460 [ 5461 'shortname' => 'cinprogress', 5462 'startdate' => $now - $day, 5463 'enddate' => $now + $day 5464 ], 5465 [ 5466 'shortname' => 'dinprogress', 5467 'startdate' => $now - $day, 5468 'enddate' => $now + $day 5469 ], 5470 [ 5471 'shortname' => 'einprogress', 5472 'startdate' => $now - $day, 5473 'enddate' => $now + $day 5474 ], 5475 [ 5476 'shortname' => 'afuture', 5477 'startdate' => $now + $day 5478 ], 5479 [ 5480 'shortname' => 'bfuture', 5481 'startdate' => $now + $day 5482 ], 5483 [ 5484 'shortname' => 'cfuture', 5485 'startdate' => $now + $day 5486 ], 5487 [ 5488 'shortname' => 'dfuture', 5489 'startdate' => $now + $day 5490 ], 5491 [ 5492 'shortname' => 'efuture', 5493 'startdate' => $now + $day 5494 ] 5495 ]; 5496 5497 // Raw enrolled courses result set should be returned in this order: 5498 // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast, 5499 // dfuture, dinprogress, dpast, efuture, einprogress, epast 5500 // 5501 // By classification the offset values for each record should be: 5502 // COURSE_TIMELINE_FUTURE 5503 // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture) 5504 // COURSE_TIMELINE_INPROGRESS 5505 // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress) 5506 // COURSE_TIMELINE_PAST 5507 // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast). 5508 return [ 5509 'empty set' => [ 5510 'coursedata' => [], 5511 'classification' => COURSE_TIMELINE_FUTURE, 5512 'limit' => 2, 5513 'offset' => 0, 5514 'expectedcourses' => [], 5515 'expectedprocessedcount' => 0, 5516 'hiddencourse' => '' 5517 ], 5518 // COURSE_TIMELINE_FUTURE. 5519 'future not limit no offset' => [ 5520 'coursedata' => $coursedata, 5521 'classification' => COURSE_TIMELINE_FUTURE, 5522 'limit' => 0, 5523 'offset' => 0, 5524 'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'], 5525 'expectedprocessedcount' => 15, 5526 'hiddencourse' => 'bfuture' 5527 ], 5528 'future no offset' => [ 5529 'coursedata' => $coursedata, 5530 'classification' => COURSE_TIMELINE_FUTURE, 5531 'limit' => 2, 5532 'offset' => 0, 5533 'expectedcourses' => ['afuture', 'cfuture'], 5534 'expectedprocessedcount' => 7, 5535 'hiddencourse' => 'bfuture' 5536 ], 5537 'future offset' => [ 5538 'coursedata' => $coursedata, 5539 'classification' => COURSE_TIMELINE_FUTURE, 5540 'limit' => 2, 5541 'offset' => 2, 5542 'expectedcourses' => ['bfuture', 'dfuture'], 5543 'expectedprocessedcount' => 8, 5544 'hiddencourse' => 'cfuture' 5545 ], 5546 'future exact limit' => [ 5547 'coursedata' => $coursedata, 5548 'classification' => COURSE_TIMELINE_FUTURE, 5549 'limit' => 5, 5550 'offset' => 0, 5551 'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'], 5552 'expectedprocessedcount' => 15, 5553 'hiddencourse' => 'bfuture' 5554 ], 5555 'future limit less results' => [ 5556 'coursedata' => $coursedata, 5557 'classification' => COURSE_TIMELINE_FUTURE, 5558 'limit' => 10, 5559 'offset' => 0, 5560 'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'], 5561 'expectedprocessedcount' => 15, 5562 'hiddencourse' => 'bfuture' 5563 ], 5564 'future limit less results with offset' => [ 5565 'coursedata' => $coursedata, 5566 'classification' => COURSE_TIMELINE_FUTURE, 5567 'limit' => 10, 5568 'offset' => 5, 5569 'expectedcourses' => ['cfuture', 'efuture'], 5570 'expectedprocessedcount' => 10, 5571 'hiddencourse' => 'dfuture' 5572 ], 5573 ]; 5574 } 5575 5576 /** 5577 * Test the course_filter_courses_by_timeline_classification function hidden courses. 5578 * 5579 * @dataProvider get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases() 5580 * @param array $coursedata Course test data to create. 5581 * @param string $classification Timeline classification. 5582 * @param int $limit Maximum number of results to return. 5583 * @param int $offset Results to skip at the start of the result set. 5584 * @param string[] $expectedcourses Expected courses in results. 5585 * @param int $expectedprocessedcount Expected number of course records to be processed. 5586 * @param int $hiddencourse The course to hide as part of this process 5587 */ 5588 public function test_course_filter_courses_by_timeline_classification_with_hidden_courses( 5589 $coursedata, 5590 $classification, 5591 $limit, 5592 $offset, 5593 $expectedcourses, 5594 $expectedprocessedcount, 5595 $hiddencourse 5596 ) { 5597 $this->resetAfterTest(); 5598 $generator = $this->getDataGenerator(); 5599 $student = $generator->create_user(); 5600 $this->setUser($student); 5601 5602 $courses = array_map(function($coursedata) use ($generator, $hiddencourse) { 5603 $course = $generator->create_course($coursedata); 5604 if ($course->shortname == $hiddencourse) { 5605 set_user_preference('block_myoverview_hidden_course_' . $course->id, true); 5606 } 5607 return $course; 5608 }, $coursedata); 5609 5610 foreach ($courses as $course) { 5611 $generator->enrol_user($student->id, $course->id, 'student'); 5612 } 5613 5614 $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname'); 5615 list($result, $processedcount) = course_filter_courses_by_timeline_classification( 5616 $coursesgenerator, 5617 $classification, 5618 $limit 5619 ); 5620 5621 $actual = array_map(function($course) { 5622 return $course->shortname; 5623 }, $result); 5624 5625 $this->assertEquals($expectedcourses, $actual); 5626 $this->assertEquals($expectedprocessedcount, $processedcount); 5627 } 5628 5629 5630 /** 5631 * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has no end date. 5632 */ 5633 public function test_core_course_core_calendar_get_valid_event_timestart_range_no_enddate() { 5634 global $CFG; 5635 require_once($CFG->dirroot . "/calendar/lib.php"); 5636 5637 $this->resetAfterTest(true); 5638 $this->setAdminUser(); 5639 $generator = $this->getDataGenerator(); 5640 $now = time(); 5641 $course = $generator->create_course(['startdate' => $now - 86400]); 5642 5643 // Create a course event. 5644 $event = new \calendar_event([ 5645 'name' => 'Test course event', 5646 'eventtype' => 'course', 5647 'courseid' => $course->id, 5648 ]); 5649 5650 list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course); 5651 $this->assertEquals($course->startdate, $min[0]); 5652 $this->assertNull($max); 5653 } 5654 5655 /** 5656 * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has end date. 5657 */ 5658 public function test_core_course_core_calendar_get_valid_event_timestart_range_with_enddate() { 5659 global $CFG; 5660 require_once($CFG->dirroot . "/calendar/lib.php"); 5661 5662 $this->resetAfterTest(true); 5663 $this->setAdminUser(); 5664 $generator = $this->getDataGenerator(); 5665 $now = time(); 5666 $course = $generator->create_course(['startdate' => $now - 86400, 'enddate' => $now + 86400]); 5667 5668 // Create a course event. 5669 $event = new \calendar_event([ 5670 'name' => 'Test course event', 5671 'eventtype' => 'course', 5672 'courseid' => $course->id, 5673 ]); 5674 5675 list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course); 5676 $this->assertEquals($course->startdate, $min[0]); 5677 $this->assertNull($max); 5678 } 5679 5680 /** 5681 * Test the course_get_recent_courses function. 5682 */ 5683 public function test_course_get_recent_courses() { 5684 global $DB; 5685 5686 $this->resetAfterTest(); 5687 $generator = $this->getDataGenerator(); 5688 5689 $courses = array(); 5690 for ($i = 1; $i < 4; $i++) { 5691 $courses[] = $generator->create_course(); 5692 }; 5693 5694 $student = $generator->create_user(); 5695 5696 foreach ($courses as $course) { 5697 $generator->enrol_user($student->id, $course->id, 'student'); 5698 } 5699 5700 $this->setUser($student); 5701 5702 $result = course_get_recent_courses($student->id); 5703 5704 // No course accessed. 5705 $this->assertCount(0, $result); 5706 5707 $time = time(); 5708 foreach ($courses as $course) { 5709 $context = context_course::instance($course->id); 5710 course_view($context); 5711 $DB->set_field('user_lastaccess', 'timeaccess', $time, [ 5712 'userid' => $student->id, 5713 'courseid' => $course->id, 5714 ]); 5715 $time++; 5716 } 5717 5718 // Every course accessed. 5719 $result = course_get_recent_courses($student->id); 5720 $this->assertCount(3, $result); 5721 5722 // Every course accessed, result limited to 2 courses. 5723 $result = course_get_recent_courses($student->id, 2); 5724 $this->assertCount(2, $result); 5725 5726 // Every course accessed, with limit and offset should return the first course. 5727 $result = course_get_recent_courses($student->id, 3, 2); 5728 $this->assertCount(1, $result); 5729 $this->assertArrayHasKey($courses[0]->id, $result); 5730 5731 // Every course accessed, order by shortname DESC. The last create course ($course[2]) should have the greater shortname. 5732 $result = course_get_recent_courses($student->id, 0, 0, 'shortname DESC'); 5733 $this->assertCount(3, $result); 5734 $this->assertEquals($courses[2]->id, array_values($result)[0]->id); 5735 $this->assertEquals($courses[1]->id, array_values($result)[1]->id); 5736 $this->assertEquals($courses[0]->id, array_values($result)[2]->id); 5737 5738 // Every course accessed, order by shortname ASC. 5739 $result = course_get_recent_courses($student->id, 0, 0, 'shortname ASC'); 5740 $this->assertCount(3, $result); 5741 $this->assertEquals($courses[0]->id, array_values($result)[0]->id); 5742 $this->assertEquals($courses[1]->id, array_values($result)[1]->id); 5743 $this->assertEquals($courses[2]->id, array_values($result)[2]->id); 5744 5745 $guestcourse = $generator->create_course( 5746 (object)array('shortname' => 'guestcourse', 5747 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED, 5748 'enrol_guest_password_0' => '')); 5749 $context = context_course::instance($guestcourse->id); 5750 course_view($context); 5751 5752 // Every course accessed, even the not enrolled one. 5753 $result = course_get_recent_courses($student->id); 5754 $this->assertCount(4, $result); 5755 5756 // Suspended student. 5757 $this->getDataGenerator()->enrol_user($student->id, $courses[0]->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED); 5758 5759 // The course with suspended enrolment is not returned by the function. 5760 $result = course_get_recent_courses($student->id); 5761 $this->assertCount(3, $result); 5762 $this->assertArrayNotHasKey($courses[0]->id, $result); 5763 } 5764 5765 /** 5766 * Test the validation of the sort value in course_get_recent_courses(). 5767 * 5768 * @dataProvider course_get_recent_courses_sort_validation_provider 5769 * @param string $sort The sort value 5770 * @param string $expectedexceptionmsg The expected exception message 5771 */ 5772 public function test_course_get_recent_courses_sort_validation(string $sort, string $expectedexceptionmsg) { 5773 $this->resetAfterTest(); 5774 5775 $user = $this->getDataGenerator()->create_user(); 5776 5777 if (!empty($expectedexceptionmsg)) { 5778 $this->expectException('invalid_parameter_exception'); 5779 $this->expectExceptionMessage($expectedexceptionmsg); 5780 } 5781 course_get_recent_courses($user->id, 0, 0, $sort); 5782 } 5783 5784 /** 5785 * Data provider for test_course_get_recent_courses_sort_validation(). 5786 * 5787 * @return array 5788 */ 5789 function course_get_recent_courses_sort_validation_provider() { 5790 return [ 5791 'Invalid sort format (SQL injection attempt)' => 5792 [ 5793 'shortname DESC LIMIT 1--', 5794 'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].', 5795 ], 5796 'Sort uses \'sort by\' field that does not exist' => 5797 [ 5798 'shortname DESC, xyz ASC', 5799 'Invalid field in the sort parameter, allowed fields: id, idnumber, summary, summaryformat, ' . 5800 'startdate, enddate, category, shortname, fullname, timeaccess, component, visible, ' . 5801 'showactivitydates, showcompletionconditions.', 5802 ], 5803 'Sort uses invalid value for the sorting direction' => 5804 [ 5805 'shortname xyz, lastaccess', 5806 'Invalid sort direction in the sort parameter, allowed values: asc, desc.', 5807 ], 5808 'Valid sort format' => 5809 [ 5810 'shortname asc, timeaccess', 5811 '' 5812 ] 5813 ]; 5814 } 5815 5816 /** 5817 * Test the course_get_recent_courses function. 5818 */ 5819 public function test_course_get_recent_courses_with_guest() { 5820 global $DB; 5821 $this->resetAfterTest(true); 5822 5823 $student = $this->getDataGenerator()->create_user(); 5824 5825 // Course 1 with guest access and no direct enrolment. 5826 $course1 = $this->getDataGenerator()->create_course(); 5827 $context1 = context_course::instance($course1->id); 5828 $record = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'guest']); 5829 enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED); 5830 5831 // Course 2 where student is enrolled with two enrolment methods. 5832 $course2 = $this->getDataGenerator()->create_course(); 5833 $context2 = context_course::instance($course2->id); 5834 $record = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'self']); 5835 enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED); 5836 $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE); 5837 $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'self', 0, 0, ENROL_USER_ACTIVE); 5838 5839 // Course 3. 5840 $course3 = $this->getDataGenerator()->create_course(); 5841 $context3 = context_course::instance($course3->id); 5842 5843 // Student visits first two courses, course_get_recent_courses returns two courses. 5844 $this->setUser($student); 5845 course_view($context1); 5846 course_view($context2); 5847 5848 $result = course_get_recent_courses($student->id); 5849 $this->assertEqualsCanonicalizing([$course2->id, $course1->id], array_column($result, 'id')); 5850 5851 // Admin visits all three courses. Only the one with guest access is returned. 5852 $this->setAdminUser(); 5853 course_view($context1); 5854 course_view($context2); 5855 course_view($context3); 5856 $result = course_get_recent_courses(get_admin()->id); 5857 $this->assertEqualsCanonicalizing([$course1->id], array_column($result, 'id')); 5858 } 5859 5860 /** 5861 * Test cases for the course_get_course_dates_for_user_ids tests. 5862 */ 5863 public function get_course_get_course_dates_for_user_ids_test_cases() { 5864 $now = time(); 5865 $pastcoursestart = $now - 100; 5866 $futurecoursestart = $now + 100; 5867 5868 return [ 5869 'future course start fixed no users enrolled' => [ 5870 'relativedatemode' => false, 5871 'coursestart' => $futurecoursestart, 5872 'usercount' => 2, 5873 'enrolmentmethods' => [ 5874 ['manual', ENROL_INSTANCE_ENABLED], 5875 ['self', ENROL_INSTANCE_ENABLED] 5876 ], 5877 'enrolled' => [[], []], 5878 'expected' => [ 5879 [ 5880 'start' => $futurecoursestart, 5881 'startoffset' => 0 5882 ], 5883 [ 5884 'start' => $futurecoursestart, 5885 'startoffset' => 0 5886 ] 5887 ] 5888 ], 5889 'future course start fixed 1 users enrolled future' => [ 5890 'relativedatemode' => false, 5891 'coursestart' => $futurecoursestart, 5892 'usercount' => 2, 5893 'enrolmentmethods' => [ 5894 ['manual', ENROL_INSTANCE_ENABLED], 5895 ['self', ENROL_INSTANCE_ENABLED] 5896 ], 5897 'enrolled' => [ 5898 // User 1. 5899 ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], 5900 // User 2. 5901 [] 5902 ], 5903 'expected' => [ 5904 [ 5905 'start' => $futurecoursestart, 5906 'startoffset' => 0 5907 ], 5908 [ 5909 'start' => $futurecoursestart, 5910 'startoffset' => 0 5911 ] 5912 ] 5913 ], 5914 'future course start fixed 1 users enrolled past' => [ 5915 'relativedatemode' => false, 5916 'coursestart' => $futurecoursestart, 5917 'usercount' => 2, 5918 'enrolmentmethods' => [ 5919 ['manual', ENROL_INSTANCE_ENABLED], 5920 ['self', ENROL_INSTANCE_ENABLED] 5921 ], 5922 'enrolled' => [ 5923 // User 1. 5924 ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], 5925 // User 2. 5926 [] 5927 ], 5928 'expected' => [ 5929 [ 5930 'start' => $futurecoursestart, 5931 'startoffset' => 0 5932 ], 5933 [ 5934 'start' => $futurecoursestart, 5935 'startoffset' => 0 5936 ] 5937 ] 5938 ], 5939 'future course start fixed 2 users enrolled future' => [ 5940 'relativedatemode' => false, 5941 'coursestart' => $futurecoursestart, 5942 'usercount' => 2, 5943 'enrolmentmethods' => [ 5944 ['manual', ENROL_INSTANCE_ENABLED], 5945 ['self', ENROL_INSTANCE_ENABLED] 5946 ], 5947 'enrolled' => [ 5948 // User 1. 5949 ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], 5950 // User 2. 5951 ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]] 5952 ], 5953 'expected' => [ 5954 [ 5955 'start' => $futurecoursestart, 5956 'startoffset' => 0 5957 ], 5958 [ 5959 'start' => $futurecoursestart, 5960 'startoffset' => 0 5961 ] 5962 ] 5963 ], 5964 'future course start fixed 2 users enrolled past' => [ 5965 'relativedatemode' => false, 5966 'coursestart' => $futurecoursestart, 5967 'usercount' => 2, 5968 'enrolmentmethods' => [ 5969 ['manual', ENROL_INSTANCE_ENABLED], 5970 ['self', ENROL_INSTANCE_ENABLED] 5971 ], 5972 'enrolled' => [ 5973 // User 1. 5974 ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], 5975 // User 2. 5976 ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] 5977 ], 5978 'expected' => [ 5979 [ 5980 'start' => $futurecoursestart, 5981 'startoffset' => 0 5982 ], 5983 [ 5984 'start' => $futurecoursestart, 5985 'startoffset' => 0 5986 ] 5987 ] 5988 ], 5989 'future course start fixed 2 users enrolled mixed' => [ 5990 'relativedatemode' => false, 5991 'coursestart' => $futurecoursestart, 5992 'usercount' => 2, 5993 'enrolmentmethods' => [ 5994 ['manual', ENROL_INSTANCE_ENABLED], 5995 ['self', ENROL_INSTANCE_ENABLED] 5996 ], 5997 'enrolled' => [ 5998 // User 1. 5999 ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], 6000 // User 2. 6001 ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] 6002 ], 6003 'expected' => [ 6004 [ 6005 'start' => $futurecoursestart, 6006 'startoffset' => 0 6007 ], 6008 [ 6009 'start' => $futurecoursestart, 6010 'startoffset' => 0 6011 ] 6012 ] 6013 ], 6014 'future course start fixed 2 users enrolled 2 methods' => [ 6015 'relativedatemode' => false, 6016 'coursestart' => $futurecoursestart, 6017 'usercount' => 2, 6018 'enrolmentmethods' => [ 6019 ['manual', ENROL_INSTANCE_ENABLED], 6020 ['self', ENROL_INSTANCE_ENABLED] 6021 ], 6022 'enrolled' => [ 6023 // User 1. 6024 [ 6025 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], 6026 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6027 ], 6028 // User 2. 6029 [ 6030 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], 6031 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6032 ] 6033 ], 6034 'expected' => [ 6035 [ 6036 'start' => $futurecoursestart, 6037 'startoffset' => 0 6038 ], 6039 [ 6040 'start' => $futurecoursestart, 6041 'startoffset' => 0 6042 ] 6043 ] 6044 ], 6045 'future course start fixed 2 users enrolled 2 methods 1 disabled' => [ 6046 'relativedatemode' => false, 6047 'coursestart' => $futurecoursestart, 6048 'usercount' => 2, 6049 'enrolmentmethods' => [ 6050 ['manual', ENROL_INSTANCE_DISABLED], 6051 ['self', ENROL_INSTANCE_ENABLED] 6052 ], 6053 'enrolled' => [ 6054 // User 1. 6055 [ 6056 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], 6057 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6058 ], 6059 // User 2. 6060 [ 6061 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], 6062 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6063 ] 6064 ], 6065 'expected' => [ 6066 [ 6067 'start' => $futurecoursestart, 6068 'startoffset' => 0 6069 ], 6070 [ 6071 'start' => $futurecoursestart, 6072 'startoffset' => 0 6073 ] 6074 ] 6075 ], 6076 'future course start fixed 2 users enrolled 2 methods 2 disabled' => [ 6077 'relativedatemode' => false, 6078 'coursestart' => $futurecoursestart, 6079 'usercount' => 2, 6080 'enrolmentmethods' => [ 6081 ['manual', ENROL_INSTANCE_DISABLED], 6082 ['self', ENROL_INSTANCE_DISABLED] 6083 ], 6084 'enrolled' => [ 6085 // User 1. 6086 [ 6087 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], 6088 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6089 ], 6090 // User 2. 6091 [ 6092 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], 6093 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6094 ] 6095 ], 6096 'expected' => [ 6097 [ 6098 'start' => $futurecoursestart, 6099 'startoffset' => 0 6100 ], 6101 [ 6102 'start' => $futurecoursestart, 6103 'startoffset' => 0 6104 ] 6105 ] 6106 ], 6107 'future course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ 6108 'relativedatemode' => false, 6109 'coursestart' => $futurecoursestart, 6110 'usercount' => 2, 6111 'enrolmentmethods' => [ 6112 ['manual', ENROL_INSTANCE_ENABLED], 6113 ['self', ENROL_INSTANCE_ENABLED] 6114 ], 6115 'enrolled' => [ 6116 // User 1. 6117 [ 6118 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], 6119 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6120 ], 6121 // User 2. 6122 [ 6123 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], 6124 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6125 ] 6126 ], 6127 'expected' => [ 6128 [ 6129 'start' => $futurecoursestart, 6130 'startoffset' => 0 6131 ], 6132 [ 6133 'start' => $futurecoursestart, 6134 'startoffset' => 0 6135 ] 6136 ] 6137 ], 6138 'future course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ 6139 'relativedatemode' => false, 6140 'coursestart' => $futurecoursestart, 6141 'usercount' => 2, 6142 'enrolmentmethods' => [ 6143 ['manual', ENROL_INSTANCE_ENABLED], 6144 ['self', ENROL_INSTANCE_ENABLED] 6145 ], 6146 'enrolled' => [ 6147 // User 1. 6148 [ 6149 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], 6150 'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED] 6151 ], 6152 // User 2. 6153 [ 6154 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], 6155 'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED] 6156 ] 6157 ], 6158 'expected' => [ 6159 [ 6160 'start' => $futurecoursestart, 6161 'startoffset' => 0 6162 ], 6163 [ 6164 'start' => $futurecoursestart, 6165 'startoffset' => 0 6166 ] 6167 ] 6168 ], 6169 'future course start relative no users enrolled' => [ 6170 'relativedatemode' => true, 6171 'coursestart' => $futurecoursestart, 6172 'usercount' => 2, 6173 'enrolmentmethods' => [ 6174 ['manual', ENROL_INSTANCE_ENABLED], 6175 ['self', ENROL_INSTANCE_ENABLED] 6176 ], 6177 'enrolled' => [[], []], 6178 'expected' => [ 6179 [ 6180 'start' => $futurecoursestart, 6181 'startoffset' => 0 6182 ], 6183 [ 6184 'start' => $futurecoursestart, 6185 'startoffset' => 0 6186 ] 6187 ] 6188 ], 6189 'future course start relative 1 users enrolled future' => [ 6190 'relativedatemode' => true, 6191 'coursestart' => $futurecoursestart, 6192 'usercount' => 2, 6193 'enrolmentmethods' => [ 6194 ['manual', ENROL_INSTANCE_ENABLED], 6195 ['self', ENROL_INSTANCE_ENABLED] 6196 ], 6197 'enrolled' => [ 6198 // User 1. 6199 ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], 6200 // User 2. 6201 [] 6202 ], 6203 'expected' => [ 6204 [ 6205 'start' => $futurecoursestart + 10, 6206 'startoffset' => 10 6207 ], 6208 [ 6209 'start' => $futurecoursestart, 6210 'startoffset' => 0 6211 ] 6212 ] 6213 ], 6214 'future course start relative 1 users enrolled past' => [ 6215 'relativedatemode' => true, 6216 'coursestart' => $futurecoursestart, 6217 'usercount' => 2, 6218 'enrolmentmethods' => [ 6219 ['manual', ENROL_INSTANCE_ENABLED], 6220 ['self', ENROL_INSTANCE_ENABLED] 6221 ], 6222 'enrolled' => [ 6223 // User 1. 6224 ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], 6225 // User 2. 6226 [] 6227 ], 6228 'expected' => [ 6229 [ 6230 'start' => $futurecoursestart, 6231 'startoffset' => 0 6232 ], 6233 [ 6234 'start' => $futurecoursestart, 6235 'startoffset' => 0 6236 ] 6237 ] 6238 ], 6239 'future course start relative 2 users enrolled future' => [ 6240 'relativedatemode' => true, 6241 'coursestart' => $futurecoursestart, 6242 'usercount' => 2, 6243 'enrolmentmethods' => [ 6244 ['manual', ENROL_INSTANCE_ENABLED], 6245 ['self', ENROL_INSTANCE_ENABLED] 6246 ], 6247 'enrolled' => [ 6248 // User 1. 6249 ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], 6250 // User 2. 6251 ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]] 6252 ], 6253 'expected' => [ 6254 [ 6255 'start' => $futurecoursestart + 10, 6256 'startoffset' => 10 6257 ], 6258 [ 6259 'start' => $futurecoursestart + 20, 6260 'startoffset' => 20 6261 ] 6262 ] 6263 ], 6264 'future course start relative 2 users enrolled past' => [ 6265 'relativedatemode' => true, 6266 'coursestart' => $futurecoursestart, 6267 'usercount' => 2, 6268 'enrolmentmethods' => [ 6269 ['manual', ENROL_INSTANCE_ENABLED], 6270 ['self', ENROL_INSTANCE_ENABLED] 6271 ], 6272 'enrolled' => [ 6273 // User 1. 6274 ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], 6275 // User 2. 6276 ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] 6277 ], 6278 'expected' => [ 6279 [ 6280 'start' => $futurecoursestart, 6281 'startoffset' => 0 6282 ], 6283 [ 6284 'start' => $futurecoursestart, 6285 'startoffset' => 0 6286 ] 6287 ] 6288 ], 6289 'future course start relative 2 users enrolled mixed' => [ 6290 'relativedatemode' => true, 6291 'coursestart' => $futurecoursestart, 6292 'usercount' => 2, 6293 'enrolmentmethods' => [ 6294 ['manual', ENROL_INSTANCE_ENABLED], 6295 ['self', ENROL_INSTANCE_ENABLED] 6296 ], 6297 'enrolled' => [ 6298 // User 1. 6299 ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], 6300 // User 2. 6301 ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] 6302 ], 6303 'expected' => [ 6304 [ 6305 'start' => $futurecoursestart + 10, 6306 'startoffset' => 10 6307 ], 6308 [ 6309 'start' => $futurecoursestart, 6310 'startoffset' => 0 6311 ] 6312 ] 6313 ], 6314 'future course start relative 2 users enrolled 2 methods' => [ 6315 'relativedatemode' => true, 6316 'coursestart' => $futurecoursestart, 6317 'usercount' => 2, 6318 'enrolmentmethods' => [ 6319 ['manual', ENROL_INSTANCE_ENABLED], 6320 ['self', ENROL_INSTANCE_ENABLED] 6321 ], 6322 'enrolled' => [ 6323 // User 1. 6324 [ 6325 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], 6326 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6327 ], 6328 // User 2. 6329 [ 6330 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], 6331 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6332 ] 6333 ], 6334 'expected' => [ 6335 [ 6336 'start' => $futurecoursestart + 10, 6337 'startoffset' => 10 6338 ], 6339 [ 6340 'start' => $futurecoursestart + 10, 6341 'startoffset' => 10 6342 ] 6343 ] 6344 ], 6345 'future course start relative 2 users enrolled 2 methods 1 disabled' => [ 6346 'relativedatemode' => true, 6347 'coursestart' => $futurecoursestart, 6348 'usercount' => 2, 6349 'enrolmentmethods' => [ 6350 ['manual', ENROL_INSTANCE_DISABLED], 6351 ['self', ENROL_INSTANCE_ENABLED] 6352 ], 6353 'enrolled' => [ 6354 // User 1. 6355 [ 6356 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], 6357 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6358 ], 6359 // User 2. 6360 [ 6361 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], 6362 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6363 ] 6364 ], 6365 'expected' => [ 6366 [ 6367 'start' => $futurecoursestart + 20, 6368 'startoffset' => 20 6369 ], 6370 [ 6371 'start' => $futurecoursestart + 10, 6372 'startoffset' => 10 6373 ] 6374 ] 6375 ], 6376 'future course start relative 2 users enrolled 2 methods 2 disabled' => [ 6377 'relativedatemode' => true, 6378 'coursestart' => $futurecoursestart, 6379 'usercount' => 2, 6380 'enrolmentmethods' => [ 6381 ['manual', ENROL_INSTANCE_DISABLED], 6382 ['self', ENROL_INSTANCE_DISABLED] 6383 ], 6384 'enrolled' => [ 6385 // User 1. 6386 [ 6387 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], 6388 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6389 ], 6390 // User 2. 6391 [ 6392 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], 6393 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6394 ] 6395 ], 6396 'expected' => [ 6397 [ 6398 'start' => $futurecoursestart, 6399 'startoffset' => 0 6400 ], 6401 [ 6402 'start' => $futurecoursestart, 6403 'startoffset' => 0 6404 ] 6405 ] 6406 ], 6407 'future course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ 6408 'relativedatemode' => true, 6409 'coursestart' => $futurecoursestart, 6410 'usercount' => 2, 6411 'enrolmentmethods' => [ 6412 ['manual', ENROL_INSTANCE_ENABLED], 6413 ['self', ENROL_INSTANCE_ENABLED] 6414 ], 6415 'enrolled' => [ 6416 // User 1. 6417 [ 6418 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], 6419 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] 6420 ], 6421 // User 2. 6422 [ 6423 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], 6424 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] 6425 ] 6426 ], 6427 'expected' => [ 6428 [ 6429 'start' => $futurecoursestart + 20, 6430 'startoffset' => 20 6431 ], 6432 [ 6433 'start' => $futurecoursestart + 10, 6434 'startoffset' => 10 6435 ] 6436 ] 6437 ], 6438 'future course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ 6439 'relativedatemode' => true, 6440 'coursestart' => $futurecoursestart, 6441 'usercount' => 2, 6442 'enrolmentmethods' => [ 6443 ['manual', ENROL_INSTANCE_ENABLED], 6444 ['self', ENROL_INSTANCE_ENABLED] 6445 ], 6446 'enrolled' => [ 6447 // User 1. 6448 [ 6449 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], 6450 'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED] 6451 ], 6452 // User 2. 6453 [ 6454 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], 6455 'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED] 6456 ] 6457 ], 6458 'expected' => [ 6459 [ 6460 'start' => $futurecoursestart, 6461 'startoffset' => 0 6462 ], 6463 [ 6464 'start' => $futurecoursestart, 6465 'startoffset' => 0 6466 ] 6467 ] 6468 ], 6469 6470 // Course start date in the past. 6471 'past course start fixed no users enrolled' => [ 6472 'relativedatemode' => false, 6473 'coursestart' => $pastcoursestart, 6474 'usercount' => 2, 6475 'enrolmentmethods' => [ 6476 ['manual', ENROL_INSTANCE_ENABLED], 6477 ['self', ENROL_INSTANCE_ENABLED] 6478 ], 6479 'enrolled' => [[], []], 6480 'expected' => [ 6481 [ 6482 'start' => $pastcoursestart, 6483 'startoffset' => 0 6484 ], 6485 [ 6486 'start' => $pastcoursestart, 6487 'startoffset' => 0 6488 ] 6489 ] 6490 ], 6491 'past course start fixed 1 users enrolled future' => [ 6492 'relativedatemode' => false, 6493 'coursestart' => $pastcoursestart, 6494 'usercount' => 2, 6495 'enrolmentmethods' => [ 6496 ['manual', ENROL_INSTANCE_ENABLED], 6497 ['self', ENROL_INSTANCE_ENABLED] 6498 ], 6499 'enrolled' => [ 6500 // User 1. 6501 ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], 6502 // User 2. 6503 [] 6504 ], 6505 'expected' => [ 6506 [ 6507 'start' => $pastcoursestart, 6508 'startoffset' => 0 6509 ], 6510 [ 6511 'start' => $pastcoursestart, 6512 'startoffset' => 0 6513 ] 6514 ] 6515 ], 6516 'past course start fixed 1 users enrolled past' => [ 6517 'relativedatemode' => false, 6518 'coursestart' => $pastcoursestart, 6519 'usercount' => 2, 6520 'enrolmentmethods' => [ 6521 ['manual', ENROL_INSTANCE_ENABLED], 6522 ['self', ENROL_INSTANCE_ENABLED] 6523 ], 6524 'enrolled' => [ 6525 // User 1. 6526 ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], 6527 // User 2. 6528 [] 6529 ], 6530 'expected' => [ 6531 [ 6532 'start' => $pastcoursestart, 6533 'startoffset' => 0 6534 ], 6535 [ 6536 'start' => $pastcoursestart, 6537 'startoffset' => 0 6538 ] 6539 ] 6540 ], 6541 'past course start fixed 2 users enrolled future' => [ 6542 'relativedatemode' => false, 6543 'coursestart' => $pastcoursestart, 6544 'usercount' => 2, 6545 'enrolmentmethods' => [ 6546 ['manual', ENROL_INSTANCE_ENABLED], 6547 ['self', ENROL_INSTANCE_ENABLED] 6548 ], 6549 'enrolled' => [ 6550 // User 1. 6551 ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], 6552 // User 2. 6553 ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]] 6554 ], 6555 'expected' => [ 6556 [ 6557 'start' => $pastcoursestart, 6558 'startoffset' => 0 6559 ], 6560 [ 6561 'start' => $pastcoursestart, 6562 'startoffset' => 0 6563 ] 6564 ] 6565 ], 6566 'past course start fixed 2 users enrolled past' => [ 6567 'relativedatemode' => false, 6568 'coursestart' => $pastcoursestart, 6569 'usercount' => 2, 6570 'enrolmentmethods' => [ 6571 ['manual', ENROL_INSTANCE_ENABLED], 6572 ['self', ENROL_INSTANCE_ENABLED] 6573 ], 6574 'enrolled' => [ 6575 // User 1. 6576 ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], 6577 // User 2. 6578 ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] 6579 ], 6580 'expected' => [ 6581 [ 6582 'start' => $pastcoursestart, 6583 'startoffset' => 0 6584 ], 6585 [ 6586 'start' => $pastcoursestart, 6587 'startoffset' => 0 6588 ] 6589 ] 6590 ], 6591 'past course start fixed 2 users enrolled mixed' => [ 6592 'relativedatemode' => false, 6593 'coursestart' => $pastcoursestart, 6594 'usercount' => 2, 6595 'enrolmentmethods' => [ 6596 ['manual', ENROL_INSTANCE_ENABLED], 6597 ['self', ENROL_INSTANCE_ENABLED] 6598 ], 6599 'enrolled' => [ 6600 // User 1. 6601 ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], 6602 // User 2. 6603 ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] 6604 ], 6605 'expected' => [ 6606 [ 6607 'start' => $pastcoursestart, 6608 'startoffset' => 0 6609 ], 6610 [ 6611 'start' => $pastcoursestart, 6612 'startoffset' => 0 6613 ] 6614 ] 6615 ], 6616 'past course start fixed 2 users enrolled 2 methods' => [ 6617 'relativedatemode' => false, 6618 'coursestart' => $pastcoursestart, 6619 'usercount' => 2, 6620 'enrolmentmethods' => [ 6621 ['manual', ENROL_INSTANCE_ENABLED], 6622 ['self', ENROL_INSTANCE_ENABLED] 6623 ], 6624 'enrolled' => [ 6625 // User 1. 6626 [ 6627 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], 6628 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6629 ], 6630 // User 2. 6631 [ 6632 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], 6633 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6634 ] 6635 ], 6636 'expected' => [ 6637 [ 6638 'start' => $pastcoursestart, 6639 'startoffset' => 0 6640 ], 6641 [ 6642 'start' => $pastcoursestart, 6643 'startoffset' => 0 6644 ] 6645 ] 6646 ], 6647 'past course start fixed 2 users enrolled 2 methods 1 disabled' => [ 6648 'relativedatemode' => false, 6649 'coursestart' => $pastcoursestart, 6650 'usercount' => 2, 6651 'enrolmentmethods' => [ 6652 ['manual', ENROL_INSTANCE_DISABLED], 6653 ['self', ENROL_INSTANCE_ENABLED] 6654 ], 6655 'enrolled' => [ 6656 // User 1. 6657 [ 6658 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], 6659 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6660 ], 6661 // User 2. 6662 [ 6663 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], 6664 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6665 ] 6666 ], 6667 'expected' => [ 6668 [ 6669 'start' => $pastcoursestart, 6670 'startoffset' => 0 6671 ], 6672 [ 6673 'start' => $pastcoursestart, 6674 'startoffset' => 0 6675 ] 6676 ] 6677 ], 6678 'past course start fixed 2 users enrolled 2 methods 2 disabled' => [ 6679 'relativedatemode' => false, 6680 'coursestart' => $pastcoursestart, 6681 'usercount' => 2, 6682 'enrolmentmethods' => [ 6683 ['manual', ENROL_INSTANCE_DISABLED], 6684 ['self', ENROL_INSTANCE_DISABLED] 6685 ], 6686 'enrolled' => [ 6687 // User 1. 6688 [ 6689 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], 6690 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6691 ], 6692 // User 2. 6693 [ 6694 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], 6695 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6696 ] 6697 ], 6698 'expected' => [ 6699 [ 6700 'start' => $pastcoursestart, 6701 'startoffset' => 0 6702 ], 6703 [ 6704 'start' => $pastcoursestart, 6705 'startoffset' => 0 6706 ] 6707 ] 6708 ], 6709 'past course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ 6710 'relativedatemode' => false, 6711 'coursestart' => $pastcoursestart, 6712 'usercount' => 2, 6713 'enrolmentmethods' => [ 6714 ['manual', ENROL_INSTANCE_ENABLED], 6715 ['self', ENROL_INSTANCE_ENABLED] 6716 ], 6717 'enrolled' => [ 6718 // User 1. 6719 [ 6720 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], 6721 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6722 ], 6723 // User 2. 6724 [ 6725 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], 6726 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6727 ] 6728 ], 6729 'expected' => [ 6730 [ 6731 'start' => $pastcoursestart, 6732 'startoffset' => 0 6733 ], 6734 [ 6735 'start' => $pastcoursestart, 6736 'startoffset' => 0 6737 ] 6738 ] 6739 ], 6740 'past course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ 6741 'relativedatemode' => false, 6742 'coursestart' => $pastcoursestart, 6743 'usercount' => 2, 6744 'enrolmentmethods' => [ 6745 ['manual', ENROL_INSTANCE_ENABLED], 6746 ['self', ENROL_INSTANCE_ENABLED] 6747 ], 6748 'enrolled' => [ 6749 // User 1. 6750 [ 6751 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], 6752 'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED] 6753 ], 6754 // User 2. 6755 [ 6756 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], 6757 'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED] 6758 ] 6759 ], 6760 'expected' => [ 6761 [ 6762 'start' => $pastcoursestart, 6763 'startoffset' => 0 6764 ], 6765 [ 6766 'start' => $pastcoursestart, 6767 'startoffset' => 0 6768 ] 6769 ] 6770 ], 6771 'past course start relative no users enrolled' => [ 6772 'relativedatemode' => true, 6773 'coursestart' => $pastcoursestart, 6774 'usercount' => 2, 6775 'enrolmentmethods' => [ 6776 ['manual', ENROL_INSTANCE_ENABLED], 6777 ['self', ENROL_INSTANCE_ENABLED] 6778 ], 6779 'enrolled' => [[], []], 6780 'expected' => [ 6781 [ 6782 'start' => $pastcoursestart, 6783 'startoffset' => 0 6784 ], 6785 [ 6786 'start' => $pastcoursestart, 6787 'startoffset' => 0 6788 ] 6789 ] 6790 ], 6791 'past course start relative 1 users enrolled future' => [ 6792 'relativedatemode' => true, 6793 'coursestart' => $pastcoursestart, 6794 'usercount' => 2, 6795 'enrolmentmethods' => [ 6796 ['manual', ENROL_INSTANCE_ENABLED], 6797 ['self', ENROL_INSTANCE_ENABLED] 6798 ], 6799 'enrolled' => [ 6800 // User 1. 6801 ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], 6802 // User 2. 6803 [] 6804 ], 6805 'expected' => [ 6806 [ 6807 'start' => $pastcoursestart + 10, 6808 'startoffset' => 10 6809 ], 6810 [ 6811 'start' => $pastcoursestart, 6812 'startoffset' => 0 6813 ] 6814 ] 6815 ], 6816 'past course start relative 1 users enrolled past' => [ 6817 'relativedatemode' => true, 6818 'coursestart' => $pastcoursestart, 6819 'usercount' => 2, 6820 'enrolmentmethods' => [ 6821 ['manual', ENROL_INSTANCE_ENABLED], 6822 ['self', ENROL_INSTANCE_ENABLED] 6823 ], 6824 'enrolled' => [ 6825 // User 1. 6826 ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], 6827 // User 2. 6828 [] 6829 ], 6830 'expected' => [ 6831 [ 6832 'start' => $pastcoursestart, 6833 'startoffset' => 0 6834 ], 6835 [ 6836 'start' => $pastcoursestart, 6837 'startoffset' => 0 6838 ] 6839 ] 6840 ], 6841 'past course start relative 2 users enrolled future' => [ 6842 'relativedatemode' => true, 6843 'coursestart' => $pastcoursestart, 6844 'usercount' => 2, 6845 'enrolmentmethods' => [ 6846 ['manual', ENROL_INSTANCE_ENABLED], 6847 ['self', ENROL_INSTANCE_ENABLED] 6848 ], 6849 'enrolled' => [ 6850 // User 1. 6851 ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], 6852 // User 2. 6853 ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]] 6854 ], 6855 'expected' => [ 6856 [ 6857 'start' => $pastcoursestart + 10, 6858 'startoffset' => 10 6859 ], 6860 [ 6861 'start' => $pastcoursestart + 20, 6862 'startoffset' => 20 6863 ] 6864 ] 6865 ], 6866 'past course start relative 2 users enrolled past' => [ 6867 'relativedatemode' => true, 6868 'coursestart' => $pastcoursestart, 6869 'usercount' => 2, 6870 'enrolmentmethods' => [ 6871 ['manual', ENROL_INSTANCE_ENABLED], 6872 ['self', ENROL_INSTANCE_ENABLED] 6873 ], 6874 'enrolled' => [ 6875 // User 1. 6876 ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], 6877 // User 2. 6878 ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] 6879 ], 6880 'expected' => [ 6881 [ 6882 'start' => $pastcoursestart, 6883 'startoffset' => 0 6884 ], 6885 [ 6886 'start' => $pastcoursestart, 6887 'startoffset' => 0 6888 ] 6889 ] 6890 ], 6891 'past course start relative 2 users enrolled mixed' => [ 6892 'relativedatemode' => true, 6893 'coursestart' => $pastcoursestart, 6894 'usercount' => 2, 6895 'enrolmentmethods' => [ 6896 ['manual', ENROL_INSTANCE_ENABLED], 6897 ['self', ENROL_INSTANCE_ENABLED] 6898 ], 6899 'enrolled' => [ 6900 // User 1. 6901 ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], 6902 // User 2. 6903 ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] 6904 ], 6905 'expected' => [ 6906 [ 6907 'start' => $pastcoursestart + 10, 6908 'startoffset' => 10 6909 ], 6910 [ 6911 'start' => $pastcoursestart, 6912 'startoffset' => 0 6913 ] 6914 ] 6915 ], 6916 'past course start relative 2 users enrolled 2 methods' => [ 6917 'relativedatemode' => true, 6918 'coursestart' => $pastcoursestart, 6919 'usercount' => 2, 6920 'enrolmentmethods' => [ 6921 ['manual', ENROL_INSTANCE_ENABLED], 6922 ['self', ENROL_INSTANCE_ENABLED] 6923 ], 6924 'enrolled' => [ 6925 // User 1. 6926 [ 6927 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], 6928 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6929 ], 6930 // User 2. 6931 [ 6932 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], 6933 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6934 ] 6935 ], 6936 'expected' => [ 6937 [ 6938 'start' => $pastcoursestart + 10, 6939 'startoffset' => 10 6940 ], 6941 [ 6942 'start' => $pastcoursestart + 10, 6943 'startoffset' => 10 6944 ] 6945 ] 6946 ], 6947 'past course start relative 2 users enrolled 2 methods 1 disabled' => [ 6948 'relativedatemode' => true, 6949 'coursestart' => $pastcoursestart, 6950 'usercount' => 2, 6951 'enrolmentmethods' => [ 6952 ['manual', ENROL_INSTANCE_DISABLED], 6953 ['self', ENROL_INSTANCE_ENABLED] 6954 ], 6955 'enrolled' => [ 6956 // User 1. 6957 [ 6958 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], 6959 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6960 ], 6961 // User 2. 6962 [ 6963 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], 6964 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6965 ] 6966 ], 6967 'expected' => [ 6968 [ 6969 'start' => $pastcoursestart + 20, 6970 'startoffset' => 20 6971 ], 6972 [ 6973 'start' => $pastcoursestart + 10, 6974 'startoffset' => 10 6975 ] 6976 ] 6977 ], 6978 'past course start relative 2 users enrolled 2 methods 2 disabled' => [ 6979 'relativedatemode' => true, 6980 'coursestart' => $pastcoursestart, 6981 'usercount' => 2, 6982 'enrolmentmethods' => [ 6983 ['manual', ENROL_INSTANCE_DISABLED], 6984 ['self', ENROL_INSTANCE_DISABLED] 6985 ], 6986 'enrolled' => [ 6987 // User 1. 6988 [ 6989 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], 6990 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 6991 ], 6992 // User 2. 6993 [ 6994 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], 6995 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 6996 ] 6997 ], 6998 'expected' => [ 6999 [ 7000 'start' => $pastcoursestart, 7001 'startoffset' => 0 7002 ], 7003 [ 7004 'start' => $pastcoursestart, 7005 'startoffset' => 0 7006 ] 7007 ] 7008 ], 7009 'past course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ 7010 'relativedatemode' => true, 7011 'coursestart' => $pastcoursestart, 7012 'usercount' => 2, 7013 'enrolmentmethods' => [ 7014 ['manual', ENROL_INSTANCE_ENABLED], 7015 ['self', ENROL_INSTANCE_ENABLED] 7016 ], 7017 'enrolled' => [ 7018 // User 1. 7019 [ 7020 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], 7021 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] 7022 ], 7023 // User 2. 7024 [ 7025 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], 7026 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] 7027 ] 7028 ], 7029 'expected' => [ 7030 [ 7031 'start' => $pastcoursestart + 20, 7032 'startoffset' => 20 7033 ], 7034 [ 7035 'start' => $pastcoursestart + 10, 7036 'startoffset' => 10 7037 ] 7038 ] 7039 ], 7040 'past course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ 7041 'relativedatemode' => true, 7042 'coursestart' => $pastcoursestart, 7043 'usercount' => 2, 7044 'enrolmentmethods' => [ 7045 ['manual', ENROL_INSTANCE_ENABLED], 7046 ['self', ENROL_INSTANCE_ENABLED] 7047 ], 7048 'enrolled' => [ 7049 // User 1. 7050 [ 7051 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], 7052 'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED] 7053 ], 7054 // User 2. 7055 [ 7056 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], 7057 'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED] 7058 ] 7059 ], 7060 'expected' => [ 7061 [ 7062 'start' => $pastcoursestart, 7063 'startoffset' => 0 7064 ], 7065 [ 7066 'start' => $pastcoursestart, 7067 'startoffset' => 0 7068 ] 7069 ] 7070 ] 7071 ]; 7072 } 7073 7074 /** 7075 * Test the course_get_course_dates_for_user_ids function. 7076 * 7077 * @dataProvider get_course_get_course_dates_for_user_ids_test_cases() 7078 * @param bool $relativedatemode Set the course to relative dates mode 7079 * @param int $coursestart Course start date 7080 * @param int $usercount Number of users to create 7081 * @param array $enrolmentmethods Enrolment methods to set for the course 7082 * @param array $enrolled Enrolment config for to set for the users 7083 * @param array $expected Expected output 7084 */ 7085 public function test_course_get_course_dates_for_user_ids( 7086 $relativedatemode, 7087 $coursestart, 7088 $usercount, 7089 $enrolmentmethods, 7090 $enrolled, 7091 $expected 7092 ) { 7093 global $DB; 7094 $this->resetAfterTest(); 7095 7096 $generator = $this->getDataGenerator(); 7097 $course = $generator->create_course(['startdate' => $coursestart]); 7098 $course->relativedatesmode = $relativedatemode; 7099 $users = []; 7100 7101 for ($i = 0; $i < $usercount; $i++) { 7102 $users[] = $generator->create_user(); 7103 } 7104 7105 foreach ($enrolmentmethods as [$type, $status]) { 7106 $record = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => $type]); 7107 $plugin = enrol_get_plugin($type); 7108 if ($record->status != $status) { 7109 $plugin->update_status($record, $status); 7110 } 7111 } 7112 7113 foreach ($enrolled as $index => $enrolconfig) { 7114 $user = $users[$index]; 7115 foreach ($enrolconfig as $type => [$starttime, $status]) { 7116 $generator->enrol_user($user->id, $course->id, 'student', $type, $starttime, 0, $status); 7117 } 7118 } 7119 7120 $userids = array_map(function($user) { 7121 return $user->id; 7122 }, $users); 7123 $actual = course_get_course_dates_for_user_ids($course, $userids); 7124 7125 foreach ($expected as $index => $exp) { 7126 $userid = $userids[$index]; 7127 $act = $actual[$userid]; 7128 7129 $this->assertEquals($exp['start'], $act['start']); 7130 $this->assertEquals($exp['startoffset'], $act['startoffset']); 7131 } 7132 } 7133 7134 /** 7135 * Test that calling course_get_course_dates_for_user_ids multiple times in the 7136 * same request fill fetch the correct data for the user. 7137 */ 7138 public function test_course_get_course_dates_for_user_ids_multiple_calls() { 7139 $this->resetAfterTest(); 7140 7141 $generator = $this->getDataGenerator(); 7142 $now = time(); 7143 $coursestart = $now - 1000; 7144 $course = $generator->create_course(['startdate' => $coursestart]); 7145 $course->relativedatesmode = true; 7146 $user1 = $generator->create_user(); 7147 $user2 = $generator->create_user(); 7148 $user1start = $coursestart + 100; 7149 $user2start = $coursestart + 200; 7150 7151 $generator->enrol_user($user1->id, $course->id, 'student', 'manual', $user1start); 7152 $generator->enrol_user($user2->id, $course->id, 'student', 'manual', $user2start); 7153 7154 $result = course_get_course_dates_for_user_ids($course, [$user1->id]); 7155 $this->assertEquals($user1start, $result[$user1->id]['start']); 7156 7157 $result = course_get_course_dates_for_user_ids($course, [$user1->id, $user2->id]); 7158 $this->assertEquals($user1start, $result[$user1->id]['start']); 7159 $this->assertEquals($user2start, $result[$user2->id]['start']); 7160 7161 $result = course_get_course_dates_for_user_ids($course, [$user2->id]); 7162 $this->assertEquals($user2start, $result[$user2->id]['start']); 7163 } 7164 7165 /** 7166 * Data provider for test_course_modules_pending_deletion. 7167 * 7168 * @return array An array of arrays contain test data 7169 */ 7170 public function provider_course_modules_pending_deletion() { 7171 return [ 7172 'Non-gradable activity, check all' => [['forum'], 0, false, true], 7173 'Gradable activity, check all' => [['assign'], 0, false, true], 7174 'Non-gradable activity, check gradables' => [['forum'], 0, true, false], 7175 'Gradable activity, check gradables' => [['assign'], 0, true, true], 7176 'Non-gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 1, false, true], 7177 'Non-gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 1, true, false], 7178 'Gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 2, false, true], 7179 'Gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 2, true, true], 7180 ]; 7181 } 7182 7183 /** 7184 * Tests the function course_modules_pending_deletion. 7185 * 7186 * @param string[] $modules A complete list aff all available modules before deletion 7187 * @param int $indextodelete The index of the module in the $modules array that we want to test with 7188 * @param bool $gradable The value to pass to the gradable argument of the course_modules_pending_deletion function 7189 * @param bool $expected The expected result 7190 * @dataProvider provider_course_modules_pending_deletion 7191 */ 7192 public function test_course_modules_pending_deletion(array $modules, int $indextodelete, bool $gradable, bool $expected) { 7193 $this->resetAfterTest(); 7194 7195 // Ensure recyclebin is enabled. 7196 set_config('coursebinenable', true, 'tool_recyclebin'); 7197 7198 // Create course and modules. 7199 $generator = $this->getDataGenerator(); 7200 $course = $generator->create_course(); 7201 7202 $moduleinstances = []; 7203 foreach ($modules as $module) { 7204 $moduleinstances[] = $generator->create_module($module, array('course' => $course->id)); 7205 } 7206 7207 course_delete_module($moduleinstances[$indextodelete]->cmid, true); // Try to delete the instance asynchronously. 7208 $this->assertEquals($expected, course_modules_pending_deletion($course->id, $gradable)); 7209 } 7210 7211 /** 7212 * Tests for the course_request::can_request 7213 */ 7214 public function test_can_request_course() { 7215 global $CFG, $DB; 7216 $this->resetAfterTest(); 7217 7218 $user = $this->getDataGenerator()->create_user(); 7219 $cat1 = $CFG->defaultrequestcategory; 7220 $cat2 = $this->getDataGenerator()->create_category()->id; 7221 $cat3 = $this->getDataGenerator()->create_category()->id; 7222 $context1 = context_coursecat::instance($cat1); 7223 $context2 = context_coursecat::instance($cat2); 7224 $context3 = context_coursecat::instance($cat3); 7225 $this->setUser($user); 7226 7227 // By default users don't have capability to request courses. 7228 $this->assertFalse(course_request::can_request(context_system::instance())); 7229 $this->assertFalse(course_request::can_request($context1)); 7230 $this->assertFalse(course_request::can_request($context2)); 7231 $this->assertFalse(course_request::can_request($context3)); 7232 7233 // Allow for the 'user' role the capability to request courses. 7234 $userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']); 7235 assign_capability('moodle/course:request', CAP_ALLOW, $userroleid, 7236 context_system::instance()->id); 7237 accesslib_clear_all_caches_for_unit_testing(); 7238 7239 // Lock category selection. 7240 $CFG->lockrequestcategory = 1; 7241 7242 // Now user can only request course in the default category or in system context. 7243 $this->assertTrue(course_request::can_request(context_system::instance())); 7244 $this->assertTrue(course_request::can_request($context1)); 7245 $this->assertFalse(course_request::can_request($context2)); 7246 $this->assertFalse(course_request::can_request($context3)); 7247 7248 // Enable category selection. User can request course anywhere. 7249 $CFG->lockrequestcategory = 0; 7250 $this->assertTrue(course_request::can_request(context_system::instance())); 7251 $this->assertTrue(course_request::can_request($context1)); 7252 $this->assertTrue(course_request::can_request($context2)); 7253 $this->assertTrue(course_request::can_request($context3)); 7254 7255 // Remove cap from cat2. 7256 $roleid = create_role('Test role', 'testrole', 'Test role description'); 7257 assign_capability('moodle/course:request', CAP_PROHIBIT, $roleid, 7258 $context2->id, true); 7259 role_assign($roleid, $user->id, $context2->id); 7260 accesslib_clear_all_caches_for_unit_testing(); 7261 7262 $this->assertTrue(course_request::can_request(context_system::instance())); 7263 $this->assertTrue(course_request::can_request($context1)); 7264 $this->assertFalse(course_request::can_request($context2)); 7265 $this->assertTrue(course_request::can_request($context3)); 7266 7267 // Disable course request functionality. 7268 $CFG->enablecourserequests = false; 7269 $this->assertFalse(course_request::can_request(context_system::instance())); 7270 $this->assertFalse(course_request::can_request($context1)); 7271 $this->assertFalse(course_request::can_request($context2)); 7272 $this->assertFalse(course_request::can_request($context3)); 7273 } 7274 7275 /** 7276 * Tests for the course_request::can_approve 7277 */ 7278 public function test_can_approve_course_request() { 7279 global $CFG; 7280 $this->resetAfterTest(); 7281 7282 $requestor = $this->getDataGenerator()->create_user(); 7283 $user = $this->getDataGenerator()->create_user(); 7284 $cat1 = $CFG->defaultrequestcategory; 7285 $cat2 = $this->getDataGenerator()->create_category()->id; 7286 $cat3 = $this->getDataGenerator()->create_category()->id; 7287 7288 // Enable course requests. Default 'user' role has capability to request courses. 7289 $CFG->enablecourserequests = true; 7290 $CFG->lockrequestcategory = 0; 7291 $this->setUser($requestor); 7292 $requestdata = ['summary_editor' => ['text' => '', 'format' => 0], 'name' => 'Req', 'reason' => 'test']; 7293 $request1 = course_request::create((object)($requestdata)); 7294 $request2 = course_request::create((object)($requestdata + ['category' => $cat2])); 7295 $request3 = course_request::create((object)($requestdata + ['category' => $cat3])); 7296 7297 $this->setUser($user); 7298 // Add capability to approve courses. 7299 $roleid = create_role('Test role', 'testrole', 'Test role description'); 7300 assign_capability('moodle/site:approvecourse', CAP_ALLOW, $roleid, 7301 context_system::instance()->id, true); 7302 role_assign($roleid, $user->id, context_coursecat::instance($cat2)->id); 7303 accesslib_clear_all_caches_for_unit_testing(); 7304 7305 $this->assertFalse($request1->can_approve()); 7306 $this->assertTrue($request2->can_approve()); 7307 $this->assertFalse($request3->can_approve()); 7308 7309 // Delete category where course was requested. Now only site-wide manager can approve it. 7310 core_course_category::get($cat2, MUST_EXIST, true)->delete_full(false); 7311 $this->assertFalse($request2->can_approve()); 7312 7313 $this->setAdminUser(); 7314 $this->assertTrue($request2->can_approve()); 7315 } 7316 7317 /** 7318 * Test the course allowed module method. 7319 */ 7320 public function test_course_allowed_module() { 7321 $this->resetAfterTest(); 7322 global $DB; 7323 7324 $course = $this->getDataGenerator()->create_course(); 7325 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 7326 $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager'); 7327 7328 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 7329 assign_capability('mod/assign:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id)); 7330 7331 // Global user (teacher) has no permissions in this course. 7332 $this->setUser($teacher); 7333 $this->assertFalse(course_allowed_module($course, 'assign')); 7334 7335 // Manager has permissions. 7336 $this->assertTrue(course_allowed_module($course, 'assign', $manager)); 7337 } 7338 7339 /** 7340 * Test the {@link average_number_of_participants()} function. 7341 */ 7342 public function test_average_number_of_participants() { 7343 global $DB; 7344 $this->resetAfterTest(true); 7345 7346 $generator = $this->getDataGenerator(); 7347 $now = time(); 7348 7349 // If there are no courses, expect zero number of participants per course. 7350 $this->assertEquals(0, average_number_of_participants()); 7351 7352 $c1 = $generator->create_course(); 7353 $c2 = $generator->create_course(); 7354 7355 // If there are no users, expect zero number of participants per course. 7356 $this->assertEquals(0, average_number_of_participants()); 7357 7358 $t1 = $generator->create_user(['lastlogin' => $now]); 7359 $s1 = $generator->create_user(['lastlogin' => $now]); 7360 $s2 = $generator->create_user(['lastlogin' => $now - WEEKSECS]); 7361 $s3 = $generator->create_user(['lastlogin' => $now - WEEKSECS]); 7362 $s4 = $generator->create_user(['lastlogin' => $now - YEARSECS]); 7363 7364 // We have courses, we have users, but no enrolments yet. 7365 $this->assertEquals(0, average_number_of_participants()); 7366 7367 // Front page enrolments are ignored. 7368 $generator->enrol_user($t1->id, SITEID, 'teacher'); 7369 $this->assertEquals(0, average_number_of_participants()); 7370 7371 // The teacher enrolled into one of the two courses. 7372 $generator->enrol_user($t1->id, $c1->id, 'editingteacher'); 7373 $this->assertEquals(0.5, average_number_of_participants()); 7374 7375 // The teacher enrolled into both courses. 7376 $generator->enrol_user($t1->id, $c2->id, 'editingteacher'); 7377 $this->assertEquals(1, average_number_of_participants()); 7378 7379 // Student 1 enrolled in the Course 1 only. 7380 $generator->enrol_user($s1->id, $c1->id, 'student'); 7381 $this->assertEquals(1.5, average_number_of_participants()); 7382 7383 // Student 2 enrolled in both courses, but the enrolment in the Course 2 not active yet (enrolment starts in the future). 7384 $generator->enrol_user($s2->id, $c1->id, 'student'); 7385 $generator->enrol_user($s2->id, $c2->id, 'student', 'manual', $now + WEEKSECS); 7386 $this->assertEquals(2.5, average_number_of_participants()); 7387 $this->assertEquals(2, average_number_of_participants(true)); 7388 7389 // Student 3 enrolled in the Course 1, but the enrolment already expired. 7390 $generator->enrol_user($s3->id, $c1->id, 'student', 'manual', 0, $now - YEARSECS); 7391 $this->assertEquals(3, average_number_of_participants()); 7392 $this->assertEquals(2, average_number_of_participants(true)); 7393 7394 // Student 4 enrolled in both courses, but the enrolment has been suspended. 7395 $generator->enrol_user($s4->id, $c1->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED); 7396 $generator->enrol_user($s4->id, $c2->id, 'student', 'manual', $now - DAYSECS, $now + YEARSECS, ENROL_USER_SUSPENDED); 7397 $this->assertEquals(4, average_number_of_participants()); 7398 $this->assertEquals(2, average_number_of_participants(true)); 7399 7400 // Consider only t1 and s1 who logged in recently. 7401 $this->assertEquals(1.5, average_number_of_participants(false, $now - DAYSECS)); 7402 7403 // Consider only t1, s1, s2 and s3 who logged in in recent weeks. 7404 $this->assertEquals(3, average_number_of_participants(false, $now - 4 * WEEKSECS)); 7405 7406 // Hidden courses are excluded from stats. 7407 $DB->set_field('course', 'visible', 0, ['id' => $c1->id]); 7408 $this->assertEquals(3, average_number_of_participants()); 7409 $this->assertEquals(1, average_number_of_participants(true)); 7410 } 7411 7412 /** 7413 * Test the set_downloadcontent() function. 7414 */ 7415 public function test_set_downloadcontent() { 7416 $this->resetAfterTest(); 7417 7418 $generator = $this->getDataGenerator(); 7419 $course = $generator->create_course(); 7420 $page = $generator->create_module('page', ['course' => $course]); 7421 7422 // Test the module 'downloadcontent' field is set to enabled. 7423 set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED); 7424 $modinfo = get_fast_modinfo($course)->get_cm($page->cmid); 7425 $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $modinfo->downloadcontent); 7426 7427 // Now let's test the 'downloadcontent' value is updated to disabled. 7428 set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED); 7429 $modinfo = get_fast_modinfo($course)->get_cm($page->cmid); 7430 $this->assertEquals(DOWNLOAD_COURSE_CONTENT_DISABLED, $modinfo->downloadcontent); 7431 7432 // Nothing to update, the download course content value is the same, it should return false. 7433 $this->assertFalse(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED)); 7434 7435 // The download course content value has changed, it should return true in this case. 7436 $this->assertTrue(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED)); 7437 } 7438 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body