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