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