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