Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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