Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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