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