Search moodle.org's
Developer Documentation

See Release Notes

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