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