Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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