Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  
      17  /**
      18   * Course related unit tests
      19   *
      20   * @package    core
      21   * @category   phpunit
      22   * @copyright  2012 Petr Skoda {@link http://skodak.org}
      23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      24   */
      25  
      26  defined('MOODLE_INTERNAL') || die();
      27  
      28  global $CFG;
      29  require_once($CFG->dirroot . '/course/lib.php');
      30  require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
      31  require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
      32  
      33  class core_course_courselib_testcase extends advanced_testcase {
      34  
      35      /**
      36       * Set forum specific test values for calling create_module().
      37       *
      38       * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
      39       */
      40      private function forum_create_set_values(&$moduleinfo) {
      41          // Completion specific to forum - optional.
      42          $moduleinfo->completionposts = 3;
      43          $moduleinfo->completiondiscussions = 1;
      44          $moduleinfo->completionreplies = 2;
      45  
      46          // Specific values to the Forum module.
      47          $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
      48          $moduleinfo->type = 'single';
      49          $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
      50          $moduleinfo->maxbytes = 10240;
      51          $moduleinfo->maxattachments = 2;
      52  
      53          // Post threshold for blocking - specific to forum.
      54          $moduleinfo->blockperiod = 60*60*24;
      55          $moduleinfo->blockafter = 10;
      56          $moduleinfo->warnafter = 5;
      57  
      58          // Grading of whole forum settings.
      59          $moduleinfo->grade_forum = 0;
      60      }
      61  
      62      /**
      63       * Execute test asserts on the saved DB data by create_module($forum).
      64       *
      65       * @param object $moduleinfo - the specific forum values that were used to create a forum.
      66       * @param object $dbmodinstance - the DB values of the created forum.
      67       */
      68      private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
      69          // Compare values specific to forums.
      70          $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
      71          $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
      72          $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
      73          $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
      74          $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
      75          $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
      76          $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
      77          $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
      78          $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
      79          $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
      80          $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
      81          $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
      82          $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
      83          $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
      84          $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
      85          $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
      86          $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
      87      }
      88  
      89      /**
      90       * Set assign module specific test values for calling create_module().
      91       *
      92       * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
      93       */
      94      private function assign_create_set_values(&$moduleinfo) {
      95          // Specific values to the Assign module.
      96          $moduleinfo->alwaysshowdescription = true;
      97          $moduleinfo->submissiondrafts = true;
      98          $moduleinfo->requiresubmissionstatement = true;
      99          $moduleinfo->sendnotifications = true;
     100          $moduleinfo->sendlatenotifications = true;
     101          $moduleinfo->duedate = time() + (7 * 24 * 3600);
     102          $moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
     103          $moduleinfo->gradingduedate = time() + (7 * 24 * 3600);
     104          $moduleinfo->allowsubmissionsfromdate = time();
     105          $moduleinfo->teamsubmission = true;
     106          $moduleinfo->requireallteammemberssubmit = true;
     107          $moduleinfo->teamsubmissiongroupingid = true;
     108          $moduleinfo->blindmarking = true;
     109          $moduleinfo->markingworkflow = true;
     110          $moduleinfo->markingallocation = true;
     111          $moduleinfo->assignsubmission_onlinetext_enabled = true;
     112          $moduleinfo->assignsubmission_file_enabled = true;
     113          $moduleinfo->assignsubmission_file_maxfiles = 1;
     114          $moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
     115          $moduleinfo->assignsubmission_comments_enabled = true;
     116          $moduleinfo->assignfeedback_comments_enabled = true;
     117          $moduleinfo->assignfeedback_offline_enabled = true;
     118          $moduleinfo->assignfeedback_file_enabled = true;
     119  
     120          // Advanced grading.
     121          $gradingmethods = grading_manager::available_methods();
     122          $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
     123      }
     124  
     125      /**
     126       * Execute test asserts on the saved DB data by create_module($assign).
     127       *
     128       * @param object $moduleinfo - the specific assign module values that were used to create an assign module.
     129       * @param object $dbmodinstance - the DB values of the created assign module.
     130       */
     131      private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
     132          global $DB;
     133  
     134          $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
     135          $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
     136          $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
     137          $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
     138          $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
     139          $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
     140          $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
     141          $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
     142          $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
     143          $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
     144          $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
     145          $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow);
     146          $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation);
     147          // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
     148  
     149          // Advanced grading.
     150          $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id);
     151          $contextmodule = context_module::instance($cm->id);
     152          $advancedgradingmethod = $DB->get_record('grading_areas',
     153              array('contextid' => $contextmodule->id,
     154                  'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
     155          $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
     156      }
     157  
     158      /**
     159       * Run some asserts test for a specific module for the function create_module().
     160       *
     161       * The function has been created (and is called) for $this->test_create_module().
     162       * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
     163       * So if you want, you can overwrite the default values/asserts in the respective functions.
     164       * @param string $modulename Name of the module ('forum', 'assign', 'book'...).
     165       */
     166      private function create_specific_module_test($modulename) {
     167          global $DB, $CFG;
     168  
     169          $this->resetAfterTest(true);
     170  
     171          $this->setAdminUser();
     172  
     173          // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
     174          require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
     175  
     176          // Enable avaibility.
     177          // If not enabled all conditional fields will be ignored.
     178          set_config('enableavailability', 1);
     179  
     180          // Enable course completion.
     181          // If not enabled all completion settings will be ignored.
     182          set_config('enablecompletion', COMPLETION_ENABLED);
     183  
     184          // Enable forum RSS feeds.
     185          set_config('enablerssfeeds', 1);
     186          set_config('forum_enablerssfeeds', 1);
     187  
     188          $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
     189             array('createsections'=>true));
     190  
     191          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
     192  
     193          // Create assign module instance for test.
     194          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
     195          $params['course'] = $course->id;
     196          $instance = $generator->create_instance($params);
     197          $assigncm = get_coursemodule_from_instance('assign', $instance->id);
     198  
     199          // Module test values.
     200          $moduleinfo = new stdClass();
     201  
     202          // Always mandatory generic values to any module.
     203          $moduleinfo->modulename = $modulename;
     204          $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
     205          $moduleinfo->course = $course->id;
     206          $moduleinfo->groupingid = $grouping->id;
     207          $moduleinfo->visible = true;
     208          $moduleinfo->visibleoncoursepage = true;
     209  
     210          // Sometimes optional generic values for some modules.
     211          $moduleinfo->name = 'My test module';
     212          $moduleinfo->showdescription = 1; // standard boolean
     213          require_once($CFG->libdir . '/gradelib.php');
     214          $gradecats = grade_get_categories_menu($moduleinfo->course, false);
     215          $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
     216          $moduleinfo->gradecat = $gradecatid;
     217          $moduleinfo->groupmode = VISIBLEGROUPS;
     218          $moduleinfo->cmidnumber = 'idnumber_XXX';
     219  
     220          // Completion common to all module.
     221          $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
     222          $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
     223          $moduleinfo->completiongradeitemnumber = 1;
     224          $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
     225  
     226          // Conditional activity.
     227          $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
     228                  '{"type":"date","d":">=","t":' . time() . '},' .
     229                  '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
     230                  ']}';
     231          $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
     232          $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
     233          $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
     234          $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
     235  
     236          // Grading and Advanced grading.
     237          require_once($CFG->dirroot . '/rating/lib.php');
     238          $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
     239          $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
     240          $moduleinfo->assesstimestart = time();
     241          $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
     242  
     243          // RSS.
     244          $moduleinfo->rsstype = 2;
     245          $moduleinfo->rssarticles = 10;
     246  
     247          // Optional intro editor (depends of module).
     248          $draftid_editor = 0;
     249          file_prepare_draft_area($draftid_editor, null, null, null, null);
     250          $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
     251  
     252          // Following is the advanced grading method area called 'submissions' for the 'assign' module.
     253          if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
     254              $moduleinfo->grade = 100;
     255          }
     256  
     257          // Plagiarism form values.
     258          // No plagiarism plugin installed by default. Use this space to make your own test.
     259  
     260          // Values specific to the module.
     261          $modulesetvalues = $modulename.'_create_set_values';
     262          $this->$modulesetvalues($moduleinfo);
     263  
     264          // Create the module.
     265          $result = create_module($moduleinfo);
     266  
     267          // Retrieve the module info.
     268          $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
     269          $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
     270          // We passed the course section number to create_courses but $dbcm contain the section id.
     271          // We need to retrieve the db course section number.
     272          $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
     273          // Retrieve the grade item.
     274          $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
     275              'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
     276  
     277          // Compare the values common to all module instances.
     278          $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
     279          $this->assertEquals($moduleinfo->section, $section->section);
     280          $this->assertEquals($moduleinfo->course, $dbcm->course);
     281          $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
     282          $this->assertEquals($moduleinfo->visible, $dbcm->visible);
     283          $this->assertEquals($moduleinfo->completion, $dbcm->completion);
     284          $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
     285          $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
     286          $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
     287          $this->assertEquals($moduleinfo->availability, $dbcm->availability);
     288          $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
     289          $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
     290          $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
     291          $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
     292  
     293          // Optional grade testing.
     294          if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
     295              $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
     296          }
     297  
     298          // Some optional (but quite common) to some module.
     299          $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
     300          $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
     301          $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
     302  
     303          // Test specific to the module.
     304          $modulerunasserts = $modulename.'_create_run_asserts';
     305          $this->$modulerunasserts($moduleinfo, $dbmodinstance);
     306          return $moduleinfo;
     307      }
     308  
     309      /**
     310       * Create module associated blog and tags.
     311       *
     312       * @param object $course Course.
     313       * @param object $modulecontext The context of the module.
     314       */
     315      private function create_module_asscociated_blog($course, $modulecontext) {
     316          global $DB, $CFG;
     317  
     318          // Create default group.
     319          $group = new stdClass();
     320          $group->courseid = $course->id;
     321          $group->name = 'Group';
     322          $group->id = $DB->insert_record('groups', $group);
     323  
     324          // Create default user.
     325          $user = $this->getDataGenerator()->create_user(array(
     326              'username' => 'testuser',
     327              'firstname' => 'Firsname',
     328              'lastname' => 'Lastname'
     329          ));
     330  
     331          // Create default post.
     332          $post = new stdClass();
     333          $post->userid = $user->id;
     334          $post->groupid = $group->id;
     335          $post->content = 'test post content text';
     336          $post->module = 'blog';
     337          $post->id = $DB->insert_record('post', $post);
     338  
     339          // Create default tag.
     340          $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
     341              'rawname' => 'Testtagname', 'isstandard' => 1));
     342          // Apply the tag to the blog.
     343          $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user',
     344              'component' => 'core', 'itemid' => $post->id, 'ordering' => 0));
     345  
     346          require_once($CFG->dirroot . '/blog/locallib.php');
     347          $blog = new blog_entry($post->id);
     348          $blog->add_association($modulecontext->id);
     349  
     350          return $blog;
     351      }
     352  
     353      /**
     354       * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
     355       */
     356      public function test_create_module() {
     357          // Add the module name you want to test here.
     358          // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
     359          $modules = array('forum', 'assign');
     360          // Run all tests.
     361          foreach ($modules as $modulename) {
     362              $this->create_specific_module_test($modulename);
     363          }
     364      }
     365  
     366      /**
     367       * Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
     368       */
     369      public function test_update_module() {
     370          // Add the module name you want to test here.
     371          // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
     372          $modules = array('forum');
     373          // Run all tests.
     374          foreach ($modules as $modulename) {
     375              $this->update_specific_module_test($modulename);
     376          }
     377      }
     378  
     379      /**
     380       * Set forum specific test values for calling update_module().
     381       *
     382       * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
     383       */
     384      private function forum_update_set_values(&$moduleinfo) {
     385          // Completion specific to forum - optional.
     386          $moduleinfo->completionposts = 3;
     387          $moduleinfo->completiondiscussions = 1;
     388          $moduleinfo->completionreplies = 2;
     389  
     390          // Specific values to the Forum module.
     391          $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
     392          $moduleinfo->type = 'single';
     393          $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
     394          $moduleinfo->maxbytes = 10240;
     395          $moduleinfo->maxattachments = 2;
     396  
     397          // Post threshold for blocking - specific to forum.
     398          $moduleinfo->blockperiod = 60*60*24;
     399          $moduleinfo->blockafter = 10;
     400          $moduleinfo->warnafter = 5;
     401  
     402          // Grading of whole forum settings.
     403          $moduleinfo->grade_forum = 0;
     404      }
     405  
     406      /**
     407       * Execute test asserts on the saved DB data by update_module($forum).
     408       *
     409       * @param object $moduleinfo - the specific forum values that were used to update a forum.
     410       * @param object $dbmodinstance - the DB values of the updated forum.
     411       */
     412      private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
     413          // Compare values specific to forums.
     414          $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
     415          $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
     416          $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
     417          $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
     418          $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
     419          $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
     420          $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
     421          $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
     422          $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
     423          $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
     424          $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
     425          $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
     426          $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
     427          $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
     428          $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
     429          $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
     430          $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
     431      }
     432  
     433  
     434  
     435      /**
     436       * Test a specific type of module.
     437       *
     438       * @param string $modulename - the module name to test
     439       */
     440      private function update_specific_module_test($modulename) {
     441          global $DB, $CFG;
     442  
     443          $this->resetAfterTest(true);
     444  
     445          $this->setAdminUser();
     446  
     447          // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
     448          require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
     449  
     450          // Enable avaibility.
     451          // If not enabled all conditional fields will be ignored.
     452          set_config('enableavailability', 1);
     453  
     454          // Enable course completion.
     455          // If not enabled all completion settings will be ignored.
     456          set_config('enablecompletion', COMPLETION_ENABLED);
     457  
     458          // Enable forum RSS feeds.
     459          set_config('enablerssfeeds', 1);
     460          set_config('forum_enablerssfeeds', 1);
     461  
     462          $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
     463             array('createsections'=>true));
     464  
     465          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
     466  
     467          // Create assign module instance for testing gradeitem.
     468          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
     469          $params['course'] = $course->id;
     470          $instance = $generator->create_instance($params);
     471          $assigncm = get_coursemodule_from_instance('assign', $instance->id);
     472  
     473          // Create the test forum to update.
     474          $initvalues = new stdClass();
     475          $initvalues->introformat = FORMAT_HTML;
     476          $initvalues->course = $course->id;
     477          $forum = self::getDataGenerator()->create_module('forum', $initvalues);
     478  
     479          // Retrieve course module.
     480          $cm = get_coursemodule_from_instance('forum', $forum->id);
     481  
     482          // Module test values.
     483          $moduleinfo = new stdClass();
     484  
     485          // Always mandatory generic values to any module.
     486          $moduleinfo->coursemodule = $cm->id;
     487          $moduleinfo->modulename = $modulename;
     488          $moduleinfo->course = $course->id;
     489          $moduleinfo->groupingid = $grouping->id;
     490          $moduleinfo->visible = true;
     491          $moduleinfo->visibleoncoursepage = true;
     492  
     493          // Sometimes optional generic values for some modules.
     494          $moduleinfo->name = 'My test module';
     495          $moduleinfo->showdescription = 1; // standard boolean
     496          require_once($CFG->libdir . '/gradelib.php');
     497          $gradecats = grade_get_categories_menu($moduleinfo->course, false);
     498          $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
     499          $moduleinfo->gradecat = $gradecatid;
     500          $moduleinfo->groupmode = VISIBLEGROUPS;
     501          $moduleinfo->cmidnumber = 'idnumber_XXX';
     502  
     503          // Completion common to all module.
     504          $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
     505          $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
     506          $moduleinfo->completiongradeitemnumber = 1;
     507          $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
     508          $moduleinfo->completionunlocked = 1;
     509  
     510          // Conditional activity.
     511          $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
     512          $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
     513                  array(\availability_date\condition::get_json('>=', time()),
     514                  \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
     515                  \availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
     516                  \availability_profile\condition::get_json(false, 'email', 'contains', '@'),
     517                  \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
     518  
     519          // Grading and Advanced grading.
     520          require_once($CFG->dirroot . '/rating/lib.php');
     521          $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
     522          $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
     523          $moduleinfo->assesstimestart = time();
     524          $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
     525  
     526          // RSS.
     527          $moduleinfo->rsstype = 2;
     528          $moduleinfo->rssarticles = 10;
     529  
     530          // Optional intro editor (depends of module).
     531          $draftid_editor = 0;
     532          file_prepare_draft_area($draftid_editor, null, null, null, null);
     533          $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
     534  
     535          // Following is the advanced grading method area called 'submissions' for the 'assign' module.
     536          if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
     537              $moduleinfo->grade = 100;
     538          }
     539          // Plagiarism form values.
     540          // No plagiarism plugin installed by default. Use this space to make your own test.
     541  
     542          // Values specific to the module.
     543          $modulesetvalues = $modulename.'_update_set_values';
     544          $this->$modulesetvalues($moduleinfo);
     545  
     546          // Create the module.
     547          $result = update_module($moduleinfo);
     548  
     549          // Retrieve the module info.
     550          $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
     551          $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
     552          // Retrieve the grade item.
     553          $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
     554              'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
     555  
     556          // Compare the values common to all module instances.
     557          $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
     558          $this->assertEquals($moduleinfo->course, $dbcm->course);
     559          $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
     560          $this->assertEquals($moduleinfo->visible, $dbcm->visible);
     561          $this->assertEquals($moduleinfo->completion, $dbcm->completion);
     562          $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
     563          $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
     564          $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
     565          $this->assertEquals($moduleinfo->availability, $dbcm->availability);
     566          $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
     567          $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
     568          $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
     569          $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
     570  
     571          // Optional grade testing.
     572          if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
     573              $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
     574          }
     575  
     576          // Some optional (but quite common) to some module.
     577          $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
     578          $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
     579          $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
     580  
     581          // Test specific to the module.
     582          $modulerunasserts = $modulename.'_update_run_asserts';
     583          $this->$modulerunasserts($moduleinfo, $dbmodinstance);
     584          return $moduleinfo;
     585     }
     586  
     587      /**
     588       * Data provider for course_delete module
     589       *
     590       * @return array An array of arrays contain test data
     591       */
     592      public function provider_course_delete_module() {
     593          $data = array();
     594  
     595          $data['assign'] = array('assign', array('duedate' => time()));
     596          $data['quiz'] = array('quiz', array('duedate' => time()));
     597  
     598          return $data;
     599      }
     600  
     601      /**
     602       * Test the create_course function
     603       */
     604      public function test_create_course() {
     605          global $DB;
     606          $this->resetAfterTest(true);
     607          $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
     608  
     609          $course = new stdClass();
     610          $course->fullname = 'Apu loves Unit Təsts';
     611          $course->shortname = 'Spread the lŭve';
     612          $course->idnumber = '123';
     613          $course->summary = 'Awesome!';
     614          $course->summaryformat = FORMAT_PLAIN;
     615          $course->format = 'topics';
     616          $course->newsitems = 0;
     617          $course->category = $defaultcategory;
     618          $original = (array) $course;
     619  
     620          $created = create_course($course);
     621          $context = context_course::instance($created->id);
     622  
     623          // Compare original and created.
     624          $this->assertEquals($original, array_intersect_key((array) $created, $original));
     625  
     626          // Ensure default section is created.
     627          $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
     628          $this->assertTrue($sectioncreated);
     629  
     630          // Ensure that the shortname isn't duplicated.
     631          try {
     632              $created = create_course($course);
     633              $this->fail('Exception expected');
     634          } catch (moodle_exception $e) {
     635              $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
     636          }
     637  
     638          // Ensure that the idnumber isn't duplicated.
     639          $course->shortname .= '1';
     640          try {
     641              $created = create_course($course);
     642              $this->fail('Exception expected');
     643          } catch (moodle_exception $e) {
     644              $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
     645          }
     646      }
     647  
     648      public function test_create_course_with_generator() {
     649          global $DB;
     650          $this->resetAfterTest(true);
     651          $course = $this->getDataGenerator()->create_course();
     652  
     653          // Ensure default section is created.
     654          $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
     655          $this->assertTrue($sectioncreated);
     656      }
     657  
     658      public function test_create_course_sections() {
     659          global $DB;
     660          $this->resetAfterTest(true);
     661  
     662          $numsections = 5;
     663          $course = $this->getDataGenerator()->create_course(
     664                  array('shortname' => 'GrowingCourse',
     665                      'fullname' => 'Growing Course',
     666                      'numsections' => $numsections),
     667                  array('createsections' => true));
     668  
     669          // Ensure all 6 (0-5) sections were created and course content cache works properly
     670          $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
     671          $this->assertEquals(range(0, $numsections), $sectionscreated);
     672  
     673          // this will do nothing, section already exists
     674          $this->assertFalse(course_create_sections_if_missing($course, $numsections));
     675  
     676          // this will create new section
     677          $this->assertTrue(course_create_sections_if_missing($course, $numsections + 1));
     678  
     679          // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
     680          $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
     681          $this->assertEquals(range(0, $numsections + 1), $sectionscreated);
     682      }
     683  
     684      public function test_update_course() {
     685          global $DB;
     686  
     687          $this->resetAfterTest();
     688  
     689          $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
     690  
     691          $course = new stdClass();
     692          $course->fullname = 'Apu loves Unit Təsts';
     693          $course->shortname = 'test1';
     694          $course->idnumber = '1';
     695          $course->summary = 'Awesome!';
     696          $course->summaryformat = FORMAT_PLAIN;
     697          $course->format = 'topics';
     698          $course->newsitems = 0;
     699          $course->numsections = 5;
     700          $course->category = $defaultcategory;
     701  
     702          $created = create_course($course);
     703          // Ensure the checks only work on idnumber/shortname that are not already ours.
     704          update_course($created);
     705  
     706          $course->shortname = 'test2';
     707          $course->idnumber = '2';
     708  
     709          $created2 = create_course($course);
     710  
     711          // Test duplicate idnumber.
     712          $created2->idnumber = '1';
     713          try {
     714              update_course($created2);
     715              $this->fail('Expected exception when trying to update a course with duplicate idnumber');
     716          } catch (moodle_exception $e) {
     717              $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
     718          }
     719  
     720          // Test duplicate shortname.
     721          $created2->idnumber = '2';
     722          $created2->shortname = 'test1';
     723          try {
     724              update_course($created2);
     725              $this->fail('Expected exception when trying to update a course with a duplicate shortname');
     726          } catch (moodle_exception $e) {
     727              $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
     728          }
     729      }
     730  
     731      public function test_update_course_section_time_modified() {
     732          global $DB;
     733  
     734          $this->resetAfterTest();
     735  
     736          // Create the course with sections.
     737          $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
     738          $sections = $DB->get_records('course_sections', array('course' => $course->id));
     739  
     740          // Get the last section's time modified value.
     741          $section = array_pop($sections);
     742          $oldtimemodified = $section->timemodified;
     743  
     744          // Update the section.
     745          $this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp.
     746          course_update_section($course, $section, array());
     747  
     748          // Check that the time has changed.
     749          $section = $DB->get_record('course_sections', array('id' => $section->id));
     750          $newtimemodified = $section->timemodified;
     751          $this->assertGreaterThan($oldtimemodified, $newtimemodified);
     752      }
     753  
     754      /**
     755       * Relative dates mode settings provider for course creation.
     756       */
     757      public function create_course_relative_dates_provider() {
     758          return [
     759              [0, 0, 0],
     760              [0, 1, 0],
     761              [1, 0, 0],
     762              [1, 1, 1],
     763          ];
     764      }
     765  
     766      /**
     767       * Test create_course by attempting to change the relative dates mode.
     768       *
     769       * @dataProvider create_course_relative_dates_provider
     770       * @param int $setting The value for the 'enablecourserelativedates' admin setting.
     771       * @param int $mode The value for the course's 'relativedatesmode' field.
     772       * @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation.
     773       */
     774      public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue) {
     775          global $DB;
     776  
     777          $this->resetAfterTest();
     778  
     779          set_config('enablecourserelativedates', $setting);
     780  
     781          // Generate a course with relative dates mode set to $mode.
     782          $course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]);
     783  
     784          // Verify that the relative dates match what's expected.
     785          $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
     786          $this->assertEquals($expectedvalue, $relativedatesmode);
     787      }
     788  
     789      /**
     790       * Test update_course by attempting to change the relative dates mode.
     791       */
     792      public function test_relative_dates_mode_for_course_update() {
     793          global $DB;
     794  
     795          $this->resetAfterTest();
     796  
     797          set_config('enablecourserelativedates', 1);
     798  
     799          // Generate a course with relative dates mode set to 1.
     800          $course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]);
     801  
     802          // Attempt to update the course with a changed relativedatesmode.
     803          $course->relativedatesmode = 0;
     804          update_course($course);
     805  
     806          // Verify that the relative dates mode has not changed.
     807          $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
     808          $this->assertEquals(1, $relativedatesmode);
     809      }
     810  
     811      public function test_course_add_cm_to_section() {
     812          global $DB;
     813          $this->resetAfterTest(true);
     814  
     815          // Create course with 1 section.
     816          $course = $this->getDataGenerator()->create_course(
     817                  array('shortname' => 'GrowingCourse',
     818                      'fullname' => 'Growing Course',
     819                      'numsections' => 1),
     820                  array('createsections' => true));
     821  
     822          // Trash modinfo.
     823          rebuild_course_cache($course->id, true);
     824  
     825          // Create some cms for testing.
     826          $cmids = array();
     827          for ($i=0; $i<4; $i++) {
     828              $cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id));
     829          }
     830  
     831          // Add it to section that exists.
     832          course_add_cm_to_section($course, $cmids[0], 1);
     833  
     834          // Check it got added to sequence.
     835          $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
     836          $this->assertEquals($cmids[0], $sequence);
     837  
     838          // Add a second, this time using courseid variant of parameters.
     839          $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
     840          course_add_cm_to_section($course->id, $cmids[1], 1);
     841          $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
     842          $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
     843  
     844          // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
     845          $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
     846          $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
     847  
     848          // Add one to section that doesn't exist (this might rebuild modinfo).
     849          course_add_cm_to_section($course, $cmids[2], 2);
     850          $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
     851          $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
     852          $this->assertEquals($cmids[2], $sequence);
     853  
     854          // Add using the 'before' option.
     855          course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]);
     856          $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
     857          $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
     858          $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
     859      }
     860  
     861      public function test_reorder_sections() {
     862          global $DB;
     863          $this->resetAfterTest(true);
     864  
     865          $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
     866          $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
     867          $oldsections = array();
     868          $sections = array();
     869          foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
     870              $oldsections[$section->section] = $section->id;
     871              $sections[$section->id] = $section->section;
     872          }
     873          ksort($oldsections);
     874  
     875          $neworder = reorder_sections($sections, 2, 4);
     876          $neworder = array_keys($neworder);
     877          $this->assertEquals($oldsections[0], $neworder[0]);
     878          $this->assertEquals($oldsections[1], $neworder[1]);
     879          $this->assertEquals($oldsections[2], $neworder[4]);
     880          $this->assertEquals($oldsections[3], $neworder[2]);
     881          $this->assertEquals($oldsections[4], $neworder[3]);
     882          $this->assertEquals($oldsections[5], $neworder[5]);
     883          $this->assertEquals($oldsections[6], $neworder[6]);
     884  
     885          $neworder = reorder_sections($sections, 4, 2);
     886          $neworder = array_keys($neworder);
     887          $this->assertEquals($oldsections[0], $neworder[0]);
     888          $this->assertEquals($oldsections[1], $neworder[1]);
     889          $this->assertEquals($oldsections[2], $neworder[3]);
     890          $this->assertEquals($oldsections[3], $neworder[4]);
     891          $this->assertEquals($oldsections[4], $neworder[2]);
     892          $this->assertEquals($oldsections[5], $neworder[5]);
     893          $this->assertEquals($oldsections[6], $neworder[6]);
     894  
     895          $neworder = reorder_sections(1, 2, 4);
     896          $this->assertFalse($neworder);
     897      }
     898  
     899      public function test_move_section_down() {
     900          global $DB;
     901          $this->resetAfterTest(true);
     902  
     903          $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
     904          $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
     905          $oldsections = array();
     906          foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
     907              $oldsections[$section->section] = $section->id;
     908          }
     909          ksort($oldsections);
     910  
     911          // Test move section down..
     912          move_section_to($course, 2, 4);
     913          $sections = array();
     914          foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
     915              $sections[$section->section] = $section->id;
     916          }
     917          ksort($sections);
     918  
     919          $this->assertEquals($oldsections[0], $sections[0]);
     920          $this->assertEquals($oldsections[1], $sections[1]);
     921          $this->assertEquals($oldsections[2], $sections[4]);
     922          $this->assertEquals($oldsections[3], $sections[2]);
     923          $this->assertEquals($oldsections[4], $sections[3]);
     924          $this->assertEquals($oldsections[5], $sections[5]);
     925          $this->assertEquals($oldsections[6], $sections[6]);
     926      }
     927  
     928      public function test_move_section_up() {
     929          global $DB;
     930          $this->resetAfterTest(true);
     931  
     932          $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
     933          $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
     934          $oldsections = array();
     935          foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
     936              $oldsections[$section->section] = $section->id;
     937          }
     938          ksort($oldsections);
     939  
     940          // Test move section up..
     941          move_section_to($course, 6, 4);
     942          $sections = array();
     943          foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
     944              $sections[$section->section] = $section->id;
     945          }
     946          ksort($sections);
     947  
     948          $this->assertEquals($oldsections[0], $sections[0]);
     949          $this->assertEquals($oldsections[1], $sections[1]);
     950          $this->assertEquals($oldsections[2], $sections[2]);
     951          $this->assertEquals($oldsections[3], $sections[3]);
     952          $this->assertEquals($oldsections[4], $sections[5]);
     953          $this->assertEquals($oldsections[5], $sections[6]);
     954          $this->assertEquals($oldsections[6], $sections[4]);
     955      }
     956  
     957      public function test_move_section_marker() {
     958          global $DB;
     959          $this->resetAfterTest(true);
     960  
     961          $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
     962          $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
     963  
     964          // Set course marker to the section we are going to move..
     965          course_set_marker($course->id, 2);
     966          // Verify that the course marker is set correctly.
     967          $course = $DB->get_record('course', array('id' => $course->id));
     968          $this->assertEquals(2, $course->marker);
     969  
     970          // Test move the marked section down..
     971          move_section_to($course, 2, 4);
     972  
     973          // Verify that the course marker has been moved along with the section..
     974          $course = $DB->get_record('course', array('id' => $course->id));
     975          $this->assertEquals(4, $course->marker);
     976  
     977          // Test move the marked section up..
     978          move_section_to($course, 4, 3);
     979  
     980          // Verify that the course marker has been moved along with the section..
     981          $course = $DB->get_record('course', array('id' => $course->id));
     982          $this->assertEquals(3, $course->marker);
     983  
     984          // Test moving a non-marked section above the marked section..
     985          move_section_to($course, 4, 2);
     986  
     987          // Verify that the course marker has been moved down to accomodate..
     988          $course = $DB->get_record('course', array('id' => $course->id));
     989          $this->assertEquals(4, $course->marker);
     990  
     991          // Test moving a non-marked section below the marked section..
     992          move_section_to($course, 3, 6);
     993  
     994          // Verify that the course marker has been up to accomodate..
     995          $course = $DB->get_record('course', array('id' => $course->id));
     996          $this->assertEquals(3, $course->marker);
     997      }
     998  
     999      /**
    1000       * Test move_section_to method.
    1001       * Make sure that we only update the moving sections, not all the sections in the current course.
    1002       *
    1003       * @return void
    1004       */
    1005      public function test_move_section_to() {
    1006          global $DB, $CFG;
    1007          $this->resetAfterTest();
    1008          $this->setAdminUser();
    1009  
    1010          // Generate the course and pre-requisite module.
    1011          $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
    1012  
    1013          ob_start();
    1014          $DB->set_debug(true);
    1015          // Move section.
    1016          move_section_to($course, 2, 3);
    1017          $DB->set_debug(false);
    1018          $debuginfo = ob_get_contents();
    1019          ob_end_clean();
    1020          $sectionmovequerycount = substr_count($debuginfo, 'UPDATE ' . $CFG->phpunit_prefix . 'course_sections SET');
    1021          // We are updating the course_section table in steps to avoid breaking database uniqueness constraint.
    1022          // So the queries will be doubled. See: course/lib.php:1423
    1023          // Make sure that we only need 4 queries to update the position of section 2 and section 3.
    1024          $this->assertEquals(4, $sectionmovequerycount);
    1025      }
    1026  
    1027      public function test_course_can_delete_section() {
    1028          global $DB;
    1029          $this->resetAfterTest(true);
    1030  
    1031          $generator = $this->getDataGenerator();
    1032  
    1033          $courseweeks = $generator->create_course(
    1034              array('numsections' => 5, 'format' => 'weeks'),
    1035              array('createsections' => true));
    1036          $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1));
    1037          $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2));
    1038  
    1039          $coursetopics = $generator->create_course(
    1040              array('numsections' => 5, 'format' => 'topics'),
    1041              array('createsections' => true));
    1042  
    1043          $coursesingleactivity = $generator->create_course(
    1044              array('format' => 'singleactivity'),
    1045              array('createsections' => true));
    1046  
    1047          // Enrol student and teacher.
    1048          $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
    1049          $student = $generator->create_user();
    1050          $teacher = $generator->create_user();
    1051  
    1052          $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']);
    1053          $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']);
    1054  
    1055          $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']);
    1056          $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']);
    1057  
    1058          $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']);
    1059          $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']);
    1060  
    1061          // Teacher should be able to delete sections (except for 0) in topics and weeks format.
    1062          $this->setUser($teacher);
    1063  
    1064          // For topics and weeks formats will return false for section 0 and true for any other section.
    1065          $this->assertFalse(course_can_delete_section($courseweeks, 0));
    1066          $this->assertTrue(course_can_delete_section($courseweeks, 1));
    1067  
    1068          $this->assertFalse(course_can_delete_section($coursetopics, 0));
    1069          $this->assertTrue(course_can_delete_section($coursetopics, 1));
    1070  
    1071          // For singleactivity course format no section can be deleted.
    1072          $this->assertFalse(course_can_delete_section($coursesingleactivity, 0));
    1073          $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
    1074  
    1075          // Now let's revoke a capability from teacher to manage activity in section 1.
    1076          $modulecontext = context_module::instance($assign1->cmid);
    1077          assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'],
    1078              $modulecontext);
    1079          $this->assertFalse(course_can_delete_section($courseweeks, 1));
    1080          $this->assertTrue(course_can_delete_section($courseweeks, 2));
    1081  
    1082          // Student does not have permissions to delete sections.
    1083          $this->setUser($student);
    1084          $this->assertFalse(course_can_delete_section($courseweeks, 1));
    1085          $this->assertFalse(course_can_delete_section($coursetopics, 1));
    1086          $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
    1087      }
    1088  
    1089      public function test_course_delete_section() {
    1090          global $DB;
    1091          $this->resetAfterTest(true);
    1092  
    1093          $generator = $this->getDataGenerator();
    1094  
    1095          $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'),
    1096              array('createsections' => true));
    1097          $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0));
    1098          $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1));
    1099          $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
    1100          $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
    1101          $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3));
    1102          $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5));
    1103          $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6));
    1104  
    1105          $this->setAdminUser();
    1106  
    1107          // Attempt to delete non-existing section.
    1108          $this->assertFalse(course_delete_section($course, 10, false));
    1109          $this->assertFalse(course_delete_section($course, 9, true));
    1110  
    1111          // Attempt to delete 0-section.
    1112          $this->assertFalse(course_delete_section($course, 0, true));
    1113          $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid)));
    1114  
    1115          // Delete last section.
    1116          $this->assertTrue(course_delete_section($course, 6, true));
    1117          $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid)));
    1118          $this->assertEquals(5, course_get_format($course)->get_last_section_number());
    1119  
    1120          // Delete empty section.
    1121          $this->assertTrue(course_delete_section($course, 4, false));
    1122          $this->assertEquals(4, course_get_format($course)->get_last_section_number());
    1123  
    1124          // Delete section in the middle (2).
    1125          $this->assertFalse(course_delete_section($course, 2, false));
    1126          $this->assertTrue(course_delete_section($course, 2, true));
    1127          $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid)));
    1128          $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid)));
    1129          $this->assertEquals(3, course_get_format($course)->get_last_section_number());
    1130          $this->assertEquals(array(0 => array($assign0->cmid),
    1131              1 => array($assign1->cmid),
    1132              2 => array($assign3->cmid),
    1133              3 => array($assign5->cmid)), get_fast_modinfo($course)->sections);
    1134  
    1135          // Remove marked section.
    1136          course_set_marker($course->id, 1);
    1137          $this->assertTrue(course_get_format($course)->is_section_current(1));
    1138          $this->assertTrue(course_delete_section($course, 1, true));
    1139          $this->assertFalse(course_get_format($course)->is_section_current(1));
    1140      }
    1141  
    1142      public function test_get_course_display_name_for_list() {
    1143          global $CFG;
    1144          $this->resetAfterTest(true);
    1145  
    1146          $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
    1147  
    1148          $CFG->courselistshortnames = 0;
    1149          $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
    1150  
    1151          $CFG->courselistshortnames = 1;
    1152          $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
    1153      }
    1154  
    1155      public function test_move_module_in_course() {
    1156          global $DB;
    1157  
    1158          $this->resetAfterTest(true);
    1159          // Setup fixture
    1160          $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
    1161          $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
    1162  
    1163          $cms = get_fast_modinfo($course)->get_cms();
    1164          $cm = reset($cms);
    1165  
    1166          $newsection = get_fast_modinfo($course)->get_section_info(3);
    1167          $oldsectionid = $cm->section;
    1168  
    1169          // Perform the move
    1170          moveto_module($cm, $newsection);
    1171  
    1172          $cms = get_fast_modinfo($course)->get_cms();
    1173          $cm = reset($cms);
    1174  
    1175          // Check that the cached modinfo contains the correct section info
    1176          $modinfo = get_fast_modinfo($course);
    1177          $this->assertTrue(empty($modinfo->sections[0]));
    1178          $this->assertFalse(empty($modinfo->sections[3]));
    1179  
    1180          // Check that the old section's sequence no longer contains this ID
    1181          $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
    1182          $oldsequences = explode(',', $newsection->sequence);
    1183          $this->assertFalse(in_array($cm->id, $oldsequences));
    1184  
    1185          // Check that the new section's sequence now contains this ID
    1186          $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
    1187          $newsequences = explode(',', $newsection->sequence);
    1188          $this->assertTrue(in_array($cm->id, $newsequences));
    1189  
    1190          // Check that the section number has been changed in the cm
    1191          $this->assertEquals($newsection->id, $cm->section);
    1192  
    1193  
    1194          // Perform a second move as some issues were only seen on the second move
    1195          $newsection = get_fast_modinfo($course)->get_section_info(2);
    1196          $oldsectionid = $cm->section;
    1197          moveto_module($cm, $newsection);
    1198  
    1199          $cms = get_fast_modinfo($course)->get_cms();
    1200          $cm = reset($cms);
    1201  
    1202          // Check that the cached modinfo contains the correct section info
    1203          $modinfo = get_fast_modinfo($course);
    1204          $this->assertTrue(empty($modinfo->sections[0]));
    1205          $this->assertFalse(empty($modinfo->sections[2]));
    1206  
    1207          // Check that the old section's sequence no longer contains this ID
    1208          $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
    1209          $oldsequences = explode(',', $newsection->sequence);
    1210          $this->assertFalse(in_array($cm->id, $oldsequences));
    1211  
    1212          // Check that the new section's sequence now contains this ID
    1213          $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
    1214          $newsequences = explode(',', $newsection->sequence);
    1215          $this->assertTrue(in_array($cm->id, $newsequences));
    1216      }
    1217  
    1218      public function test_module_visibility() {
    1219          $this->setAdminUser();
    1220          $this->resetAfterTest(true);
    1221  
    1222          // Create course and modules.
    1223          $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
    1224          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
    1225          $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
    1226          $modules = compact('forum', 'assign');
    1227  
    1228          // Hiding the modules.
    1229          foreach ($modules as $mod) {
    1230              set_coursemodule_visible($mod->cmid, 0);
    1231              $this->check_module_visibility($mod, 0, 0);
    1232          }
    1233  
    1234          // Showing the modules.
    1235          foreach ($modules as $mod) {
    1236              set_coursemodule_visible($mod->cmid, 1);
    1237              $this->check_module_visibility($mod, 1, 1);
    1238          }
    1239      }
    1240  
    1241      public function test_section_visibility_events() {
    1242          $this->setAdminUser();
    1243          $this->resetAfterTest(true);
    1244  
    1245          $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true));
    1246          $sectionnumber = 1;
    1247          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
    1248              array('section' => $sectionnumber));
    1249          $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
    1250              'course' => $course->id), array('section' => $sectionnumber));
    1251          $sink = $this->redirectEvents();
    1252          set_section_visible($course->id, $sectionnumber, 0);
    1253          $events = $sink->get_events();
    1254  
    1255          // Extract the number of events related to what we are testing, other events
    1256          // such as course_section_updated could have been triggered.
    1257          $count = 0;
    1258          foreach ($events as $event) {
    1259              if ($event instanceof \core\event\course_module_updated) {
    1260                  $count++;
    1261              }
    1262          }
    1263          $this->assertSame(2, $count);
    1264          $sink->close();
    1265      }
    1266  
    1267      public function test_section_visibility() {
    1268          $this->setAdminUser();
    1269          $this->resetAfterTest(true);
    1270  
    1271          // Create course.
    1272          $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
    1273  
    1274          $sink = $this->redirectEvents();
    1275  
    1276          // Testing an empty section.
    1277          $sectionnumber = 1;
    1278          set_section_visible($course->id, $sectionnumber, 0);
    1279          $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
    1280          $this->assertEquals($section_info->visible, 0);
    1281          set_section_visible($course->id, $sectionnumber, 1);
    1282          $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
    1283          $this->assertEquals($section_info->visible, 1);
    1284  
    1285          // Checking that an event was fired.
    1286          $events = $sink->get_events();
    1287          $this->assertInstanceOf('\core\event\course_section_updated', $events[0]);
    1288          $sink->close();
    1289  
    1290          // Testing a section with visible modules.
    1291          $sectionnumber = 2;
    1292          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
    1293                  array('section' => $sectionnumber));
    1294          $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
    1295                  'course' => $course->id), array('section' => $sectionnumber));
    1296          $modules = compact('forum', 'assign');
    1297          set_section_visible($course->id, $sectionnumber, 0);
    1298          $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
    1299          $this->assertEquals($section_info->visible, 0);
    1300          foreach ($modules as $mod) {
    1301              $this->check_module_visibility($mod, 0, 1);
    1302          }
    1303          set_section_visible($course->id, $sectionnumber, 1);
    1304          $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
    1305          $this->assertEquals($section_info->visible, 1);
    1306          foreach ($modules as $mod) {
    1307              $this->check_module_visibility($mod, 1, 1);
    1308          }
    1309  
    1310          // Testing a section with hidden modules, which should stay hidden.
    1311          $sectionnumber = 3;
    1312          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
    1313                  array('section' => $sectionnumber));
    1314          $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
    1315                  'course' => $course->id), array('section' => $sectionnumber));
    1316          $modules = compact('forum', 'assign');
    1317          foreach ($modules as $mod) {
    1318              set_coursemodule_visible($mod->cmid, 0);
    1319              $this->check_module_visibility($mod, 0, 0);
    1320          }
    1321          set_section_visible($course->id, $sectionnumber, 0);
    1322          $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
    1323          $this->assertEquals($section_info->visible, 0);
    1324          foreach ($modules as $mod) {
    1325              $this->check_module_visibility($mod, 0, 0);
    1326          }
    1327          set_section_visible($course->id, $sectionnumber, 1);
    1328          $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
    1329          $this->assertEquals($section_info->visible, 1);
    1330          foreach ($modules as $mod) {
    1331              $this->check_module_visibility($mod, 0, 0);
    1332          }
    1333      }
    1334  
    1335      /**
    1336       * Helper function to assert that a module has correctly been made visible, or hidden.
    1337       *
    1338       * @param stdClass $mod module information
    1339       * @param int $visibility the current state of the module
    1340       * @param int $visibleold the current state of the visibleold property
    1341       * @return void
    1342       */
    1343      public function check_module_visibility($mod, $visibility, $visibleold) {
    1344          global $DB;
    1345          $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
    1346          $this->assertEquals($visibility, $cm->visible);
    1347          $this->assertEquals($visibleold, $cm->visibleold);
    1348  
    1349          // Check the module grade items.
    1350          $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
    1351                  'iteminstance' => $cm->instance, 'courseid' => $cm->course));
    1352          if ($grade_items) {
    1353              foreach ($grade_items as $grade_item) {
    1354                  if ($visibility) {
    1355                      $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
    1356                  } else {
    1357                      $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
    1358                  }
    1359              }
    1360          }
    1361  
    1362          // Check the events visibility.
    1363          if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
    1364              foreach ($events as $event) {
    1365                  $calevent = new calendar_event($event);
    1366                  $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
    1367              }
    1368          }
    1369      }
    1370  
    1371      public function test_course_page_type_list() {
    1372          global $DB;
    1373          $this->resetAfterTest(true);
    1374  
    1375          // Create a category.
    1376          $category = new stdClass();
    1377          $category->name = 'Test Category';
    1378  
    1379          $testcategory = $this->getDataGenerator()->create_category($category);
    1380  
    1381          // Create a course.
    1382          $course = new stdClass();
    1383          $course->fullname = 'Apu loves Unit Təsts';
    1384          $course->shortname = 'Spread the lŭve';
    1385          $course->idnumber = '123';
    1386          $course->summary = 'Awesome!';
    1387          $course->summaryformat = FORMAT_PLAIN;
    1388          $course->format = 'topics';
    1389          $course->newsitems = 0;
    1390          $course->numsections = 5;
    1391          $course->category = $testcategory->id;
    1392  
    1393          $testcourse = $this->getDataGenerator()->create_course($course);
    1394  
    1395          // Create contexts.
    1396          $coursecontext = context_course::instance($testcourse->id);
    1397          $parentcontext = $coursecontext->get_parent_context(); // Not actually used.
    1398          $pagetype = 'page-course-x'; // Not used either.
    1399          $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
    1400  
    1401          // Page type lists for normal courses.
    1402          $testpagetypelist1 = array();
    1403          $testpagetypelist1['*'] = 'Any page';
    1404          $testpagetypelist1['course-*'] = 'Any course page';
    1405          $testpagetypelist1['course-view-*'] = 'Any type of course main page';
    1406  
    1407          $this->assertEquals($testpagetypelist1, $pagetypelist);
    1408  
    1409          // Get the context for the front page course.
    1410          $sitecoursecontext = context_course::instance(SITEID);
    1411          $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
    1412  
    1413          // Page type list for the front page course.
    1414          $testpagetypelist2 = array('*' => 'Any page');
    1415          $this->assertEquals($testpagetypelist2, $pagetypelist);
    1416  
    1417          // Make sure that providing no current context to the function doesn't result in an error.
    1418          // Calls made from generate_page_type_patterns() may provide null values.
    1419          $pagetypelist = course_page_type_list($pagetype, null, null);
    1420          $this->assertEquals($pagetypelist, $testpagetypelist1);
    1421      }
    1422  
    1423      public function test_compare_activities_by_time_desc() {
    1424  
    1425          // Let's create some test data.
    1426          $activitiesivities = array();
    1427          $x = new stdClass();
    1428          $x->timestamp = null;
    1429          $activities[] = $x;
    1430  
    1431          $x = new stdClass();
    1432          $x->timestamp = 1;
    1433          $activities[] = $x;
    1434  
    1435          $x = new stdClass();
    1436          $x->timestamp = 3;
    1437          $activities[] = $x;
    1438  
    1439          $x = new stdClass();
    1440          $x->timestamp = 0;
    1441          $activities[] = $x;
    1442  
    1443          $x = new stdClass();
    1444          $x->timestamp = 5;
    1445          $activities[] = $x;
    1446  
    1447          $x = new stdClass();
    1448          $activities[] = $x;
    1449  
    1450          $x = new stdClass();
    1451          $x->timestamp = 5;
    1452          $activities[] = $x;
    1453  
    1454          // Do the sorting.
    1455          usort($activities, 'compare_activities_by_time_desc');
    1456  
    1457          // Let's check the result.
    1458          $last = 10;
    1459          foreach($activities as $activity) {
    1460              if (empty($activity->timestamp)) {
    1461                  $activity->timestamp = 0;
    1462              }
    1463              $this->assertLessThanOrEqual($last, $activity->timestamp);
    1464          }
    1465      }
    1466  
    1467      public function test_compare_activities_by_time_asc() {
    1468  
    1469          // Let's create some test data.
    1470          $activities = array();
    1471          $x = new stdClass();
    1472          $x->timestamp = null;
    1473          $activities[] = $x;
    1474  
    1475          $x = new stdClass();
    1476          $x->timestamp = 1;
    1477          $activities[] = $x;
    1478  
    1479          $x = new stdClass();
    1480          $x->timestamp = 3;
    1481          $activities[] = $x;
    1482  
    1483          $x = new stdClass();
    1484          $x->timestamp = 0;
    1485          $activities[] = $x;
    1486  
    1487          $x = new stdClass();
    1488          $x->timestamp = 5;
    1489          $activities[] = $x;
    1490  
    1491          $x = new stdClass();
    1492          $activities[] = $x;
    1493  
    1494          $x = new stdClass();
    1495          $x->timestamp = 5;
    1496          $activities[] = $x;
    1497  
    1498          // Do the sorting.
    1499          usort($activities, 'compare_activities_by_time_asc');
    1500  
    1501          // Let's check the result.
    1502          $last = 0;
    1503          foreach($activities as $activity) {
    1504              if (empty($activity->timestamp)) {
    1505                  $activity->timestamp = 0;
    1506              }
    1507              $this->assertGreaterThanOrEqual($last, $activity->timestamp);
    1508          }
    1509      }
    1510  
    1511      /**
    1512       * Tests moving a module between hidden/visible sections and
    1513       * verifies that the course/module visiblity seettings are
    1514       * retained.
    1515       */
    1516      public function test_moveto_module_between_hidden_sections() {
    1517          global $DB;
    1518  
    1519          $this->resetAfterTest(true);
    1520  
    1521          $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
    1522          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
    1523          $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
    1524          $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
    1525  
    1526          // Set the page as hidden
    1527          set_coursemodule_visible($page->cmid, 0);
    1528  
    1529          // Set sections 3 as hidden.
    1530          set_section_visible($course->id, 3, 0);
    1531  
    1532          $modinfo = get_fast_modinfo($course);
    1533  
    1534          $hiddensection = $modinfo->get_section_info(3);
    1535          // New section is definitely not visible:
    1536          $this->assertEquals($hiddensection->visible, 0);
    1537  
    1538          $forumcm = $modinfo->cms[$forum->cmid];
    1539          $pagecm = $modinfo->cms[$page->cmid];
    1540  
    1541          // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state.
    1542          $this->assertEquals(0, moveto_module($forumcm, $hiddensection));
    1543          $this->assertEquals(0, moveto_module($pagecm, $hiddensection));
    1544  
    1545          $modinfo = get_fast_modinfo($course);
    1546  
    1547          // Verify that forum and page have been moved to the hidden section and quiz has not.
    1548          $this->assertContainsEquals($forum->cmid, $modinfo->sections[3]);
    1549          $this->assertContainsEquals($page->cmid, $modinfo->sections[3]);
    1550          $this->assertNotContainsEquals($quiz->cmid, $modinfo->sections[3]);
    1551  
    1552          // Verify that forum has been made invisible.
    1553          $forumcm = $modinfo->cms[$forum->cmid];
    1554          $this->assertEquals($forumcm->visible, 0);
    1555          // Verify that old state has been retained.
    1556          $this->assertEquals($forumcm->visibleold, 1);
    1557  
    1558          // Verify that page has stayed invisible.
    1559          $pagecm = $modinfo->cms[$page->cmid];
    1560          $this->assertEquals($pagecm->visible, 0);
    1561          // Verify that old state has been retained.
    1562          $this->assertEquals($pagecm->visibleold, 0);
    1563  
    1564          // Verify that quiz has been unaffected.
    1565          $quizcm = $modinfo->cms[$quiz->cmid];
    1566          $this->assertEquals($quizcm->visible, 1);
    1567  
    1568          // Move forum and page back to visible section.
    1569          // Make sure the visibility is restored to the original value (visible for forum and hidden for page).
    1570          $visiblesection = $modinfo->get_section_info(2);
    1571          $this->assertEquals(1, moveto_module($forumcm, $visiblesection));
    1572          $this->assertEquals(0, moveto_module($pagecm, $visiblesection));
    1573  
    1574          $modinfo = get_fast_modinfo($course);
    1575  
    1576          // Double check that forum has been made visible.
    1577          $forumcm = $modinfo->cms[$forum->cmid];
    1578          $this->assertEquals($forumcm->visible, 1);
    1579  
    1580          // Double check that page has stayed invisible.
    1581          $pagecm = $modinfo->cms[$page->cmid];
    1582          $this->assertEquals($pagecm->visible, 0);
    1583  
    1584          // Move the page in the same section (this is what mod duplicate does).
    1585          // Visibility of page remains 0.
    1586          $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm));
    1587  
    1588          // Double check that the the page is still hidden.
    1589          $modinfo = get_fast_modinfo($course);
    1590          $pagecm = $modinfo->cms[$page->cmid];
    1591          $this->assertEquals($pagecm->visible, 0);
    1592      }
    1593  
    1594      /**
    1595       * Tests moving a module around in the same section. moveto_module()
    1596       * is called this way in modduplicate.
    1597       */
    1598      public function test_moveto_module_in_same_section() {
    1599          global $DB;
    1600  
    1601          $this->resetAfterTest(true);
    1602  
    1603          $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
    1604          $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
    1605          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
    1606  
    1607          // Simulate inconsistent visible/visibleold values (MDL-38713).
    1608          $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
    1609          $cm->visible = 0;
    1610          $cm->visibleold = 1;
    1611          $DB->update_record('course_modules', $cm);
    1612  
    1613          $modinfo = get_fast_modinfo($course);
    1614          $forumcm = $modinfo->cms[$forum->cmid];
    1615          $pagecm = $modinfo->cms[$page->cmid];
    1616  
    1617          // Verify that page is hidden.
    1618          $this->assertEquals($pagecm->visible, 0);
    1619  
    1620          // Verify section 0 is where all mods added.
    1621          $section = $modinfo->get_section_info(0);
    1622          $this->assertEquals($section->id, $forumcm->section);
    1623          $this->assertEquals($section->id, $pagecm->section);
    1624  
    1625  
    1626          // Move the page inside the hidden section. Make sure it is hidden.
    1627          $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm));
    1628  
    1629          // Double check that the the page is still hidden.
    1630          $modinfo = get_fast_modinfo($course);
    1631          $pagecm = $modinfo->cms[$page->cmid];
    1632          $this->assertEquals($pagecm->visible, 0);
    1633      }
    1634  
    1635      /**
    1636       * Tests the function that deletes a course module
    1637       *
    1638       * @param string $type The type of module for the test
    1639       * @param array $options The options for the module creation
    1640       * @dataProvider provider_course_delete_module
    1641       */
    1642      public function test_course_delete_module($type, $options) {
    1643          global $DB;
    1644  
    1645          $this->resetAfterTest(true);
    1646          $this->setAdminUser();
    1647  
    1648          // Create course and modules.
    1649          $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
    1650          $options['course'] = $course->id;
    1651  
    1652          // Generate an assignment with due date (will generate a course event).
    1653          $module = $this->getDataGenerator()->create_module($type, $options);
    1654  
    1655          // Get the module context.
    1656          $modcontext = context_module::instance($module->cmid);
    1657  
    1658          $assocblog = $this->create_module_asscociated_blog($course, $modcontext);
    1659  
    1660          // Verify context exists.
    1661          $this->assertInstanceOf('context_module', $modcontext);
    1662  
    1663          // Make module specific messes.
    1664          switch ($type) {
    1665              case 'assign':
    1666                  // Add some tags to this assignment.
    1667                  core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
    1668                  core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5'));
    1669  
    1670                  // Confirm the tag instances were added.
    1671                  $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id);
    1672                  $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
    1673                  $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
    1674                  $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
    1675  
    1676                  // Verify event assignment event has been generated.
    1677                  $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
    1678                  $this->assertEquals(1, $eventcount);
    1679  
    1680                  break;
    1681              case 'quiz':
    1682                  $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
    1683                  $qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
    1684                  $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
    1685                  $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
    1686                  break;
    1687              default:
    1688                  break;
    1689          }
    1690  
    1691          // Run delete..
    1692          course_delete_module($module->cmid);
    1693  
    1694          // Verify the context has been removed.
    1695          $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
    1696  
    1697          // Verify the course_module record has been deleted.
    1698          $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
    1699          $this->assertEmpty($cmcount);
    1700  
    1701          // Verify the blog_association record has been deleted.
    1702          $this->assertCount(0, $DB->get_records('blog_association',
    1703                  array('contextid' => $modcontext->id)));
    1704  
    1705          // Verify the blog post record has been deleted.
    1706          $this->assertCount(0, $DB->get_records('post',
    1707                  array('id' => $assocblog->id)));
    1708  
    1709          // Verify the tag instance record has been deleted.
    1710          $this->assertCount(0, $DB->get_records('tag_instance',
    1711                  array('itemid' => $assocblog->id)));
    1712  
    1713          // Test clean up of module specific messes.
    1714          switch ($type) {
    1715              case 'assign':
    1716                  // Verify event assignment events have been removed.
    1717                  $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
    1718                  $this->assertEmpty($eventcount);
    1719  
    1720                  // Verify the tag instances were deleted.
    1721                  $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
    1722                  $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
    1723  
    1724                  $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
    1725                  $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
    1726                  break;
    1727              case 'quiz':
    1728                  // Verify category deleted.
    1729                  $criteria = array('contextid' => $modcontext->id);
    1730                  $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
    1731  
    1732                  // Verify questions deleted.
    1733                  $criteria = array('category' => $qcat->id);
    1734                  $this->assertEquals(0, $DB->count_records('question', $criteria));
    1735                  break;
    1736              default:
    1737                  break;
    1738          }
    1739      }
    1740  
    1741      /**
    1742       * Test that triggering a course_created event works as expected.
    1743       */
    1744      public function test_course_created_event() {
    1745          global $DB;
    1746  
    1747          $this->resetAfterTest();
    1748  
    1749          // Catch the events.
    1750          $sink = $this->redirectEvents();
    1751  
    1752          // Create the course with an id number which is used later when generating a course via the imsenterprise plugin.
    1753          $data = new stdClass();
    1754          $data->idnumber = 'idnumber';
    1755          $course = $this->getDataGenerator()->create_course($data);
    1756          // Get course from DB for comparison.
    1757          $course = $DB->get_record('course', array('id' => $course->id));
    1758  
    1759          // Capture the event.
    1760          $events = $sink->get_events();
    1761          $sink->close();
    1762  
    1763          // Validate the event.
    1764          $event = $events[0];
    1765          $this->assertInstanceOf('\core\event\course_created', $event);
    1766          $this->assertEquals('course', $event->objecttable);
    1767          $this->assertEquals($course->id, $event->objectid);
    1768          $this->assertEquals(context_course::instance($course->id), $event->get_context());
    1769          $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
    1770          $this->assertEquals('course_created', $event->get_legacy_eventname());
    1771          $this->assertEventLegacyData($course, $event);
    1772          $expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
    1773          $this->assertEventLegacyLogData($expectedlog, $event);
    1774  
    1775          // Now we want to trigger creating a course via the imsenterprise.
    1776          // Delete the course we created earlier, as we want the imsenterprise plugin to create this.
    1777          // We do not want print out any of the text this function generates while doing this, which is why
    1778          // we are using ob_start() and ob_end_clean().
    1779          ob_start();
    1780          delete_course($course);
    1781          ob_end_clean();
    1782  
    1783          // Create the XML file we want to use.
    1784          $course->category = (array)$course->category;
    1785          $imstestcase = new enrol_imsenterprise_testcase();
    1786          $imstestcase->imsplugin = enrol_get_plugin('imsenterprise');
    1787          $imstestcase->set_test_config();
    1788          $imstestcase->set_xml_file(false, array($course));
    1789  
    1790          // Capture the event.
    1791          $sink = $this->redirectEvents();
    1792          $imstestcase->imsplugin->cron();
    1793          $events = $sink->get_events();
    1794          $sink->close();
    1795          $event = null;
    1796          foreach ($events as $eventinfo) {
    1797              if ($eventinfo instanceof \core\event\course_created ) {
    1798                  $event = $eventinfo;
    1799                  break;
    1800              }
    1801          }
    1802  
    1803          // Validate the event triggered is \core\event\course_created. There is no need to validate the other values
    1804          // as they have already been validated in the previous steps. Here we only want to make sure that when the
    1805          // imsenterprise plugin creates a course an event is triggered.
    1806          $this->assertInstanceOf('\core\event\course_created', $event);
    1807          $this->assertEventContextNotUsed($event);
    1808      }
    1809  
    1810      /**
    1811       * Test that triggering a course_updated event works as expected.
    1812       */
    1813      public function test_course_updated_event() {
    1814          global $DB;
    1815  
    1816          $this->resetAfterTest();
    1817  
    1818          // Create a course.
    1819          $course = $this->getDataGenerator()->create_course();
    1820  
    1821          // Create a category we are going to move this course to.
    1822          $category = $this->getDataGenerator()->create_category();
    1823  
    1824          // Create a hidden category we are going to move this course to.
    1825          $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
    1826  
    1827          // Update course and catch course_updated event.
    1828          $sink = $this->redirectEvents();
    1829          update_course($course);
    1830          $events = $sink->get_events();
    1831          $sink->close();
    1832  
    1833          // Get updated course information from the DB.
    1834          $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
    1835          // Validate event.
    1836          $event = array_shift($events);
    1837          $this->assertInstanceOf('\core\event\course_updated', $event);
    1838          $this->assertEquals('course', $event->objecttable);
    1839          $this->assertEquals($updatedcourse->id, $event->objectid);
    1840          $this->assertEquals(context_course::instance($course->id), $event->get_context());
    1841          $url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
    1842          $this->assertEquals($url, $event->get_url());
    1843          $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
    1844          $this->assertEquals('course_updated', $event->get_legacy_eventname());
    1845          $this->assertEventLegacyData($updatedcourse, $event);
    1846          $expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
    1847          $this->assertEventLegacyLogData($expectedlog, $event);
    1848  
    1849          // Move course and catch course_updated event.
    1850          $sink = $this->redirectEvents();
    1851          move_courses(array($course->id), $category->id);
    1852          $events = $sink->get_events();
    1853          $sink->close();
    1854  
    1855          // Return the moved course information from the DB.
    1856          $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
    1857          // Validate event.
    1858          $event = array_shift($events);
    1859          $this->assertInstanceOf('\core\event\course_updated', $event);
    1860          $this->assertEquals('course', $event->objecttable);
    1861          $this->assertEquals($movedcourse->id, $event->objectid);
    1862          $this->assertEquals(context_course::instance($course->id), $event->get_context());
    1863          $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
    1864          $this->assertEquals('course_updated', $event->get_legacy_eventname());
    1865          $this->assertEventLegacyData($movedcourse, $event);
    1866          $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
    1867          $this->assertEventLegacyLogData($expectedlog, $event);
    1868  
    1869          // Move course to hidden category and catch course_updated event.
    1870          $sink = $this->redirectEvents();
    1871          move_courses(array($course->id), $categoryhidden->id);
    1872          $events = $sink->get_events();
    1873          $sink->close();
    1874  
    1875          // Return the moved course information from the DB.
    1876          $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
    1877          // Validate event.
    1878          $event = array_shift($events);
    1879          $this->assertInstanceOf('\core\event\course_updated', $event);
    1880          $this->assertEquals('course', $event->objecttable);
    1881          $this->assertEquals($movedcoursehidden->id, $event->objectid);
    1882          $this->assertEquals(context_course::instance($course->id), $event->get_context());
    1883          $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
    1884          $this->assertEquals('course_updated', $event->get_legacy_eventname());
    1885          $this->assertEventLegacyData($movedcoursehidden, $event);
    1886          $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
    1887          $this->assertEventLegacyLogData($expectedlog, $event);
    1888          $this->assertEventContextNotUsed($event);
    1889      }
    1890  
    1891      /**
    1892       * Test that triggering a course_updated event logs changes.
    1893       */
    1894      public function test_course_updated_event_with_changes() {
    1895          global $DB;
    1896  
    1897          $this->resetAfterTest();
    1898  
    1899          // Create a course.
    1900          $course = $this->getDataGenerator()->create_course((object)['visible' => 1]);
    1901  
    1902          $editedcourse = $DB->get_record('course', ['id' => $course->id]);
    1903          $editedcourse->visible = 0;
    1904  
    1905          // Update course and catch course_updated event.
    1906          $sink = $this->redirectEvents();
    1907          update_course($editedcourse);
    1908          $events = $sink->get_events();
    1909          $sink->close();
    1910  
    1911          $event = array_shift($events);
    1912          $this->assertInstanceOf('\core\event\course_updated', $event);
    1913          $otherdata = [
    1914              'shortname' => $course->shortname,
    1915              'fullname' => $course->fullname,
    1916              'updatedfields' => [
    1917                  'visible' => 0
    1918              ]
    1919          ];
    1920          $this->assertEquals($otherdata, $event->other);
    1921  
    1922      }
    1923  
    1924      /**
    1925       * Test that triggering a course_deleted event works as expected.
    1926       */
    1927      public function test_course_deleted_event() {
    1928          $this->resetAfterTest();
    1929  
    1930          // Create the course.
    1931          $course = $this->getDataGenerator()->create_course();
    1932  
    1933          // Save the course context before we delete the course.
    1934          $coursecontext = context_course::instance($course->id);
    1935  
    1936          // Catch the update event.
    1937          $sink = $this->redirectEvents();
    1938  
    1939          // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
    1940          // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
    1941          // so use ob_start and ob_end_clean to prevent this.
    1942          ob_start();
    1943          delete_course($course);
    1944          ob_end_clean();
    1945  
    1946          // Capture the event.
    1947          $events = $sink->get_events();
    1948          $sink->close();
    1949  
    1950          // Validate the event.
    1951          $event = array_pop($events);
    1952          $this->assertInstanceOf('\core\event\course_deleted', $event);
    1953          $this->assertEquals('course', $event->objecttable);
    1954          $this->assertEquals($course->id, $event->objectid);
    1955          $this->assertEquals($coursecontext->id, $event->contextid);
    1956          $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
    1957          $this->assertEquals('course_deleted', $event->get_legacy_eventname());
    1958          $eventdata = $event->get_data();
    1959          $this->assertSame($course->idnumber, $eventdata['other']['idnumber']);
    1960          $this->assertSame($course->fullname, $eventdata['other']['fullname']);
    1961          $this->assertSame($course->shortname, $eventdata['other']['shortname']);
    1962  
    1963          // The legacy data also passed the context in the course object and substitutes timemodified with the current date.
    1964          $expectedlegacy = clone($course);
    1965          $expectedlegacy->context = $coursecontext;
    1966          $expectedlegacy->timemodified = $event->timecreated;
    1967          $this->assertEventLegacyData($expectedlegacy, $event);
    1968  
    1969          // Validate legacy log data.
    1970          $expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
    1971          $this->assertEventLegacyLogData($expectedlog, $event);
    1972          $this->assertEventContextNotUsed($event);
    1973      }
    1974  
    1975      /**
    1976       * Test that triggering a course_content_deleted event works as expected.
    1977       */
    1978      public function test_course_content_deleted_event() {
    1979          global $DB;
    1980  
    1981          $this->resetAfterTest();
    1982  
    1983          // Create the course.
    1984          $course = $this->getDataGenerator()->create_course();
    1985  
    1986          // Get the course from the DB. The data generator adds some extra properties, such as
    1987          // numsections, to the course object which will fail the assertions later on.
    1988          $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
    1989  
    1990          // Save the course context before we delete the course.
    1991          $coursecontext = context_course::instance($course->id);
    1992  
    1993          // Catch the update event.
    1994          $sink = $this->redirectEvents();
    1995  
    1996          remove_course_contents($course->id, false);
    1997  
    1998          // Capture the event.
    1999          $events = $sink->get_events();
    2000          $sink->close();
    2001  
    2002          // Validate the event.
    2003          $event = array_pop($events);
    2004          $this->assertInstanceOf('\core\event\course_content_deleted', $event);
    2005          $this->assertEquals('course', $event->objecttable);
    2006          $this->assertEquals($course->id, $event->objectid);
    2007          $this->assertEquals($coursecontext->id, $event->contextid);
    2008          $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
    2009          $this->assertEquals('course_content_removed', $event->get_legacy_eventname());
    2010          // The legacy data also passed the context and options in the course object.
    2011          $course->context = $coursecontext;
    2012          $course->options = array();
    2013          $this->assertEventLegacyData($course, $event);
    2014          $this->assertEventContextNotUsed($event);
    2015      }
    2016  
    2017      /**
    2018       * Test that triggering a course_category_deleted event works as expected.
    2019       */
    2020      public function test_course_category_deleted_event() {
    2021          $this->resetAfterTest();
    2022  
    2023          // Create a category.
    2024          $category = $this->getDataGenerator()->create_category();
    2025  
    2026          // Save the original record/context before it is deleted.
    2027          $categoryrecord = $category->get_db_record();
    2028          $categorycontext = context_coursecat::instance($category->id);
    2029  
    2030          // Catch the update event.
    2031          $sink = $this->redirectEvents();
    2032  
    2033          // Delete the category.
    2034          $category->delete_full();
    2035  
    2036          // Capture the event.
    2037          $events = $sink->get_events();
    2038          $sink->close();
    2039  
    2040          // Validate the event.
    2041          $event = $events[0];
    2042          $this->assertInstanceOf('\core\event\course_category_deleted', $event);
    2043          $this->assertEquals('course_categories', $event->objecttable);
    2044          $this->assertEquals($category->id, $event->objectid);
    2045          $this->assertEquals($categorycontext->id, $event->contextid);
    2046          $this->assertEquals([
    2047              'name' => $category->name,
    2048          ], $event->other);
    2049          $this->assertEquals($categoryrecord, $event->get_record_snapshot($event->objecttable, $event->objectid));
    2050          $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
    2051          $this->assertEquals(null, $event->get_url());
    2052          $this->assertEventLegacyData($category, $event);
    2053          $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
    2054          $this->assertEventLegacyLogData($expectedlog, $event);
    2055  
    2056          // Create two categories.
    2057          $category = $this->getDataGenerator()->create_category();
    2058          $category2 = $this->getDataGenerator()->create_category();
    2059  
    2060          // Save the original record/context before it is moved and then deleted.
    2061          $category2record = $category2->get_db_record();
    2062          $category2context = context_coursecat::instance($category2->id);
    2063  
    2064          // Catch the update event.
    2065          $sink = $this->redirectEvents();
    2066  
    2067          // Move the category.
    2068          $category2->delete_move($category->id);
    2069  
    2070          // Capture the event.
    2071          $events = $sink->get_events();
    2072          $sink->close();
    2073  
    2074          // Validate the event.
    2075          $event = $events[0];
    2076          $this->assertInstanceOf('\core\event\course_category_deleted', $event);
    2077          $this->assertEquals('course_categories', $event->objecttable);
    2078          $this->assertEquals($category2->id, $event->objectid);
    2079          $this->assertEquals($category2context->id, $event->contextid);
    2080          $this->assertEquals([
    2081              'name' => $category2->name,
    2082              'contentmovedcategoryid' => $category->id,
    2083          ], $event->other);
    2084          $this->assertEquals($category2record, $event->get_record_snapshot($event->objecttable, $event->objectid));
    2085          $this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
    2086          $this->assertEventLegacyData($category2, $event);
    2087          $expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
    2088          $this->assertEventLegacyLogData($expectedlog, $event);
    2089          $this->assertEventContextNotUsed($event);
    2090      }
    2091  
    2092      /**
    2093       * Test that triggering a course_backup_created event works as expected.
    2094       */
    2095      public function test_course_backup_created_event() {
    2096          global $CFG;
    2097  
    2098          // Get the necessary files to perform backup and restore.
    2099          require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
    2100          require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
    2101  
    2102          $this->resetAfterTest();
    2103  
    2104          // Set to admin user.
    2105          $this->setAdminUser();
    2106  
    2107          // The user id is going to be 2 since we are the admin user.
    2108          $userid = 2;
    2109  
    2110          // Create a course.
    2111          $course = $this->getDataGenerator()->create_course();
    2112  
    2113          // Create backup file and save it to the backup location.
    2114          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
    2115              backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
    2116          $sink = $this->redirectEvents();
    2117          $bc->execute_plan();
    2118  
    2119          // Capture the event.
    2120          $events = $sink->get_events();
    2121          $sink->close();
    2122  
    2123          // Validate the event.
    2124          $event = array_pop($events);
    2125          $this->assertInstanceOf('\core\event\course_backup_created', $event);
    2126          $this->assertEquals('course', $event->objecttable);
    2127          $this->assertEquals($bc->get_courseid(), $event->objectid);
    2128          $this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid);
    2129  
    2130          $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
    2131          $this->assertEquals($url, $event->get_url());
    2132          $this->assertEventContextNotUsed($event);
    2133  
    2134          // Destroy the resource controller since we are done using it.
    2135          $bc->destroy();
    2136      }
    2137  
    2138      /**
    2139       * Test that triggering a course_restored event works as expected.
    2140       */
    2141      public function test_course_restored_event() {
    2142          global $CFG;
    2143  
    2144          // Get the necessary files to perform backup and restore.
    2145          require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
    2146          require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
    2147  
    2148          $this->resetAfterTest();
    2149  
    2150          // Set to admin user.
    2151          $this->setAdminUser();
    2152  
    2153          // The user id is going to be 2 since we are the admin user.
    2154          $userid = 2;
    2155  
    2156          // Create a course.
    2157          $course = $this->getDataGenerator()->create_course();
    2158  
    2159          // Create backup file and save it to the backup location.
    2160          $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
    2161              backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
    2162          $bc->execute_plan();
    2163          $results = $bc->get_results();
    2164          $file = $results['backup_destination'];
    2165          $fp = get_file_packer('application/vnd.moodle.backup');
    2166          $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
    2167          $file->extract_to_pathname($fp, $filepath);
    2168          $bc->destroy();
    2169  
    2170          // Now we want to catch the restore course event.
    2171          $sink = $this->redirectEvents();
    2172  
    2173          // Now restore the course to trigger the event.
    2174          $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
    2175              backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
    2176          $rc->execute_precheck();
    2177          $rc->execute_plan();
    2178  
    2179          // Capture the event.
    2180          $events = $sink->get_events();
    2181          $sink->close();
    2182  
    2183          // Validate the event.
    2184          $event = array_pop($events);
    2185          $this->assertInstanceOf('\core\event\course_restored', $event);
    2186          $this->assertEquals('course', $event->objecttable);
    2187          $this->assertEquals($rc->get_courseid(), $event->objectid);
    2188          $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
    2189          $this->assertEquals('course_restored', $event->get_legacy_eventname());
    2190          $legacydata = (object) array(
    2191              'courseid' => $rc->get_courseid(),
    2192              'userid' => $rc->get_userid(),
    2193              'type' => $rc->get_type(),
    2194              'target' => $rc->get_target(),
    2195              'mode' => $rc->get_mode(),
    2196              'operation' => $rc->get_operation(),
    2197              'samesite' => $rc->is_samesite()
    2198          );
    2199          $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
    2200          $this->assertEquals($url, $event->get_url());
    2201          $this->assertEventLegacyData($legacydata, $event);
    2202          $this->assertEventContextNotUsed($event);
    2203  
    2204          // Destroy the resource controller since we are done using it.
    2205          $rc->destroy();
    2206      }
    2207  
    2208      /**
    2209       * Test that triggering a course_section_updated event works as expected.
    2210       */
    2211      public function test_course_section_updated_event() {
    2212          global $DB;
    2213  
    2214          $this->resetAfterTest();
    2215  
    2216          // Create the course with sections.
    2217          $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
    2218          $sections = $DB->get_records('course_sections', array('course' => $course->id));
    2219  
    2220          $coursecontext = context_course::instance($course->id);
    2221  
    2222          $section = array_pop($sections);
    2223          $section->name = 'Test section';
    2224          $section->summary = 'Test section summary';
    2225          $DB->update_record('course_sections', $section);
    2226  
    2227          // Trigger an event for course section update.
    2228          $event = \core\event\course_section_updated::create(
    2229                  array(
    2230                      'objectid' => $section->id,
    2231                      'courseid' => $course->id,
    2232                      'context' => context_course::instance($course->id),
    2233                      'other' => array(
    2234                          'sectionnum' => $section->section
    2235                      )
    2236                  )
    2237              );
    2238          $event->add_record_snapshot('course_sections', $section);
    2239          // Trigger and catch event.
    2240          $sink = $this->redirectEvents();
    2241          $event->trigger();
    2242          $events = $sink->get_events();
    2243          $sink->close();
    2244  
    2245          // Validate the event.
    2246          $event = $events[0];
    2247          $this->assertInstanceOf('\core\event\course_section_updated', $event);
    2248          $this->assertEquals('course_sections', $event->objecttable);
    2249          $this->assertEquals($section->id, $event->objectid);
    2250          $this->assertEquals($course->id, $event->courseid);
    2251          $this->assertEquals($coursecontext->id, $event->contextid);
    2252          $this->assertEquals($section->section, $event->other['sectionnum']);
    2253          $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'";
    2254          $this->assertEquals($expecteddesc, $event->get_description());
    2255          $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
    2256          $this->assertEquals($url, $event->get_url());
    2257          $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
    2258          $id = $section->id;
    2259          $sectionnum = $section->section;
    2260          $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
    2261          $this->assertEventLegacyLogData($expectedlegacydata, $event);
    2262          $this->assertEventContextNotUsed($event);
    2263      }
    2264  
    2265      /**
    2266       * Test that triggering a course_section_deleted event works as expected.
    2267       */
    2268      public function test_course_section_deleted_event() {
    2269          global $USER, $DB;
    2270          $this->resetAfterTest();
    2271          $sink = $this->redirectEvents();
    2272  
    2273          // Create the course with sections.
    2274          $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
    2275          $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section');
    2276          $coursecontext = context_course::instance($course->id);
    2277          $section = array_pop($sections);
    2278          course_delete_section($course, $section);
    2279          $events = $sink->get_events();
    2280          $event = array_pop($events); // Delete section event.
    2281          $sink->close();
    2282  
    2283          // Validate event data.
    2284          $this->assertInstanceOf('\core\event\course_section_deleted', $event);
    2285          $this->assertEquals('course_sections', $event->objecttable);
    2286          $this->assertEquals($section->id, $event->objectid);
    2287          $this->assertEquals($course->id, $event->courseid);
    2288          $this->assertEquals($coursecontext->id, $event->contextid);
    2289          $this->assertEquals($section->section, $event->other['sectionnum']);
    2290          $expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " .
    2291                  "(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'";
    2292          $this->assertEquals($expecteddesc, $event->get_description());
    2293          $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
    2294          $this->assertNull($event->get_url());
    2295  
    2296          // Test legacy data.
    2297          $sectionnum = $section->section;
    2298          $expectedlegacydata = array($course->id, "course", "delete section", 'view.php?id=' . $course->id, $sectionnum);
    2299          $this->assertEventLegacyLogData($expectedlegacydata, $event);
    2300          $this->assertEventContextNotUsed($event);
    2301      }
    2302  
    2303      public function test_course_integrity_check() {
    2304          global $DB;
    2305  
    2306          $this->resetAfterTest(true);
    2307          $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
    2308             array('createsections'=>true));
    2309  
    2310          $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
    2311                  array('section' => 0));
    2312          $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
    2313                  array('section' => 0));
    2314          $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
    2315                  array('section' => 0));
    2316          $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
    2317  
    2318          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2319          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2320          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2321          $this->assertEquals($correctseq, $section0->sequence);
    2322          $this->assertEmpty($section1->sequence);
    2323          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2324          $this->assertEquals($section0->id, $cms[$page->cmid]->section);
    2325          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2326          $this->assertEmpty(course_integrity_check($course->id));
    2327  
    2328          // Now let's make manual change in DB and let course_integrity_check() fix it:
    2329  
    2330          // 1. Module appears twice in one section.
    2331          $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
    2332          $this->assertEquals(
    2333                  array('Failed integrity check for course ['. $course->id.
    2334                  ']. Sequence for course section ['. $section0->id. '] is "'.
    2335                  $section0->sequence. ','. $page->cmid. '", must be "'.
    2336                  $section0->sequence. '"'),
    2337                  course_integrity_check($course->id));
    2338          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2339          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2340          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2341          $this->assertEquals($correctseq, $section0->sequence);
    2342          $this->assertEmpty($section1->sequence);
    2343          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2344          $this->assertEquals($section0->id, $cms[$page->cmid]->section);
    2345          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2346  
    2347          // 2. Module appears in two sections (last section wins).
    2348          $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
    2349          // First message about double mentioning in sequence, second message about wrong section field for $page.
    2350          $this->assertEquals(array(
    2351              'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
    2352              '] must be removed from sequence of section ['. $section0->id.
    2353              '] because it is also present in sequence of section ['. $section1->id. ']',
    2354              'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
    2355              '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
    2356                  course_integrity_check($course->id));
    2357          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2358          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2359          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2360          $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
    2361          $this->assertEquals(''. $page->cmid, $section1->sequence);
    2362          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2363          $this->assertEquals($section1->id, $cms[$page->cmid]->section);
    2364          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2365  
    2366          // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
    2367          $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
    2368          $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
    2369          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2370          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2371          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2372          $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
    2373          $this->assertEmpty($section1->sequence);
    2374          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2375          $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
    2376          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2377  
    2378          // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
    2379          $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
    2380                  $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
    2381                  course_integrity_check($course->id, null, null, true)); // Error!
    2382          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2383          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2384          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2385          $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
    2386          $this->assertEquals(''. $page->cmid, $section1->sequence);  // Yay, module added to section.
    2387          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2388          $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
    2389          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2390  
    2391          // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
    2392          $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
    2393          $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
    2394          $this->assertEquals(array(
    2395              'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
    2396              '] is missing from sequence of section ['. $section0->id. ']',
    2397              'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
    2398              '] points to section [8765] instead of ['. $section0->id. ']'),
    2399                  course_integrity_check($course->id, null, null, true));
    2400          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2401          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2402          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2403          $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
    2404          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2405          $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
    2406          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2407  
    2408          // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
    2409          $DB->delete_records('course_modules', array('id' => $page->cmid));
    2410          $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
    2411                  $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
    2412                  course_integrity_check($course->id, null, null, true));
    2413          $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
    2414          $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
    2415          $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
    2416          $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
    2417          $this->assertEmpty($section1->sequence);
    2418          $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
    2419          $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
    2420          $this->assertEquals(2, count($cms));
    2421      }
    2422  
    2423      /**
    2424       * Tests for event related to course module creation.
    2425       */
    2426      public function test_course_module_created_event() {
    2427          global $USER;
    2428  
    2429          $this->resetAfterTest();
    2430          $this->setAdminUser();
    2431  
    2432          // Create an assign module.
    2433          $sink = $this->redirectEvents();
    2434          $course = $this->getDataGenerator()->create_course();
    2435          $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
    2436          $events = $sink->get_events();
    2437          $eventscount = 0;
    2438  
    2439          // Validate event data.
    2440          foreach ($events as $event) {
    2441              if ($event instanceof \core\event\course_module_created) {
    2442                  $eventscount++;
    2443  
    2444                  $this->assertEquals($module->cmid, $event->objectid);
    2445                  $this->assertEquals($USER->id, $event->userid);
    2446                  $this->assertEquals('course_modules', $event->objecttable);
    2447                  $url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid));
    2448                  $this->assertEquals($url, $event->get_url());
    2449  
    2450                  // Test legacy data.
    2451                  $this->assertSame('mod_created', $event->get_legacy_eventname());
    2452                  $eventdata = new stdClass();
    2453                  $eventdata->modulename = 'assign';
    2454                  $eventdata->name       = $module->name;
    2455                  $eventdata->cmid       = $module->cmid;
    2456                  $eventdata->courseid   = $module->course;
    2457                  $eventdata->userid     = $USER->id;
    2458                  $this->assertEventLegacyData($eventdata, $event);
    2459  
    2460                  $arr = array(
    2461                      array($module->course, "course", "add mod", "../mod/assign/view.php?id=$module->cmid", "assign $module->id"),
    2462                      array($module->course, "assign", "add", "view.php?id=$module->cmid", $module->id, $module->cmid)
    2463                  );
    2464                  $this->assertEventLegacyLogData($arr, $event);
    2465                  $this->assertEventContextNotUsed($event);
    2466              }
    2467          }
    2468          // Only one \core\event\course_module_created event should be triggered.
    2469          $this->assertEquals(1, $eventscount);
    2470  
    2471          // Let us see if duplicating an activity results in a nice course module created event.
    2472          $sink->clear();
    2473          $course = get_course($module->course);
    2474          $cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST);
    2475          $newcm = duplicate_module($course, $cm);
    2476          $events = $sink->get_events();
    2477          $eventscount = 0;
    2478          $sink->close();
    2479  
    2480          foreach ($events as $event) {
    2481              if ($event instanceof \core\event\course_module_created) {
    2482                  $eventscount++;
    2483                  // Validate event data.
    2484                  $this->assertInstanceOf('\core\event\course_module_created', $event);
    2485                  $this->assertEquals($newcm->id, $event->objectid);
    2486                  $this->assertEquals($USER->id, $event->userid);
    2487                  $this->assertEquals($course->id, $event->courseid);
    2488                  $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id));
    2489                  $this->assertEquals($url, $event->get_url());
    2490              }
    2491          }
    2492  
    2493          // Only one \core\event\course_module_created event should be triggered.
    2494          $this->assertEquals(1, $eventscount);
    2495      }
    2496  
    2497      /**
    2498       * Tests for event validations related to course module creation.
    2499       */
    2500      public function test_course_module_created_event_exceptions() {
    2501  
    2502          $this->resetAfterTest();
    2503  
    2504          // Generate data.
    2505          $modinfo = $this->create_specific_module_test('assign');
    2506          $context = context_module::instance($modinfo->coursemodule);
    2507  
    2508          // Test not setting instanceid.
    2509          try {
    2510              $event = \core\event\course_module_created::create(array(
    2511                  'courseid' => $modinfo->course,
    2512                  'context'  => $context,
    2513                  'objectid' => $modinfo->coursemodule,
    2514                  'other'    => array(
    2515                      'modulename' => 'assign',
    2516                      'name'       => 'My assignment',
    2517                  )
    2518              ));
    2519              $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
    2520                      other['instanceid']");
    2521          } catch (coding_exception $e) {
    2522              $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
    2523          }
    2524  
    2525          // Test not setting modulename.
    2526          try {
    2527              $event = \core\event\course_module_created::create(array(
    2528                  'courseid' => $modinfo->course,
    2529                  'context'  => $context,
    2530                  'objectid' => $modinfo->coursemodule,
    2531                  'other'    => array(
    2532                      'instanceid' => $modinfo->instance,
    2533                      'name'       => 'My assignment',
    2534                  )
    2535              ));
    2536              $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
    2537                      other['modulename']");
    2538          } catch (coding_exception $e) {
    2539              $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
    2540          }
    2541  
    2542          // Test not setting name.
    2543  
    2544          try {
    2545              $event = \core\event\course_module_created::create(array(
    2546                  'courseid' => $modinfo->course,
    2547                  'context'  => $context,
    2548                  'objectid' => $modinfo->coursemodule,
    2549                  'other'    => array(
    2550                      'modulename' => 'assign',
    2551                      'instanceid' => $modinfo->instance,
    2552                  )
    2553              ));
    2554              $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
    2555                      other['name']");
    2556          } catch (coding_exception $e) {
    2557              $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
    2558          }
    2559  
    2560      }
    2561  
    2562      /**
    2563       * Tests for event related to course module updates.
    2564       */
    2565      public function test_course_module_updated_event() {
    2566          global $USER, $DB;
    2567          $this->resetAfterTest();
    2568  
    2569          // Update a forum module.
    2570          $sink = $this->redirectEvents();
    2571          $modinfo = $this->update_specific_module_test('forum');
    2572          $events = $sink->get_events();
    2573          $eventscount = 0;
    2574          $sink->close();
    2575  
    2576          $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
    2577          $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
    2578  
    2579          // Validate event data.
    2580          foreach ($events as $event) {
    2581              if ($event instanceof \core\event\course_module_updated) {
    2582                  $eventscount++;
    2583  
    2584                  $this->assertEquals($cm->id, $event->objectid);
    2585                  $this->assertEquals($USER->id, $event->userid);
    2586                  $this->assertEquals('course_modules', $event->objecttable);
    2587                  $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id));
    2588                  $this->assertEquals($url, $event->get_url());
    2589  
    2590                  // Test legacy data.
    2591                  $this->assertSame('mod_updated', $event->get_legacy_eventname());
    2592                  $eventdata = new stdClass();
    2593                  $eventdata->modulename = 'forum';
    2594                  $eventdata->name       = $mod->name;
    2595                  $eventdata->cmid       = $cm->id;
    2596                  $eventdata->courseid   = $cm->course;
    2597                  $eventdata->userid     = $USER->id;
    2598                  $this->assertEventLegacyData($eventdata, $event);
    2599  
    2600                  $arr = array(
    2601                      array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"),
    2602                      array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
    2603                  );
    2604                  $this->assertEventLegacyLogData($arr, $event);
    2605                  $this->assertEventContextNotUsed($event);
    2606              }
    2607          }
    2608  
    2609          // Only one \core\event\course_module_updated event should be triggered.
    2610          $this->assertEquals(1, $eventscount);
    2611      }
    2612  
    2613      /**
    2614       * Tests for create_from_cm method.
    2615       */
    2616      public function test_course_module_create_from_cm() {
    2617          $this->resetAfterTest();
    2618          $this->setAdminUser();
    2619  
    2620          // Create course and modules.
    2621          $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
    2622  
    2623          // Generate an assignment.
    2624          $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
    2625  
    2626          // Get the module context.
    2627          $modcontext =