Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * External calendar functions unit tests
  19   *
  20   * @package    core_calendar
  21   * @category   external
  22   * @copyright  2012 Ankit Agarwal
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  global $CFG;
  29  
  30  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  31  
  32  /**
  33   * External course functions unit tests
  34   *
  35   * @package    core_calendar
  36   * @category   external
  37   * @copyright  2012 Ankit Agarwal
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   * @since Moodle 2.5
  40   */
  41  class core_calendar_externallib_testcase extends externallib_advanced_testcase {
  42  
  43      /**
  44       * Tests set up
  45       */
  46      protected function setUp(): void {
  47          global $CFG;
  48          require_once($CFG->dirroot . '/calendar/externallib.php');
  49      }
  50  
  51      /** Create calendar events or update them
  52       * Set $prop->id, if you want to do an update instead of creating an new event
  53       *
  54       * @param string $name        Event title
  55       * @param int    $userid      User id
  56       * @param string $type        Event type
  57       * @param int    $repeats     Number of repeated events to create
  58       * @param int    $timestart   Time stamp of the event start
  59       * @param mixed  $prop        List of event properties as array or object
  60       * @return mixed              Event object or false;
  61       * @since Moodle 2.5
  62       */
  63  
  64      public static function create_calendar_event($name, $userid = 0, $type = 'user', $repeats = 0, $timestart  = null, $prop = null) {
  65          global $CFG, $DB, $SITE;
  66  
  67          require_once("$CFG->dirroot/calendar/lib.php");
  68          if (!empty($prop)) {
  69              if (is_array($prop)) {
  70                  $prop = (object)$prop;
  71              }
  72          } else {
  73              $prop = new stdClass();
  74          }
  75          $prop->name = $name;
  76          if (empty($prop->eventtype)) {
  77              $prop->eventtype = $type;
  78          }
  79          if (empty($prop->repeats)) {
  80              $prop->repeats = $repeats;
  81          }
  82          if (empty($prop->timestart)) {
  83              $prop->timestart = time();
  84          }
  85          if (empty($prop->timeduration)) {
  86              $prop->timeduration = 0;
  87          }
  88          if (empty($prop->timesort)) {
  89              $prop->timesort = 0;
  90          }
  91          if (empty($prop->type)) {
  92              $prop->type = CALENDAR_EVENT_TYPE_STANDARD;
  93          }
  94          if (empty($prop->repeats)) {
  95              $prop->repeat = 0;
  96          } else {
  97              $prop->repeat = 1;
  98          }
  99          if (empty($prop->userid)) {
 100              if (!empty($userid)) {
 101                  $prop->userid = $userid;
 102              } else {
 103                  $prop->userid = 0;
 104              }
 105          }
 106          if (!isset($prop->courseid)) {
 107              // Set a default value of the event's course ID field.
 108              if ($type === 'user') {
 109                  // If it's a user event, course ID should be zero.
 110                  $prop->courseid = 0;
 111              } else {
 112                  // Otherwise, default to the site ID.
 113                  $prop->courseid = $SITE->id;
 114              }
 115          }
 116  
 117          // Determine event priority.
 118          if ($prop->courseid == 0 && isset($prop->groupid) && $prop->groupid == 0 && !empty($prop->userid)) {
 119              // User override event.
 120              $prop->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
 121          } else if ($prop->courseid != $SITE->id && !empty($prop->groupid)) {
 122              // Group override event.
 123              $priorityparams = ['courseid' => $prop->courseid, 'groupid' => $prop->groupid];
 124              // Group override event with the highest priority.
 125              $groupevents = $DB->get_records('event', $priorityparams, 'priority DESC', 'id, priority', 0, 1);
 126              $priority = 1;
 127              if (!empty($groupevents)) {
 128                  $event = reset($groupevents);
 129                  if (!empty($event->priority)) {
 130                      $priority = $event->priority + 1;
 131                  }
 132              }
 133              $prop->priority = $priority;
 134          }
 135  
 136          $event = new calendar_event($prop);
 137          return $event->create($prop);
 138      }
 139  
 140      public function test_create_calendar_events () {
 141          global $DB, $USER;
 142  
 143          $this->setAdminUser();
 144          $this->resetAfterTest();
 145          $prevcount = count($DB->get_records("event"));
 146  
 147          // Create a few events and do asserts.
 148          $this->create_calendar_event('test', $USER->id);
 149          $where = $DB->sql_compare_text('name') ." = ?";
 150          $count = count($DB->get_records_select("event", $where, array('test')));
 151          $this->assertEquals(1, $count);
 152          $aftercount = count($DB->get_records("event"));
 153          $this->assertEquals($prevcount + 1, $aftercount);
 154  
 155          $this->create_calendar_event('user', $USER->id, 'user', 3);
 156          $where = $DB->sql_compare_text('name') ." = ?";
 157          $count = count($DB->get_records_select("event", $where, array('user')));
 158  
 159          $this->assertEquals(3, $count);
 160          $aftercount = count($DB->get_records("event"));
 161          $this->assertEquals($prevcount + 4, $aftercount);
 162  
 163      }
 164  
 165      /**
 166       * Test delete_calendar_events
 167       */
 168      public function test_delete_calendar_events() {
 169          global $DB, $USER;
 170  
 171          $this->resetAfterTest(true);
 172          $this->setAdminUser();
 173  
 174          // Create a few stuff to test with.
 175          $user = $this->getDataGenerator()->create_user();
 176          $course = $this->getDataGenerator()->create_course();
 177          $record = new stdClass();
 178          $record->courseid = $course->id;
 179          $group = $this->getDataGenerator()->create_group($record);
 180  
 181          $notdeletedcount = $DB->count_records('event');
 182  
 183          // Let's create a few events.
 184          $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
 185          $record = new stdClass();
 186          $record->courseid = $course->id;
 187          $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record);
 188          $userevent = $this->create_calendar_event('user', $USER->id);
 189          $record = new stdClass();
 190          $record->courseid = $course->id;
 191          $record->groupid = $group->id;
 192          $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
 193  
 194          // Now lets try to delete stuff with proper rights.
 195          $events = array(
 196                  array('eventid' => $siteevent->id, 'repeat' => 0),
 197                  array('eventid' => $courseevent->id, 'repeat' => 1),
 198                  array('eventid' => $userevent->id, 'repeat' => 0),
 199                  array('eventid' => $groupevent->id, 'repeat' => 0)
 200                  );
 201          core_calendar_external::delete_calendar_events($events);
 202  
 203          // Check to see if things were deleted properly.
 204          $deletedcount = $DB->count_records('event');
 205          $this->assertEquals($notdeletedcount, $deletedcount);
 206  
 207          // Let's create a few events.
 208          $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
 209          $record = new stdClass();
 210          $record->courseid = $course->id;
 211          $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 3, time(), $record);
 212          $userevent = $this->create_calendar_event('user', $user->id);
 213          $record = new stdClass();
 214          $record->courseid = $course->id;
 215          $record->groupid = $group->id;
 216          $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
 217  
 218          $this->setuser($user);
 219          $sitecontext = context_system::instance();
 220          $coursecontext = context_course::instance($course->id);
 221          $usercontext = context_user::instance($user->id);
 222          $role = $DB->get_record('role', array('shortname' => 'student'));
 223          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
 224  
 225          // Remove all caps.
 226          $this->unassignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
 227          $this->unassignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
 228          $this->unassignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
 229          $this->unassignUserCapability('moodle/calendar:manageownentries', $usercontext->id, $role->id);
 230  
 231          // Assign proper caps and attempt delete.
 232           $this->assignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
 233           $events = array(
 234                  array('eventid' => $siteevent->id, 'repeat' => 0),
 235                  );
 236          core_calendar_external::delete_calendar_events($events);
 237          $deletedcount = $DB->count_records('event');
 238          $count = $notdeletedcount+5;
 239          $this->assertEquals($count, $deletedcount);
 240  
 241           $this->assignUserCapability('moodle/calendar:manageentries', $sitecontext->id, $role->id);
 242           $events = array(
 243                  array('eventid' => $courseevent->id, 'repeat' => 0),
 244                  );
 245          core_calendar_external::delete_calendar_events($events);
 246          $deletedcount = $DB->count_records('event');
 247          $count = $notdeletedcount+4;
 248          $this->assertEquals($count, $deletedcount);
 249  
 250           $this->assignUserCapability('moodle/calendar:manageownentries', $usercontext->id, $role->id);
 251           $events = array(
 252                  array('eventid' => $userevent->id, 'repeat' => 0),
 253                  );
 254          core_calendar_external::delete_calendar_events($events);
 255          $deletedcount = $DB->count_records('event');
 256          $count = $notdeletedcount+3;
 257          $this->assertEquals($count, $deletedcount);
 258  
 259           $this->assignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
 260           $events = array(
 261                  array('eventid' => $groupevent->id, 'repeat' => 0),
 262                  );
 263          core_calendar_external::delete_calendar_events($events);
 264          $deletedcount = $DB->count_records('event');
 265          $count = $notdeletedcount+2;
 266          $this->assertEquals($count, $deletedcount);
 267  
 268          $notdeletedcount = $deletedcount;
 269  
 270          // Let us try deleting without caps.
 271  
 272          $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
 273          $record = new stdClass();
 274          $record->courseid = $course->id;
 275          $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 3, time(), $record);
 276          $userevent = $this->create_calendar_event('user', $USER->id);
 277          $record = new stdClass();
 278          $record->courseid = $course->id;
 279          $record->groupid = $group->id;
 280          $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
 281  
 282          $this->setGuestUser();
 283  
 284          $events = array(
 285              array('eventid' => $siteevent->id, 'repeat' => 0),
 286              array('eventid' => $courseevent->id, 'repeat' => 0),
 287              array('eventid' => $userevent->id, 'repeat' => 0),
 288              array('eventid' => $groupevent->id, 'repeat' => 0)
 289          );
 290          $this->expectException(moodle_exception::class);
 291          core_calendar_external::delete_calendar_events($events);
 292      }
 293  
 294      /**
 295       * Test get_calendar_events
 296       */
 297      public function test_get_calendar_events() {
 298          global $DB, $USER;
 299  
 300          $this->resetAfterTest(true);
 301          set_config('calendar_adminseesall', 1);
 302          $this->setAdminUser();
 303  
 304          // Create a few stuff to test with.
 305          $user = $this->getDataGenerator()->create_user();
 306          $user2 = $this->getDataGenerator()->create_user();
 307          $course = $this->getDataGenerator()->create_course();
 308  
 309          $category = $this->getDataGenerator()->create_category();
 310  
 311          $category2 = $this->getDataGenerator()->create_category();
 312          $category2b = $this->getDataGenerator()->create_category(['parent' => $category2->id]);
 313          $course3 = $this->getDataGenerator()->create_course(['category' => $category2b->id]);
 314  
 315          $role = $DB->get_record('role', array('shortname' => 'student'));
 316          $this->getDataGenerator()->enrol_user($user2->id, $course3->id, $role->id);
 317  
 318          $record = new stdClass();
 319          $record->courseid = $course->id;
 320          $group = $this->getDataGenerator()->create_group($record);
 321  
 322          $beforecount = $DB->count_records('event');
 323  
 324          // Let's create a few events.
 325          $siteevent = $this->create_calendar_event('site', $USER->id, 'site');
 326  
 327          // This event will have description with an inline fake image.
 328          $draftidfile = file_get_unused_draft_itemid();
 329          $usercontext = context_course::instance($course->id);
 330          $filerecord = array(
 331              'contextid' => $usercontext->id,
 332              'component' => 'user',
 333              'filearea'  => 'draft',
 334              'itemid'    => $draftidfile,
 335              'filepath'  => '/',
 336              'filename'  => 'fakeimage.png',
 337          );
 338          $fs = get_file_storage();
 339          $fs->create_file_from_string($filerecord, 'img contents');
 340  
 341          $record = new stdClass();
 342          $record->courseid = $course->id;
 343          $record->groupid = 0;
 344          $record->description = array(
 345              'format' => FORMAT_HTML,
 346              'text' => 'Text with img <img src="@@PLUGINFILE@@/fakeimage.png">',
 347              'itemid' => $draftidfile
 348          );
 349          $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record);
 350  
 351          $record = new stdClass();
 352          $record->courseid = 0;
 353          $record->groupid = 0;
 354          $userevent = $this->create_calendar_event('user', $USER->id, 'user', 0, time(), $record);
 355  
 356          $record = new stdClass();
 357          $record->courseid = $course->id;
 358          $record->groupid = $group->id;
 359          $groupevent = $this->create_calendar_event('group', $USER->id, 'group', 0, time(), $record);
 360  
 361          $paramevents = array ('eventids' => array($siteevent->id), 'courseids' => array($course->id),
 362                  'groupids' => array($group->id), 'categoryids' => array($category->id));
 363  
 364          $options = array ('siteevents' => true, 'userevents' => true);
 365          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 366          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 367  
 368          // Check to see if we got all events.
 369          $this->assertEquals(5, count($events['events']));
 370          $this->assertEquals(0, count($events['warnings']));
 371          $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + 7*WEEKSECS);
 372          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 373          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 374          $this->assertEquals(5, count($events['events']));
 375          $this->assertEquals(0, count($events['warnings']));
 376  
 377          // Expect the same URL in the description of two different events (because they are repeated).
 378          $coursecontext = context_course::instance($course->id);
 379          $expectedurl = "webservice/pluginfile.php/$coursecontext->id/calendar/event_description/$courseevent->id/fakeimage.png";
 380          $withdescription = 0;
 381          foreach ($events['events'] as $event) {
 382              if (!empty($event['description'])) {
 383                  $withdescription++;
 384                  $this->assertStringContainsString($expectedurl, $event['description']);
 385              }
 386          }
 387          $this->assertEquals(2, $withdescription);
 388  
 389          // Let's play around with caps.
 390  
 391          // Create user event for the user $user.
 392          $record = new stdClass();
 393          $record->courseid = 0;
 394          $record->groupid = 0;
 395          $this->create_calendar_event('user', $user->id, 'user', 0, time(), $record);
 396  
 397          $this->setUser($user);
 398          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 399          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 400          $this->assertEquals(2, count($events['events'])); // site, user.
 401          $this->assertEquals(2, count($events['warnings'])); // course, group.
 402  
 403          $role = $DB->get_record('role', array('shortname' => 'student'));
 404          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
 405          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 406          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 407          $this->assertEquals(4, count($events['events'])); // site, user, both course events.
 408          $this->assertEquals(1, count($events['warnings'])); // group.
 409  
 410          $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + HOURSECS);
 411          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 412          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 413          $this->assertEquals(3, count($events['events'])); // site, user, one course event.
 414          $this->assertEquals(1, count($events['warnings'])); // group.
 415  
 416          groups_add_member($group, $user);
 417          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 418          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 419          $this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
 420          $this->assertEquals(0, count($events['warnings']));
 421  
 422          $paramevents = array ('courseids' => array($course->id), 'groupids' => array($group->id));
 423          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 424          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 425          $this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
 426          $this->assertEquals(0, count($events['warnings']));
 427  
 428          $paramevents = array ('groupids' => array($group->id, 23));
 429          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 430          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 431          $this->assertEquals(3, count($events['events'])); // site, user, group.
 432          $this->assertEquals(1, count($events['warnings']));
 433  
 434          $paramevents = array ('courseids' => array(23));
 435          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 436          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 437          $this->assertEquals(2, count($events['events'])); // site, user.
 438          $this->assertEquals(1, count($events['warnings']));
 439  
 440          $paramevents = array ();
 441          $options = array ('siteevents' => false, 'userevents' => false, 'timeend' => time() + 7*WEEKSECS);
 442          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 443          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 444          $this->assertEquals(0, count($events['events'])); // nothing returned.
 445          $this->assertEquals(0, count($events['warnings']));
 446  
 447          $paramevents = array ('eventids' => array($siteevent->id, $groupevent->id));
 448          $options = array ('siteevents' => false, 'userevents' => false, 'timeend' => time() + 7*WEEKSECS);
 449          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 450          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 451          $this->assertEquals(2, count($events['events'])); // site, group.
 452          $this->assertEquals(0, count($events['warnings']));
 453  
 454          $paramevents = array ('eventids' => array($siteevent->id));
 455          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 456          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 457          $this->assertEquals(1, count($events['events'])); // site.
 458          $this->assertEquals(0, count($events['warnings']));
 459  
 460          // Try getting a course event by its id.
 461          $paramevents = array ('eventids' => array($courseevent->id));
 462          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 463          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 464          $this->assertEquals(1, count($events['events']));
 465          $this->assertEquals(0, count($events['warnings']));
 466  
 467          // Now, create an activity event.
 468          $this->setAdminUser();
 469          $nexttime = time() + DAYSECS;
 470          $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'duedate' => $nexttime));
 471  
 472          $this->setUser($user);
 473          $paramevents = array ('courseids' => array($course->id));
 474          $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + WEEKSECS);
 475          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 476          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 477  
 478          $this->assertCount(5, $events['events']);
 479  
 480          // Hide the assignment.
 481          set_coursemodule_visible($assign->cmid, 0);
 482          // Empty all the caches that may be affected  by this change.
 483          accesslib_clear_all_caches_for_unit_testing();
 484          course_modinfo::clear_instance_cache();
 485  
 486          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 487          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 488          // Expect one less.
 489          $this->assertCount(4, $events['events']);
 490  
 491          // Create some category events.
 492          $this->setAdminUser();
 493          $record = new stdClass();
 494          $record->courseid = 0;
 495          $record->categoryid = $category->id;
 496          $record->timestart = time() - DAYSECS;
 497          $catevent1 = $this->create_calendar_event('category a', $USER->id, 'category', 0, time(), $record);
 498  
 499          $record = new stdClass();
 500          $record->courseid = 0;
 501          $record->categoryid = $category2->id;
 502          $record->timestart = time() + DAYSECS;
 503          $catevent2 = $this->create_calendar_event('category b', $USER->id, 'category', 0, time(), $record);
 504  
 505          // Now as student, make sure we get the events of the courses I am enrolled.
 506          $this->setUser($user2);
 507          $paramevents = array('categoryids' => array($category2b->id));
 508          $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
 509          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 510          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 511  
 512          // Should be just one, since there's just one category event of the course I am enrolled (course3 - cat2b).
 513          $this->assertEquals(1, count($events['events']));
 514          $this->assertEquals($catevent2->id, $events['events'][0]['id']);
 515          $this->assertEquals($category2->id, $events['events'][0]['categoryid']);
 516          $this->assertEquals(0, count($events['warnings']));
 517  
 518          // Now get category events but by course (there aren't course events in the course).
 519          $paramevents = array('courseids' => array($course3->id));
 520          $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
 521          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 522          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 523          $this->assertEquals(1, count($events['events']));
 524          $this->assertEquals($catevent2->id, $events['events'][0]['id']);
 525          $this->assertEquals(0, count($events['warnings']));
 526  
 527          // Empty events in one where I'm not enrolled and one parent category
 528          // (parent of a category where this is a course where the user is enrolled).
 529          $paramevents = array('categoryids' => array($category2->id, $category->id));
 530          $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
 531          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 532          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 533          $this->assertEquals(1, count($events['events']));
 534          $this->assertEquals($catevent2->id, $events['events'][0]['id']);
 535          $this->assertEquals(0, count($events['warnings']));
 536  
 537          // Admin can see all category events.
 538          $this->setAdminUser();
 539          $paramevents = array('categoryids' => array($category->id, $category2->id, $category2b->id));
 540          $options = array('timeend' => time() + 7 * WEEKSECS, 'userevents' => false, 'siteevents' => false);
 541          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 542          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 543          $this->assertEquals(2, count($events['events']));
 544          $this->assertEquals(0, count($events['warnings']));
 545          $this->assertEquals($catevent1->id, $events['events'][0]['id']);
 546          $this->assertEquals($category->id, $events['events'][0]['categoryid']);
 547          $this->assertEquals($catevent2->id, $events['events'][1]['id']);
 548          $this->assertEquals($category2->id, $events['events'][1]['categoryid']);
 549      }
 550  
 551      /**
 552       * Test get_calendar_events with mathjax in the name.
 553       */
 554      public function test_get_calendar_events_with_mathjax() {
 555          global $USER;
 556  
 557          $this->resetAfterTest(true);
 558          set_config('calendar_adminseesall', 1);
 559          $this->setAdminUser();
 560  
 561          // Enable MathJax filter in content and headings.
 562          $this->configure_filters([
 563              ['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
 564          ]);
 565  
 566          // Create a site event with mathjax in the name and description.
 567          $siteevent = $this->create_calendar_event('Site Event $$(a+b)=2$$', $USER->id, 'site', 0, time(),
 568                  ['description' => 'Site Event Description $$(a+b)=2$$']);
 569  
 570          // Now call the WebService.
 571          $events = core_calendar_external::get_calendar_events();
 572          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 573  
 574          // Format the original data.
 575          $sitecontext = context_system::instance();
 576          $siteevent->name = $siteevent->format_external_name();
 577          list($siteevent->description, $siteevent->descriptionformat) = $siteevent->format_external_text();
 578  
 579          // Check that the event data is formatted.
 580          $this->assertCount(1, $events['events']);
 581          $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $events['events'][0]['name']);
 582          $this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $events['events'][0]['description']);
 583          $this->assertEquals($siteevent->name, $events['events'][0]['name']);
 584          $this->assertEquals($siteevent->description, $events['events'][0]['description']);
 585      }
 586  
 587      /**
 588       * Test core_calendar_external::create_calendar_events
 589       */
 590      public function test_core_create_calendar_events() {
 591          global $DB, $USER, $SITE;
 592  
 593          $this->resetAfterTest(true);
 594          $this->setAdminUser();
 595  
 596          // Create a few stuff to test with.
 597          $user = $this->getDataGenerator()->create_user();
 598          $course = $this->getDataGenerator()->create_course();
 599          $record = new stdClass();
 600          $record->courseid = $course->id;
 601          $group = $this->getDataGenerator()->create_group($record);
 602  
 603          $prevcount = $DB->count_records('event');
 604  
 605          // Let's create a few events.
 606          $events = array (
 607                  array('name' => 'site', 'courseid' => $SITE->id, 'eventtype' => 'site'),
 608                  array('name' => 'course', 'courseid' => $course->id, 'eventtype' => 'course', 'repeats' => 2),
 609                  array('name' => 'group', 'courseid' => $course->id, 'groupid' => $group->id, 'eventtype' => 'group'),
 610                  array('name' => 'user')
 611                  );
 612          $eventsret = core_calendar_external::create_calendar_events($events);
 613          $eventsret = external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
 614  
 615          // Check to see if things were created properly.
 616          $aftercount = $DB->count_records('event');
 617          $this->assertEquals($prevcount + 5, $aftercount);
 618          $this->assertEquals(5, count($eventsret['events']));
 619          $this->assertEquals(0, count($eventsret['warnings']));
 620  
 621          $sitecontext = context_system::instance();
 622          $coursecontext = context_course::instance($course->id);
 623  
 624          $this->setUser($user);
 625          $prevcount = $aftercount;
 626          $events = array (
 627                  array('name' => 'course', 'courseid' => $course->id, 'eventtype' => 'course', 'repeats' => 2),
 628                  array('name' => 'group', 'courseid' => $course->id, 'groupid' => $group->id, 'eventtype' => 'group'),
 629                  array('name' => 'user')
 630          );
 631          $role = $DB->get_record('role', array('shortname' => 'student'));
 632          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
 633          groups_add_member($group, $user);
 634          $this->assignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
 635          $this->assignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
 636          $eventsret = core_calendar_external::create_calendar_events($events);
 637          $eventsret = external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
 638          // Check to see if things were created properly.
 639          $aftercount = $DB->count_records('event');
 640          $this->assertEquals($prevcount + 4, $aftercount);
 641          $this->assertEquals(4, count($eventsret['events']));
 642          $this->assertEquals(0, count($eventsret['warnings']));
 643  
 644          // Check to see nothing was created without proper permission.
 645          $this->setGuestUser();
 646          $prevcount = $DB->count_records('event');
 647          $eventsret = core_calendar_external::create_calendar_events($events);
 648          $eventsret = external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
 649          $aftercount = $DB->count_records('event');
 650          $this->assertEquals($prevcount, $aftercount);
 651          $this->assertEquals(0, count($eventsret['events']));
 652          $this->assertEquals(3, count($eventsret['warnings']));
 653  
 654          $this->setUser($user);
 655          $this->unassignUserCapability('moodle/calendar:manageentries', $coursecontext->id, $role->id);
 656          $this->unassignUserCapability('moodle/calendar:managegroupentries', $coursecontext->id, $role->id);
 657          $prevcount = $DB->count_records('event');
 658          $eventsret = core_calendar_external::create_calendar_events($events);
 659          $eventsret = external_api::clean_returnvalue(core_calendar_external::create_calendar_events_returns(), $eventsret);
 660          $aftercount = $DB->count_records('event');
 661          $this->assertEquals($prevcount + 1, $aftercount); // User event.
 662          $this->assertEquals(1, count($eventsret['events']));
 663          $this->assertEquals(2, count($eventsret['warnings']));
 664      }
 665  
 666      /**
 667       * Requesting calendar events from a given time should return all events with a sort
 668       * time at or after the requested time. All events prior to that time should not
 669       * be return.
 670       *
 671       * If there are no events on or after the given time then an empty result set should
 672       * be returned.
 673       */
 674      public function test_get_calendar_action_events_by_timesort_after_time() {
 675          $user = $this->getDataGenerator()->create_user();
 676          $course = $this->getDataGenerator()->create_course();
 677          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 678          $moduleinstance = $generator->create_instance(['course' => $course->id]);
 679  
 680          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 681          $this->resetAfterTest(true);
 682          $this->setUser($user);
 683  
 684          $params = [
 685              'type' => CALENDAR_EVENT_TYPE_ACTION,
 686              'modulename' => 'assign',
 687              'instance' => $moduleinstance->id,
 688              'courseid' => $course->id,
 689          ];
 690  
 691          $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 1]));
 692          $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
 693          $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
 694          $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
 695          $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
 696          $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
 697          $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
 698          $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));
 699  
 700          $result = core_calendar_external::get_calendar_action_events_by_timesort(5);
 701          $result = external_api::clean_returnvalue(
 702              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 703              $result
 704          );
 705          $events = $result['events'];
 706  
 707          $this->assertCount(4, $events);
 708          $this->assertEquals('Event 5', $events[0]['name']);
 709          $this->assertEquals('Event 6', $events[1]['name']);
 710          $this->assertEquals('Event 7', $events[2]['name']);
 711          $this->assertEquals('Event 8', $events[3]['name']);
 712          $this->assertEquals($event5->id, $result['firstid']);
 713          $this->assertEquals($event8->id, $result['lastid']);
 714  
 715          $result = core_calendar_external::get_calendar_action_events_by_timesort(9);
 716          $result = external_api::clean_returnvalue(
 717              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 718              $result
 719          );
 720  
 721          $this->assertEmpty($result['events']);
 722          $this->assertNull($result['firstid']);
 723          $this->assertNull($result['lastid']);
 724  
 725          // Requesting action events on behalf of another user.
 726          $this->setAdminUser();
 727          $result = core_calendar_external::get_calendar_action_events_by_timesort(5, null, 0, 20, false, $user->id);
 728          $result = external_api::clean_returnvalue(
 729              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 730              $result
 731          );
 732          $events = $result['events'];
 733  
 734          $this->assertCount(4, $events);
 735          $this->assertEquals('Event 5', $events[0]['name']);
 736          $this->assertEquals('Event 6', $events[1]['name']);
 737          $this->assertEquals('Event 7', $events[2]['name']);
 738          $this->assertEquals('Event 8', $events[3]['name']);
 739          $this->assertEquals($event5->id, $result['firstid']);
 740          $this->assertEquals($event8->id, $result['lastid']);
 741      }
 742  
 743      /**
 744       * Requesting calendar events before a given time should return all events with a sort
 745       * time at or before the requested time (inclusive). All events after that time
 746       * should not be returned.
 747       *
 748       * If there are no events before the given time then an empty result set should be
 749       * returned.
 750       */
 751      public function test_get_calendar_action_events_by_timesort_before_time() {
 752          $user = $this->getDataGenerator()->create_user();
 753          $course = $this->getDataGenerator()->create_course();
 754          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 755          $moduleinstance = $generator->create_instance(['course' => $course->id]);
 756  
 757          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 758          $this->resetAfterTest(true);
 759          $this->setUser($user);
 760  
 761          $params = [
 762              'type' => CALENDAR_EVENT_TYPE_ACTION,
 763              'modulename' => 'assign',
 764              'instance' => $moduleinstance->id,
 765              'courseid' => $course->id,
 766          ];
 767  
 768          $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
 769          $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
 770          $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
 771          $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
 772          $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
 773          $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
 774          $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));
 775          $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 9]));
 776  
 777          $result = core_calendar_external::get_calendar_action_events_by_timesort(null, 5);
 778          $result = external_api::clean_returnvalue(
 779              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 780              $result
 781          );
 782          $events = $result['events'];
 783  
 784          $this->assertCount(4, $events);
 785          $this->assertEquals('Event 1', $events[0]['name']);
 786          $this->assertEquals('Event 2', $events[1]['name']);
 787          $this->assertEquals('Event 3', $events[2]['name']);
 788          $this->assertEquals('Event 4', $events[3]['name']);
 789          $this->assertEquals($event1->id, $result['firstid']);
 790          $this->assertEquals($event4->id, $result['lastid']);
 791  
 792          $result = core_calendar_external::get_calendar_action_events_by_timesort(null, 1);
 793          $result = external_api::clean_returnvalue(
 794              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 795              $result
 796          );
 797  
 798          $this->assertEmpty($result['events']);
 799          $this->assertNull($result['firstid']);
 800          $this->assertNull($result['lastid']);
 801  
 802          // Requesting action events on behalf of another user.
 803          $this->setAdminUser();
 804  
 805          $result = core_calendar_external::get_calendar_action_events_by_timesort(null, 5, 0, 20, false, $user->id);
 806          $result = external_api::clean_returnvalue(
 807              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 808              $result
 809          );
 810          $events = $result['events'];
 811  
 812          $this->assertCount(4, $events);
 813          $this->assertEquals('Event 1', $events[0]['name']);
 814          $this->assertEquals('Event 2', $events[1]['name']);
 815          $this->assertEquals('Event 3', $events[2]['name']);
 816          $this->assertEquals('Event 4', $events[3]['name']);
 817          $this->assertEquals($event1->id, $result['firstid']);
 818          $this->assertEquals($event4->id, $result['lastid']);
 819      }
 820  
 821      /**
 822       * Test retrieving event that was overridden for a user
 823       */
 824      public function test_get_calendar_events_override() {
 825          $user = $this->getDataGenerator()->create_user();
 826          $user2 = $this->getDataGenerator()->create_user();
 827          $teacher = $this->getDataGenerator()->create_user();
 828          $anotheruser = $this->getDataGenerator()->create_user();
 829          $course = $this->getDataGenerator()->create_course();
 830          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 831          $moduleinstance = $generator->create_instance(['course' => $course->id]);
 832  
 833          $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
 834          $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
 835          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
 836          $this->resetAfterTest(true);
 837          $this->setAdminUser();
 838  
 839          $params = [
 840              'type' => CALENDAR_EVENT_TYPE_ACTION,
 841              'modulename' => 'assign',
 842              'instance' => $moduleinstance->id,
 843          ];
 844  
 845          $now = time();
 846          // Create two events - one for everybody in the course and one only for the first student.
 847          $event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]);
 848          $event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]);
 849  
 850          // Retrieve course events for the second student - only one "Base event" is returned.
 851          $this->setUser($user2);
 852          $paramevents = array('courseids' => array($course->id));
 853          $options = array ('siteevents' => true, 'userevents' => true);
 854          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 855          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 856          $this->assertEquals(1, count($events['events']));
 857          $this->assertEquals(0, count($events['warnings']));
 858          $this->assertEquals('Base event', $events['events'][0]['name']);
 859  
 860          // Retrieve events for the first student - both events are returned.
 861          $this->setUser($user);
 862          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 863          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 864          $this->assertEquals(2, count($events['events']));
 865          $this->assertEquals(0, count($events['warnings']));
 866          $this->assertEquals('Base event', $events['events'][0]['name']);
 867          $this->assertEquals('User event', $events['events'][1]['name']);
 868  
 869          // Retrieve events by id as a teacher, 'User event' should be returned since teacher has access to this course.
 870          $this->setUser($teacher);
 871          $paramevents = ['eventids' => [$event2->id]];
 872          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 873          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 874          $this->assertEquals(1, count($events['events']));
 875          $this->assertEquals(0, count($events['warnings']));
 876          $this->assertEquals('User event', $events['events'][0]['name']);
 877  
 878          // Retrieve events by id as another user, nothing should be returned.
 879          $this->setUser($anotheruser);
 880          $paramevents = ['eventids' => [$event2->id, $event1->id]];
 881          $events = core_calendar_external::get_calendar_events($paramevents, $options);
 882          $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
 883          $this->assertEquals(0, count($events['events']));
 884          $this->assertEquals(0, count($events['warnings']));
 885      }
 886  
 887      /**
 888       * Requesting calendar events within a given time range should return all events with
 889       * a sort time between the lower and upper time bound (inclusive).
 890       *
 891       * If there are no events in the given time range then an empty result set should be
 892       * returned.
 893       */
 894      public function test_get_calendar_action_events_by_timesort_time_range() {
 895          $user = $this->getDataGenerator()->create_user();
 896          $course = $this->getDataGenerator()->create_course();
 897          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 898          $moduleinstance = $generator->create_instance(['course' => $course->id]);
 899  
 900          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 901          $this->resetAfterTest(true);
 902          $this->setUser($user);
 903  
 904          $params = [
 905              'type' => CALENDAR_EVENT_TYPE_ACTION,
 906              'modulename' => 'assign',
 907              'instance' => $moduleinstance->id,
 908              'courseid' => $course->id,
 909          ];
 910  
 911          $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 1]));
 912          $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
 913          $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
 914          $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
 915          $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
 916          $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
 917          $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
 918          $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));
 919  
 920          $result = core_calendar_external::get_calendar_action_events_by_timesort(3, 6);
 921          $result = external_api::clean_returnvalue(
 922              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 923              $result
 924          );
 925          $events = $result['events'];
 926  
 927          $this->assertCount(4, $events);
 928          $this->assertEquals('Event 3', $events[0]['name']);
 929          $this->assertEquals('Event 4', $events[1]['name']);
 930          $this->assertEquals('Event 5', $events[2]['name']);
 931          $this->assertEquals('Event 6', $events[3]['name']);
 932          $this->assertEquals($event3->id, $result['firstid']);
 933          $this->assertEquals($event6->id, $result['lastid']);
 934  
 935          $result = core_calendar_external::get_calendar_action_events_by_timesort(10, 15);
 936          $result = external_api::clean_returnvalue(
 937              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 938              $result
 939          );
 940  
 941          $this->assertEmpty($result['events']);
 942          $this->assertNull($result['firstid']);
 943          $this->assertNull($result['lastid']);
 944      }
 945  
 946      /**
 947       * Requesting calendar events within a given time range and a limit and offset should return
 948       * the number of events up to the given limit value that have a sort time between the lower
 949       * and uppper time bound (inclusive) where the result set is shifted by the offset value.
 950       *
 951       * If there are no events in the given time range then an empty result set should be
 952       * returned.
 953       */
 954      public function test_get_calendar_action_events_by_timesort_time_limit_offset() {
 955          $user = $this->getDataGenerator()->create_user();
 956          $course = $this->getDataGenerator()->create_course();
 957          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
 958          $moduleinstance = $generator->create_instance(['course' => $course->id]);
 959  
 960          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 961          $this->resetAfterTest(true);
 962          $this->setUser($user);
 963  
 964          $params = [
 965              'type' => CALENDAR_EVENT_TYPE_ACTION,
 966              'modulename' => 'assign',
 967              'instance' => $moduleinstance->id,
 968              'courseid' => $course->id,
 969          ];
 970  
 971          $event1 = $this->create_calendar_event('Event 1', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 1]));
 972          $event2 = $this->create_calendar_event('Event 2', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 2]));
 973          $event3 = $this->create_calendar_event('Event 3', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 3]));
 974          $event4 = $this->create_calendar_event('Event 4', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 4]));
 975          $event5 = $this->create_calendar_event('Event 5', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 5]));
 976          $event6 = $this->create_calendar_event('Event 6', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 6]));
 977          $event7 = $this->create_calendar_event('Event 7', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 7]));
 978          $event8 = $this->create_calendar_event('Event 8', $user->id, 'user', 0, 1, array_merge($params, ['timesort' => 8]));
 979  
 980          $result = core_calendar_external::get_calendar_action_events_by_timesort(2, 7, $event3->id, 2);
 981          $result = external_api::clean_returnvalue(
 982              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 983              $result
 984          );
 985          $events = $result['events'];
 986  
 987          $this->assertCount(2, $events);
 988          $this->assertEquals('Event 4', $events[0]['name']);
 989          $this->assertEquals('Event 5', $events[1]['name']);
 990          $this->assertEquals($event4->id, $result['firstid']);
 991          $this->assertEquals($event5->id, $result['lastid']);
 992  
 993          $result = core_calendar_external::get_calendar_action_events_by_timesort(2, 7, $event5->id, 2);
 994          $result = external_api::clean_returnvalue(
 995              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
 996              $result
 997          );
 998          $events = $result['events'];
 999  
1000          $this->assertCount(2, $events);
1001          $this->assertEquals('Event 6', $events[0]['name']);
1002          $this->assertEquals('Event 7', $events[1]['name']);
1003          $this->assertEquals($event6->id, $result['firstid']);
1004          $this->assertEquals($event7->id, $result['lastid']);
1005  
1006          $result = core_calendar_external::get_calendar_action_events_by_timesort(2, 7, $event7->id, 2);
1007          $result = external_api::clean_returnvalue(
1008              core_calendar_external::get_calendar_action_events_by_timesort_returns(),
1009              $result
1010          );
1011  
1012          $this->assertEmpty($result['events']);
1013          $this->assertNull($result['firstid']);
1014          $this->assertNull($result['lastid']);
1015      }
1016  
1017      /**
1018       * Check that it is possible to restrict the calendar events to events where the user is not suspended in the course.
1019       */
1020      public function test_get_calendar_action_events_by_timesort_suspended_course() {
1021          $this->resetAfterTest();
1022          $user1 = $this->getDataGenerator()->create_user();
1023          $user2 = $this->getDataGenerator()->create_user();
1024          $course = $this->getDataGenerator()->create_course();
1025          $this->setAdminUser();
1026          $lesson = $this->getDataGenerator()->create_module('lesson', [
1027                  'name' => 'Lesson 1',
1028                  'course' => $course->id,
1029                  'available' => time(),
1030                  'deadline' => (time() + (60 * 60 * 24 * 5))
1031              ]
1032          );
1033          $this->getDataGenerator()->enrol_user($user1->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1034          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
1035  
1036          $this->setUser($user1);
1037          $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
1038          $this->assertEmpty($result->events);
1039          $this->setUser($user2);
1040          $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
1041          $this->assertCount(1, $result->events);
1042          $this->assertEquals('Lesson 1 closes', $result->events[0]->name);
1043      }
1044  
1045      /**
1046       * Check that it is possible to get other user's events without the permission.
1047       */
1048      public function test_get_calendar_action_events_by_timesort_for_other_users() {
1049          $this->resetAfterTest();
1050          // Create test users.
1051          $user1 = $this->getDataGenerator()->create_user(['email' => 'student1@localhost.com']);
1052          $user2 = $this->getDataGenerator()->create_user(['email' => 'student2@localhost.com']);
1053          // Create test course.
1054          $course = $this->getDataGenerator()->create_course();
1055          $this->setAdminUser();
1056          // Create test activity and make it available only for student2.
1057          $lesson = $this->getDataGenerator()->create_module('lesson', [
1058                  'name' => 'Lesson 1',
1059                  'course' => $course->id,
1060                  'available' => time(),
1061                  'deadline' => (time() + (60 * 60 * 24 * 5)),
1062                  'availability' => '{"op":"&","c":[{"type":"profile","sf":"email","op":"isequalto","v":"student2@localhost.com"}],"showc":[true]}'
1063              ]
1064          );
1065          // Enrol.
1066          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1067          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
1068  
1069          // Student2 can see the event.
1070          $this->setUser($user2);
1071          $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
1072          $this->assertCount(1, $result->events);
1073          $this->assertEquals('Lesson 1 closes', $result->events[0]->name);
1074  
1075          // Student1 cannot see the event.
1076          $this->setUser($user1);
1077          $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true);
1078          $this->assertEmpty($result->events);
1079  
1080          // Admin, Manager, Teacher can view student2's data.
1081          $this->setAdminUser();
1082          $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true, $user2->id);
1083          $this->assertCount(1, $result->events);
1084          $this->assertEquals('Lesson 1 closes', $result->events[0]->name);
1085  
1086          // Student1 will see an exception if he/she trying to view student2's data.
1087          $this->setUser($user1);
1088          $this->expectException(required_capability_exception::class);
1089          $this->expectExceptionMessage('error/nopermission');
1090          $result = core_calendar_external::get_calendar_action_events_by_timesort(0, null, 0, 20, true, $user2->id);
1091      }
1092  
1093      /**
1094       * Requesting calendar events from a given course and time should return all
1095       * events with a sort time at or after the requested time. All events prior
1096       * to that time should not be return.
1097       *
1098       * If there are no events on or after the given time then an empty result set should
1099       * be returned.
1100       */
1101      public function test_get_calendar_action_events_by_course_after_time() {
1102          $user = $this->getDataGenerator()->create_user();
1103          $course1 = $this->getDataGenerator()->create_course();
1104          $course2 = $this->getDataGenerator()->create_course();
1105          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1106          $instance1 = $generator->create_instance(['course' => $course1->id]);
1107          $instance2 = $generator->create_instance(['course' => $course2->id]);
1108          $records = [];
1109  
1110          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1111          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1112          $this->resetAfterTest(true);
1113          $this->setUser($user);
1114  
1115          for ($i = 1; $i < 19; $i++) {
1116              $courseid = ($i < 9) ? $course1->id : $course2->id;
1117              $instance = ($i < 9) ? $instance1->id : $instance2->id;
1118              $records[] = $this->create_calendar_event(
1119                  sprintf('Event %d', $i),
1120                  $user->id,
1121                  'user',
1122                  0,
1123                  1,
1124                  [
1125                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1126                      'courseid' => $courseid,
1127                      'timesort' => $i,
1128                      'modulename' => 'assign',
1129                      'instance' => $instance,
1130                  ]
1131              );
1132          }
1133  
1134          $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 5);
1135          $result = external_api::clean_returnvalue(
1136              core_calendar_external::get_calendar_action_events_by_course_returns(),
1137              $result
1138          );
1139          $result = $result['events'];
1140  
1141          $this->assertCount(4, $result);
1142          $this->assertEquals('Event 5', $result[0]['name']);
1143          $this->assertEquals('Event 6', $result[1]['name']);
1144          $this->assertEquals('Event 7', $result[2]['name']);
1145          $this->assertEquals('Event 8', $result[3]['name']);
1146  
1147          $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 9);
1148          $result = external_api::clean_returnvalue(
1149              core_calendar_external::get_calendar_action_events_by_course_returns(),
1150              $result
1151          );
1152          $result = $result['events'];
1153  
1154          $this->assertEmpty($result);
1155      }
1156  
1157      /**
1158       * Requesting calendar events for a course and before a given time should return
1159       * all events with a sort time at or before the requested time (inclusive). All
1160       * events after that time should not be returned.
1161       *
1162       * If there are no events before the given time then an empty result set should be
1163       * returned.
1164       */
1165      public function test_get_calendar_action_events_by_course_before_time() {
1166          $user = $this->getDataGenerator()->create_user();
1167          $course1 = $this->getDataGenerator()->create_course();
1168          $course2 = $this->getDataGenerator()->create_course();
1169          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1170          $instance1 = $generator->create_instance(['course' => $course1->id]);
1171          $instance2 = $generator->create_instance(['course' => $course2->id]);
1172          $records = [];
1173  
1174          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1175          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1176          $this->resetAfterTest(true);
1177          $this->setUser($user);
1178  
1179          for ($i = 1; $i < 19; $i++) {
1180              $courseid = ($i < 9) ? $course1->id : $course2->id;
1181              $instance = ($i < 9) ? $instance1->id : $instance2->id;
1182              $records[] = $this->create_calendar_event(
1183                  sprintf('Event %d', $i),
1184                  $user->id,
1185                  'user',
1186                  0,
1187                  1,
1188                  [
1189                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1190                      'courseid' => $courseid,
1191                      'timesort' => $i + 1,
1192                      'modulename' => 'assign',
1193                      'instance' => $instance,
1194                  ]
1195              );
1196          }
1197  
1198          $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, null, 5);
1199          $result = external_api::clean_returnvalue(
1200              core_calendar_external::get_calendar_action_events_by_course_returns(),
1201              $result
1202          );
1203          $result = $result['events'];
1204  
1205          $this->assertCount(4, $result);
1206          $this->assertEquals('Event 1', $result[0]['name']);
1207          $this->assertEquals('Event 2', $result[1]['name']);
1208          $this->assertEquals('Event 3', $result[2]['name']);
1209          $this->assertEquals('Event 4', $result[3]['name']);
1210  
1211          $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, null, 1);
1212          $result = external_api::clean_returnvalue(
1213              core_calendar_external::get_calendar_action_events_by_course_returns(),
1214              $result
1215          );
1216          $result = $result['events'];
1217  
1218          $this->assertEmpty($result);
1219      }
1220  
1221      /**
1222       * Requesting calendar events for a course and within a given time range should
1223       * return all events with a sort time between the lower and upper time bound
1224       * (inclusive).
1225       *
1226       * If there are no events in the given time range then an empty result set should be
1227       * returned.
1228       */
1229      public function test_get_calendar_action_events_by_course_time_range() {
1230          $user = $this->getDataGenerator()->create_user();
1231          $course1 = $this->getDataGenerator()->create_course();
1232          $course2 = $this->getDataGenerator()->create_course();
1233          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1234          $instance1 = $generator->create_instance(['course' => $course1->id]);
1235          $instance2 = $generator->create_instance(['course' => $course2->id]);
1236          $records = [];
1237  
1238          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1239          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1240          $this->resetAfterTest(true);
1241          $this->setUser($user);
1242  
1243          for ($i = 1; $i < 19; $i++) {
1244              $courseid = ($i < 9) ? $course1->id : $course2->id;
1245              $instance = ($i < 9) ? $instance1->id : $instance2->id;
1246              $records[] = $this->create_calendar_event(
1247                  sprintf('Event %d', $i),
1248                  $user->id,
1249                  'user',
1250                  0,
1251                  1,
1252                  [
1253                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1254                      'courseid' => $courseid,
1255                      'timesort' => $i,
1256                      'modulename' => 'assign',
1257                      'instance' => $instance,
1258                  ]
1259              );
1260          }
1261  
1262          $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 3, 6);
1263          $result = external_api::clean_returnvalue(
1264              core_calendar_external::get_calendar_action_events_by_course_returns(),
1265              $result
1266          );
1267          $result = $result['events'];
1268  
1269          $this->assertCount(4, $result);
1270          $this->assertEquals('Event 3', $result[0]['name']);
1271          $this->assertEquals('Event 4', $result[1]['name']);
1272          $this->assertEquals('Event 5', $result[2]['name']);
1273          $this->assertEquals('Event 6', $result[3]['name']);
1274  
1275          $result = core_calendar_external::get_calendar_action_events_by_course($course1->id, 10, 15);
1276          $result = external_api::clean_returnvalue(
1277              core_calendar_external::get_calendar_action_events_by_course_returns(),
1278              $result
1279          );
1280          $result = $result['events'];
1281  
1282          $this->assertEmpty($result);
1283      }
1284  
1285      /**
1286       * Requesting calendar events for a course and within a given time range and a limit
1287       * and offset should return the number of events up to the given limit value that have
1288       * a sort time between the lower and uppper time bound (inclusive) where the result
1289       * set is shifted by the offset value.
1290       *
1291       * If there are no events in the given time range then an empty result set should be
1292       * returned.
1293       */
1294      public function test_get_calendar_action_events_by_course_time_limit_offset() {
1295          $user = $this->getDataGenerator()->create_user();
1296          $course1 = $this->getDataGenerator()->create_course();
1297          $course2 = $this->getDataGenerator()->create_course();
1298          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1299          $instance1 = $generator->create_instance(['course' => $course1->id]);
1300          $instance2 = $generator->create_instance(['course' => $course2->id]);
1301          $records = [];
1302  
1303          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1304          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1305          $this->resetAfterTest(true);
1306          $this->setUser($user);
1307  
1308          for ($i = 1; $i < 19; $i++) {
1309              $courseid = ($i < 9) ? $course1->id : $course2->id;
1310              $instance = ($i < 9) ? $instance1->id : $instance2->id;
1311              $records[] = $this->create_calendar_event(
1312                  sprintf('Event %d', $i),
1313                  $user->id,
1314                  'user',
1315                  0,
1316                  1,
1317                  [
1318                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1319                      'courseid' => $courseid,
1320                      'timesort' => $i,
1321                      'modulename' => 'assign',
1322                      'instance' => $instance,
1323                  ]
1324              );
1325          }
1326  
1327          $result = core_calendar_external::get_calendar_action_events_by_course(
1328              $course1->id, 2, 7, $records[2]->id, 2);
1329          $result = external_api::clean_returnvalue(
1330              core_calendar_external::get_calendar_action_events_by_course_returns(),
1331              $result
1332          );
1333          $result = $result['events'];
1334  
1335          $this->assertCount(2, $result);
1336          $this->assertEquals('Event 4', $result[0]['name']);
1337          $this->assertEquals('Event 5', $result[1]['name']);
1338  
1339          $result = core_calendar_external::get_calendar_action_events_by_course(
1340              $course1->id, 2, 7, $records[4]->id, 2);
1341          $result = external_api::clean_returnvalue(
1342              core_calendar_external::get_calendar_action_events_by_course_returns(),
1343              $result
1344          );
1345          $result = $result['events'];
1346  
1347          $this->assertCount(2, $result);
1348          $this->assertEquals('Event 6', $result[0]['name']);
1349          $this->assertEquals('Event 7', $result[1]['name']);
1350  
1351          $result = core_calendar_external::get_calendar_action_events_by_course(
1352              $course1->id, 2, 7, $records[6]->id, 2);
1353          $result = external_api::clean_returnvalue(
1354              core_calendar_external::get_calendar_action_events_by_course_returns(),
1355              $result
1356          );
1357          $result = $result['events'];
1358  
1359          $this->assertEmpty($result);
1360      }
1361  
1362      /**
1363       * Test that get_action_events_by_courses will return a list of events for each
1364       * course you provided as long as the user is enrolled in the course.
1365       */
1366      public function test_get_action_events_by_courses() {
1367          $user = $this->getDataGenerator()->create_user();
1368          $course1 = $this->getDataGenerator()->create_course();
1369          $course2 = $this->getDataGenerator()->create_course();
1370          $course3 = $this->getDataGenerator()->create_course();
1371          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1372          $instance1 = $generator->create_instance(['course' => $course1->id]);
1373          $instance2 = $generator->create_instance(['course' => $course2->id]);
1374          $instance3 = $generator->create_instance(['course' => $course3->id]);
1375          $records = [];
1376          $mapresult = function($result) {
1377              $groupedbycourse = [];
1378              foreach ($result['groupedbycourse'] as $group) {
1379                  $events = $group['events'];
1380                  $courseid = $group['courseid'];
1381                  $groupedbycourse[$courseid] = $events;
1382              }
1383  
1384              return $groupedbycourse;
1385          };
1386  
1387          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1388          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1389          $this->resetAfterTest(true);
1390          $this->setUser($user);
1391  
1392          for ($i = 1; $i < 10; $i++) {
1393              if ($i < 3) {
1394                  $courseid = $course1->id;
1395                  $instance = $instance1->id;
1396              } else if ($i < 6) {
1397                  $courseid = $course2->id;
1398                  $instance = $instance2->id;
1399              } else {
1400                  $courseid = $course3->id;
1401                  $instance = $instance3->id;
1402              }
1403  
1404              $records[] = $this->create_calendar_event(
1405                  sprintf('Event %d', $i),
1406                  $user->id,
1407                  'user',
1408                  0,
1409                  1,
1410                  [
1411                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1412                      'courseid' => $courseid,
1413                      'timesort' => $i,
1414                      'modulename' => 'assign',
1415                      'instance' => $instance,
1416                  ]
1417              );
1418          }
1419  
1420          $result = core_calendar_external::get_calendar_action_events_by_courses([], 1);
1421          $result = external_api::clean_returnvalue(
1422              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1423              $result
1424          );
1425          $result = $result['groupedbycourse'];
1426  
1427          $this->assertEmpty($result);
1428  
1429          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id], 3);
1430          $result = external_api::clean_returnvalue(
1431              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1432              $result
1433          );
1434  
1435          $groupedbycourse = $mapresult($result);
1436  
1437          $this->assertEmpty($groupedbycourse[$course1->id]);
1438  
1439          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id], 1);
1440          $result = external_api::clean_returnvalue(
1441              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1442              $result
1443          );
1444          $groupedbycourse = $mapresult($result);
1445  
1446          $this->assertCount(2, $groupedbycourse[$course1->id]);
1447          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1448          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']);
1449  
1450          $result = core_calendar_external::get_calendar_action_events_by_courses(
1451              [$course1->id, $course2->id], 1);
1452          $result = external_api::clean_returnvalue(
1453              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1454              $result
1455          );
1456          $groupedbycourse = $mapresult($result);
1457  
1458          $this->assertCount(2, $groupedbycourse[$course1->id]);
1459          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1460          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']);
1461          $this->assertCount(3, $groupedbycourse[$course2->id]);
1462          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1463          $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']);
1464          $this->assertEquals('Event 5', $groupedbycourse[$course2->id][2]['name']);
1465  
1466          $result = core_calendar_external::get_calendar_action_events_by_courses(
1467              [$course1->id, $course2->id], 2, 4);
1468          $result = external_api::clean_returnvalue(
1469              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1470              $result
1471          );
1472          $groupedbycourse = $mapresult($result);
1473  
1474          $this->assertCount(2, $groupedbycourse);
1475          $this->assertCount(1, $groupedbycourse[$course1->id]);
1476          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][0]['name']);
1477          $this->assertCount(2, $groupedbycourse[$course2->id]);
1478          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1479          $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']);
1480  
1481          $result = core_calendar_external::get_calendar_action_events_by_courses(
1482              [$course1->id, $course2->id], 1, null, 1);
1483          $result = external_api::clean_returnvalue(
1484              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1485              $result
1486          );
1487          $groupedbycourse = $mapresult($result);
1488  
1489          $this->assertCount(2, $groupedbycourse);
1490          $this->assertCount(1, $groupedbycourse[$course1->id]);
1491          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1492          $this->assertCount(1, $groupedbycourse[$course2->id]);
1493          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1494      }
1495  
1496      /**
1497       * Test for deleting module events.
1498       */
1499      public function test_delete_calendar_events_for_modules() {
1500          $this->resetAfterTest();
1501          $this->setAdminUser();
1502          $course = $this->getDataGenerator()->create_course();
1503          $nexttime = time() + DAYSECS;
1504          $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'duedate' => $nexttime]);
1505          $events = calendar_get_events(time(), $nexttime, true, true, true);
1506          $this->assertCount(1, $events);
1507          $params = [];
1508          foreach ($events as $event) {
1509              $params[] = [
1510                  'eventid' => $event->id,
1511                  'repeat' => false
1512              ];
1513          }
1514  
1515          $this->expectException('moodle_exception');
1516          core_calendar_external::delete_calendar_events($params);
1517      }
1518  
1519      /**
1520       * Updating the event start day should change the date value but leave
1521       * the time of day unchanged.
1522       */
1523      public function test_update_event_start_day() {
1524          $generator = $this->getDataGenerator();
1525          $user = $generator->create_user();
1526          $roleid = $generator->create_role();
1527          $context = \context_system::instance();
1528          $originalstarttime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
1529          $newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
1530          $expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
1531  
1532          $generator->role_assign($roleid, $user->id, $context->id);
1533          assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
1534  
1535          $this->setUser($user);
1536          $this->resetAfterTest(true);
1537  
1538          $event = $this->create_calendar_event(
1539              'Test event',
1540              $user->id,
1541              'user',
1542              0,
1543              null,
1544              [
1545                  'courseid' => 0,
1546                  'timestart' => $originalstarttime->getTimestamp()
1547              ]
1548          );
1549  
1550          $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp());
1551          $result = external_api::clean_returnvalue(
1552              core_calendar_external::update_event_start_day_returns(),
1553              $result
1554          );
1555  
1556          $this->assertEquals($expected->getTimestamp(), $result['event']['timestart']);
1557      }
1558  
1559      /**
1560       * A user should not be able to edit an event that they don't have
1561       * capabilities for.
1562       */
1563      public function test_update_event_start_day_no_permission() {
1564          $generator = $this->getDataGenerator();
1565          $user = $generator->create_user();
1566          $roleid = $generator->create_role();
1567          $context = \context_system::instance();
1568          $originalstarttime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
1569          $newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
1570          $expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
1571  
1572          $generator->role_assign($roleid, $user->id, $context->id);
1573          assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
1574  
1575          $this->setUser($user);
1576          $this->resetAfterTest(true);
1577  
1578          $event = $this->create_calendar_event(
1579              'Test event',
1580              $user->id,
1581              'user',
1582              0,
1583              null,
1584              [
1585                  'courseid' => 0,
1586                  'timestart' => $originalstarttime->getTimestamp()
1587              ]
1588          );
1589  
1590          assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
1591          $this->expectException('moodle_exception');
1592          $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp());
1593          $result = external_api::clean_returnvalue(
1594              core_calendar_external::update_event_start_day_returns(),
1595              $result
1596          );
1597      }
1598  
1599      /**
1600       * A user should not be able to update a module event.
1601       */
1602      public function test_update_event_start_day_module_event() {
1603          $generator = $this->getDataGenerator();
1604          $user = $generator->create_user();
1605          $course = $generator->create_course();
1606          $plugingenerator = $generator->get_plugin_generator('mod_assign');
1607          $moduleinstance = $plugingenerator->create_instance(['course' => $course->id]);
1608          $roleid = $generator->create_role();
1609          $context = \context_course::instance($course->id);
1610          $originalstarttime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
1611          $newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
1612          $expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
1613  
1614          $generator->role_assign($roleid, $user->id, $context->id);
1615          $generator->enrol_user($user->id, $course->id);
1616  
1617          $this->setUser($user);
1618          $this->resetAfterTest(true);
1619  
1620          $event = $this->create_calendar_event(
1621              'Test event',
1622              $user->id,
1623              'user',
1624              0,
1625              null,
1626              [
1627                  'modulename' => 'assign',
1628                  'instance' => $moduleinstance->id,
1629                  'courseid' => $course->id,
1630                  'timestart' => $originalstarttime->getTimestamp()
1631              ]
1632          );
1633  
1634          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1635          $this->expectException('moodle_exception');
1636          $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp());
1637          $result = external_api::clean_returnvalue(
1638              core_calendar_external::update_event_start_day_returns(),
1639              $result
1640          );
1641      }
1642  
1643      /**
1644       * Submit a request where the time duration until is earlier than the time
1645       * start in order to get a validation error from the server.
1646       */
1647      public function test_submit_create_update_form_validation_error() {
1648          $user = $this->getDataGenerator()->create_user();
1649          $timestart = new DateTime();
1650          $interval = new DateInterval("P1D"); // One day.
1651          $timedurationuntil = new DateTime();
1652          $timedurationuntil->sub($interval);
1653          $formdata = [
1654              'id' => 0,
1655              'userid' => $user->id,
1656              'modulename' => '',
1657              'instance' => 0,
1658              'visible' => 1,
1659              'name' => 'Test',
1660              'timestart' => [
1661                  'day' => $timestart->format('j'),
1662                  'month' => $timestart->format('n'),
1663                  'year' => $timestart->format('Y'),
1664                  'hour' => $timestart->format('G'),
1665                  'minute' => 0,
1666              ],
1667              'eventtype' => 'user',
1668              'description' => [
1669                  'text' => '',
1670                  'format' => 1,
1671              ],
1672              'location' => 'Test',
1673              'duration' => 1,
1674              'timedurationuntil' => [
1675                  'day' => $timedurationuntil->format('j'),
1676                  'month' => $timedurationuntil->format('n'),
1677                  'year' => $timedurationuntil->format('Y'),
1678                  'hour' => $timedurationuntil->format('G'),
1679                  'minute' => 0,
1680              ]
1681          ];
1682  
1683          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1684  
1685          $querystring = http_build_query($formdata, '', '&amp;');
1686  
1687          $this->resetAfterTest(true);
1688          $this->setUser($user);
1689  
1690          $result = external_api::clean_returnvalue(
1691              core_calendar_external::submit_create_update_form_returns(),
1692              core_calendar_external::submit_create_update_form($querystring)
1693          );
1694  
1695          $this->assertTrue($result['validationerror']);
1696      }
1697  
1698      /**
1699       * A user with the moodle/calendar:manageownentries capability at the
1700       * system context should be able to create a user event.
1701       */
1702      public function test_submit_create_update_form_create_user_event() {
1703          $generator = $this->getDataGenerator();
1704          $user = $generator->create_user();
1705          $roleid = $generator->create_role();
1706          $context = \context_system::instance();
1707          $timestart = new DateTime();
1708          $interval = new DateInterval("P1D"); // One day.
1709          $timedurationuntil = new DateTime();
1710          $timedurationuntil->add($interval);
1711          $formdata = [
1712              'id' => 0,
1713              'userid' => $user->id,
1714              'modulename' => '',
1715              'instance' => 0,
1716              'visible' => 1,
1717              'name' => 'Test',
1718              'timestart' => [
1719                  'day' => $timestart->format('j'),
1720                  'month' => $timestart->format('n'),
1721                  'year' => $timestart->format('Y'),
1722                  'hour' => $timestart->format('G'),
1723                  'minute' => 0,
1724              ],
1725              'eventtype' => 'user',
1726              'description' => [
1727                  'text' => '',
1728                  'format' => 1,
1729                  'itemid' => 0
1730              ],
1731              'location' => 'Test',
1732              'duration' => 1,
1733              'timedurationuntil' => [
1734                  'day' => $timedurationuntil->format('j'),
1735                  'month' => $timedurationuntil->format('n'),
1736                  'year' => $timedurationuntil->format('Y'),
1737                  'hour' => $timedurationuntil->format('G'),
1738                  'minute' => 0,
1739              ]
1740          ];
1741  
1742          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1743          $querystring = http_build_query($formdata, '', '&');
1744  
1745          $generator->role_assign($roleid, $user->id, $context->id);
1746          assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
1747  
1748          $user->ignoresesskey = true;
1749          $this->resetAfterTest(true);
1750          $this->setUser($user);
1751  
1752          $result = external_api::clean_returnvalue(
1753              core_calendar_external::submit_create_update_form_returns(),
1754              core_calendar_external::submit_create_update_form($querystring)
1755          );
1756  
1757          $event = $result['event'];
1758          $this->assertEquals($user->id, $event['userid']);
1759          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
1760          $this->assertEquals($formdata['name'], $event['name']);
1761      }
1762  
1763      /**
1764       * A user without the moodle/calendar:manageownentries capability at the
1765       * system context should not be able to create a user event.
1766       */
1767      public function test_submit_create_update_form_create_user_event_no_permission() {
1768          $generator = $this->getDataGenerator();
1769          $user = $generator->create_user();
1770          $roleid = $generator->create_role();
1771          $context = \context_system::instance();
1772          $timestart = new DateTime();
1773          $interval = new DateInterval("P1D"); // One day.
1774          $timedurationuntil = new DateTime();
1775          $timedurationuntil->add($interval);
1776          $formdata = [
1777              'id' => 0,
1778              'userid' => $user->id,
1779              'modulename' => '',
1780              'instance' => 0,
1781              'visible' => 1,
1782              'name' => 'Test',
1783              'timestart' => [
1784                  'day' => $timestart->format('j'),
1785                  'month' => $timestart->format('n'),
1786                  'year' => $timestart->format('Y'),
1787                  'hour' => $timestart->format('G'),
1788                  'minute' => 0,
1789              ],
1790              'eventtype' => 'user',
1791              'description' => [
1792                  'text' => '',
1793                  'format' => 1,
1794              ],
1795              'location' => 'Test',
1796              'duration' => 1,
1797              'timedurationuntil' => [
1798                  'day' => $timedurationuntil->format('j'),
1799                  'month' => $timedurationuntil->format('n'),
1800                  'year' => $timedurationuntil->format('Y'),
1801                  'hour' => $timedurationuntil->format('G'),
1802                  'minute' => 0,
1803              ]
1804          ];
1805  
1806          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1807          $querystring = http_build_query($formdata, '', '&');
1808  
1809          $generator->role_assign($roleid, $user->id, $context->id);
1810          assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
1811  
1812          $user->ignoresesskey = true;
1813          $this->resetAfterTest(true);
1814          $this->setUser($user);
1815  
1816          $this->expectException(moodle_exception::class);
1817          external_api::clean_returnvalue(
1818              core_calendar_external::submit_create_update_form_returns(),
1819              core_calendar_external::submit_create_update_form($querystring)
1820          );
1821      }
1822  
1823      /**
1824       * A user with the moodle/calendar:manageentries capability at the
1825       * site course context should be able to create a site event.
1826       */
1827      public function test_submit_create_update_form_create_site_event() {
1828          $generator = $this->getDataGenerator();
1829          $user = $generator->create_user();
1830          $context = context_system::instance();
1831          $roleid = $generator->create_role();
1832          $timestart = new DateTime();
1833          $interval = new DateInterval("P1D"); // One day.
1834          $timedurationuntil = new DateTime();
1835          $timedurationuntil->add($interval);
1836          $formdata = [
1837              'id' => 0,
1838              'userid' => $user->id,
1839              'modulename' => '',
1840              'instance' => 0,
1841              'visible' => 1,
1842              'name' => 'Test',
1843              'timestart' => [
1844                  'day' => $timestart->format('j'),
1845                  'month' => $timestart->format('n'),
1846                  'year' => $timestart->format('Y'),
1847                  'hour' => $timestart->format('G'),
1848                  'minute' => 0,
1849              ],
1850              'eventtype' => 'site',
1851              'description' => [
1852                  'text' => '',
1853                  'format' => 1,
1854                  'itemid' => 0
1855              ],
1856              'location' => 'Test',
1857              'duration' => 1,
1858              'timedurationuntil' => [
1859                  'day' => $timedurationuntil->format('j'),
1860                  'month' => $timedurationuntil->format('n'),
1861                  'year' => $timedurationuntil->format('Y'),
1862                  'hour' => $timedurationuntil->format('G'),
1863                  'minute' => 0,
1864              ]
1865          ];
1866  
1867          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1868          $querystring = http_build_query($formdata, '', '&');
1869  
1870          $generator->role_assign($roleid, $user->id, $context->id);
1871  
1872          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1873  
1874          $user->ignoresesskey = true;
1875          $this->resetAfterTest(true);
1876          $this->setUser($user);
1877  
1878          $result = external_api::clean_returnvalue(
1879              core_calendar_external::submit_create_update_form_returns(),
1880              core_calendar_external::submit_create_update_form($querystring)
1881          );
1882  
1883          $event = $result['event'];
1884          $this->assertEquals($user->id, $event['userid']);
1885          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
1886          $this->assertEquals($formdata['name'], $event['name']);
1887      }
1888  
1889      /**
1890       * A user without the moodle/calendar:manageentries capability at the
1891       * site course context should not be able to create a site event.
1892       */
1893      public function test_submit_create_update_form_create_site_event_no_permission() {
1894          $generator = $this->getDataGenerator();
1895          $user = $generator->create_user();
1896          $context = context_course::instance(SITEID);
1897          $roleid = $generator->create_role();
1898          $timestart = new DateTime();
1899          $interval = new DateInterval("P1D"); // One day.
1900          $timedurationuntil = new DateTime();
1901          $timedurationuntil->add($interval);
1902          $formdata = [
1903              'id' => 0,
1904              'userid' => $user->id,
1905              'modulename' => '',
1906              'instance' => 0,
1907              'visible' => 1,
1908              'name' => 'Test',
1909              'timestart' => [
1910                  'day' => $timestart->format('j'),
1911                  'month' => $timestart->format('n'),
1912                  'year' => $timestart->format('Y'),
1913                  'hour' => $timestart->format('G'),
1914                  'minute' => 0,
1915              ],
1916              'eventtype' => 'site',
1917              'description' => [
1918                  'text' => '',
1919                  'format' => 1,
1920              ],
1921              'location' => 'Test',
1922              'duration' => 1,
1923              'timedurationuntil' => [
1924                  'day' => $timedurationuntil->format('j'),
1925                  'month' => $timedurationuntil->format('n'),
1926                  'year' => $timedurationuntil->format('Y'),
1927                  'hour' => $timedurationuntil->format('G'),
1928                  'minute' => 0,
1929              ]
1930          ];
1931  
1932          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1933          $querystring = http_build_query($formdata, '', '&');
1934  
1935          $generator->role_assign($roleid, $user->id, $context->id);
1936  
1937          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
1938  
1939          $user->ignoresesskey = true;
1940          $this->resetAfterTest(true);
1941          $this->setUser($user);
1942  
1943          $result = external_api::clean_returnvalue(
1944              core_calendar_external::submit_create_update_form_returns(),
1945              core_calendar_external::submit_create_update_form($querystring)
1946          );
1947  
1948          $this->assertTrue($result['validationerror']);
1949      }
1950  
1951      /**
1952       * A user that has the moodle/calendar:manageentries in a course that they
1953       * are enrolled in should be able to create a course event in that course.
1954       */
1955      public function test_submit_create_update_form_create_course_event() {
1956          $generator = $this->getDataGenerator();
1957          $user = $generator->create_user();
1958          $course = $generator->create_course();
1959          $context = context_course::instance($course->id);
1960          $roleid = $generator->create_role();
1961          $timestart = new DateTime();
1962          $interval = new DateInterval("P1D"); // One day.
1963          $timedurationuntil = new DateTime();
1964          $timedurationuntil->add($interval);
1965          $formdata = [
1966              'id' => 0,
1967              'userid' => $user->id,
1968              'modulename' => '',
1969              'instance' => 0,
1970              'visible' => 1,
1971              'name' => 'Test',
1972              'timestart' => [
1973                  'day' => $timestart->format('j'),
1974                  'month' => $timestart->format('n'),
1975                  'year' => $timestart->format('Y'),
1976                  'hour' => $timestart->format('G'),
1977                  'minute' => 0,
1978              ],
1979              'eventtype' => 'course',
1980              'courseid' => $course->id,
1981              'description' => [
1982                  'text' => '',
1983                  'format' => 1,
1984                  'itemid' => 0,
1985              ],
1986              'location' => 'Test',
1987              'duration' => 1,
1988              'timedurationuntil' => [
1989                  'day' => $timedurationuntil->format('j'),
1990                  'month' => $timedurationuntil->format('n'),
1991                  'year' => $timedurationuntil->format('Y'),
1992                  'hour' => $timedurationuntil->format('G'),
1993                  'minute' => 0,
1994              ]
1995          ];
1996  
1997          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1998          $querystring = http_build_query($formdata, '', '&');
1999  
2000          $generator->enrol_user($user->id, $course->id, 'student');
2001          $generator->role_assign($roleid, $user->id, $context->id);
2002  
2003          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2004  
2005          $user->ignoresesskey = true;
2006          $this->resetAfterTest(true);
2007          $this->setUser($user);
2008  
2009          $result = external_api::clean_returnvalue(
2010              core_calendar_external::submit_create_update_form_returns(),
2011              core_calendar_external::submit_create_update_form($querystring)
2012          );
2013  
2014          $event = $result['event'];
2015          $this->assertEquals($user->id, $event['userid']);
2016          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2017          $this->assertEquals($formdata['name'], $event['name']);
2018          $this->assertEquals($formdata['courseid'], $event['course']['id']);
2019      }
2020  
2021      /**
2022       * A user without the moodle/calendar:manageentries capability in a course
2023       * that they are enrolled in should not be able to create a course event in that course.
2024       */
2025      public function test_submit_create_update_form_create_course_event_no_permission() {
2026          $generator = $this->getDataGenerator();
2027          $user = $generator->create_user();
2028          $course = $generator->create_course();
2029          $context = context_course::instance($course->id);
2030          $roleid = $generator->create_role();
2031          $timestart = new DateTime();
2032          $interval = new DateInterval("P1D"); // One day.
2033          $timedurationuntil = new DateTime();
2034          $timedurationuntil->add($interval);
2035          $formdata = [
2036              'id' => 0,
2037              'userid' => $user->id,
2038              'modulename' => '',
2039              'instance' => 0,
2040              'visible' => 1,
2041              'name' => 'Test',
2042              'timestart' => [
2043                  'day' => $timestart->format('j'),
2044                  'month' => $timestart->format('n'),
2045                  'year' => $timestart->format('Y'),
2046                  'hour' => $timestart->format('G'),
2047                  'minute' => 0,
2048              ],
2049              'eventtype' => 'course',
2050              'courseid' => $course->id,
2051              'description' => [
2052                  'text' => '',
2053                  'format' => 1,
2054              ],
2055              'location' => 'Test',
2056              'duration' => 1,
2057              'timedurationuntil' => [
2058                  'day' => $timedurationuntil->format('j'),
2059                  'month' => $timedurationuntil->format('n'),
2060                  'year' => $timedurationuntil->format('Y'),
2061                  'hour' => $timedurationuntil->format('G'),
2062                  'minute' => 0,
2063              ]
2064          ];
2065  
2066          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2067          $querystring = http_build_query($formdata, '', '&');
2068  
2069          $generator->enrol_user($user->id, $course->id, 'student');
2070          $generator->role_assign($roleid, $user->id, $context->id);
2071  
2072          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
2073  
2074          $user->ignoresesskey = true;
2075          $this->resetAfterTest(true);
2076          $this->setUser($user);
2077  
2078          $result = external_api::clean_returnvalue(
2079              core_calendar_external::submit_create_update_form_returns(),
2080              core_calendar_external::submit_create_update_form($querystring)
2081          );
2082  
2083          $this->assertTrue($result['validationerror']);
2084      }
2085  
2086      /**
2087       * A user should not be able to create an event for a course that they are
2088       * not enrolled in.
2089       */
2090      public function test_submit_create_update_form_create_course_event_not_enrolled() {
2091          $generator = $this->getDataGenerator();
2092          $user = $generator->create_user();
2093          $course = $generator->create_course();
2094          $course2 = $generator->create_course();
2095          $context = context_course::instance($course->id);
2096          $roleid = $generator->create_role();
2097          $timestart = new DateTime();
2098          $interval = new DateInterval("P1D"); // One day.
2099          $timedurationuntil = new DateTime();
2100          $timedurationuntil->add($interval);
2101          $formdata = [
2102              'id' => 0,
2103              'userid' => $user->id,
2104              'modulename' => '',
2105              'instance' => 0,
2106              'visible' => 1,
2107              'name' => 'Test',
2108              'timestart' => [
2109                  'day' => $timestart->format('j'),
2110                  'month' => $timestart->format('n'),
2111                  'year' => $timestart->format('Y'),
2112                  'hour' => $timestart->format('G'),
2113                  'minute' => 0,
2114              ],
2115              'eventtype' => 'course',
2116              'courseid' => $course2->id, // Not enrolled.
2117              'description' => [
2118                  'text' => '',
2119                  'format' => 1,
2120              ],
2121              'location' => 'Test',
2122              'duration' => 1,
2123              'timedurationuntil' => [
2124                  'day' => $timedurationuntil->format('j'),
2125                  'month' => $timedurationuntil->format('n'),
2126                  'year' => $timedurationuntil->format('Y'),
2127                  'hour' => $timedurationuntil->format('G'),
2128                  'minute' => 0,
2129              ]
2130          ];
2131  
2132          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2133          $querystring = http_build_query($formdata, '', '&');
2134  
2135          $generator->enrol_user($user->id, $course->id, 'student');
2136          $generator->role_assign($roleid, $user->id, $context->id);
2137  
2138          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2139  
2140          $user->ignoresesskey = true;
2141          $this->resetAfterTest(true);
2142          $this->setUser($user);
2143  
2144          $result = external_api::clean_returnvalue(
2145              core_calendar_external::submit_create_update_form_returns(),
2146              core_calendar_external::submit_create_update_form($querystring)
2147          );
2148  
2149          $this->assertTrue($result['validationerror']);
2150      }
2151  
2152      /**
2153       * A user should be able to create an event for a group that they are a member of in
2154       * a course in which they are enrolled and have the moodle/calendar:manageentries capability.
2155       */
2156      public function test_submit_create_update_form_create_group_event_group_member_manage_course() {
2157          $generator = $this->getDataGenerator();
2158          $user = $generator->create_user();
2159          $course = $generator->create_course();
2160          $group = $generator->create_group(array('courseid' => $course->id));
2161          $context = context_course::instance($course->id);
2162          $roleid = $generator->create_role();
2163          $timestart = new DateTime();
2164          $interval = new DateInterval("P1D"); // One day.
2165          $timedurationuntil = new DateTime();
2166          $timedurationuntil->add($interval);
2167          $formdata = [
2168              'id' => 0,
2169              'userid' => $user->id,
2170              'modulename' => '',
2171              'instance' => 0,
2172              'visible' => 1,
2173              'name' => 'Test',
2174              'timestart' => [
2175                  'day' => $timestart->format('j'),
2176                  'month' => $timestart->format('n'),
2177                  'year' => $timestart->format('Y'),
2178                  'hour' => $timestart->format('G'),
2179                  'minute' => 0,
2180              ],
2181              'eventtype' => 'group',
2182              'groupid' => $group->id,
2183              'groupcourseid' => $course->id,
2184              'description' => [
2185                  'text' => '',
2186                  'format' => 1,
2187                  'itemid' => 0
2188              ],
2189              'location' => 'Test',
2190              'duration' => 1,
2191              'timedurationuntil' => [
2192                  'day' => $timedurationuntil->format('j'),
2193                  'month' => $timedurationuntil->format('n'),
2194                  'year' => $timedurationuntil->format('Y'),
2195                  'hour' => $timedurationuntil->format('G'),
2196                  'minute' => 0,
2197              ]
2198          ];
2199  
2200          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2201          $querystring = http_build_query($formdata, '', '&');
2202  
2203          $generator->enrol_user($user->id, $course->id, 'student');
2204          $generator->role_assign($roleid, $user->id, $context->id);
2205          $generator->create_group_member(['groupid' => $group->id, 'userid' => $user->id]);
2206  
2207          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2208  
2209          $user->ignoresesskey = true;
2210          $this->resetAfterTest(true);
2211          $this->setUser($user);
2212  
2213          $result = external_api::clean_returnvalue(
2214              core_calendar_external::submit_create_update_form_returns(),
2215              core_calendar_external::submit_create_update_form($querystring)
2216          );
2217  
2218          $event = $result['event'];
2219          $this->assertEquals($user->id, $event['userid']);
2220          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2221          $this->assertEquals($formdata['name'], $event['name']);
2222          $this->assertEquals($group->id, $event['groupid']);
2223      }
2224  
2225      /**
2226       * A user should be able to create an event for a group that they are a member of in
2227       * a course in which they are enrolled and have the moodle/calendar:managegroupentries capability.
2228       */
2229      public function test_submit_create_update_form_create_group_event_group_member_manage_group_entries() {
2230          $generator = $this->getDataGenerator();
2231          $user = $generator->create_user();
2232          $course = $generator->create_course();
2233          $group = $generator->create_group(array('courseid' => $course->id));
2234          $context = context_course::instance($course->id);
2235          $roleid = $generator->create_role();
2236          $timestart = new DateTime();
2237          $interval = new DateInterval("P1D"); // One day.
2238          $timedurationuntil = new DateTime();
2239          $timedurationuntil->add($interval);
2240          $formdata = [
2241              'id' => 0,
2242              'userid' => $user->id,
2243              'modulename' => '',
2244              'instance' => 0,
2245              'visible' => 1,
2246              'name' => 'Test',
2247              'timestart' => [
2248                  'day' => $timestart->format('j'),
2249                  'month' => $timestart->format('n'),
2250                  'year' => $timestart->format('Y'),
2251                  'hour' => $timestart->format('G'),
2252                  'minute' => 0,
2253              ],
2254              'eventtype' => 'group',
2255              'groupid' => $group->id,
2256              'groupcourseid' => $course->id,
2257              'description' => [
2258                  'text' => '',
2259                  'format' => 1,
2260                  'itemid' => 0
2261              ],
2262              'location' => 'Test',
2263              'duration' => 1,
2264              'timedurationuntil' => [
2265                  'day' => $timedurationuntil->format('j'),
2266                  'month' => $timedurationuntil->format('n'),
2267                  'year' => $timedurationuntil->format('Y'),
2268                  'hour' => $timedurationuntil->format('G'),
2269                  'minute' => 0,
2270              ]
2271          ];
2272  
2273          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2274          $querystring = http_build_query($formdata, '', '&');
2275  
2276          $generator->enrol_user($user->id, $course->id, 'student');
2277          $generator->role_assign($roleid, $user->id, $context->id);
2278          $generator->create_group_member(['groupid' => $group->id, 'userid' => $user->id]);
2279  
2280          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
2281          assign_capability('moodle/calendar:managegroupentries', CAP_ALLOW, $roleid, $context, true);
2282  
2283          $user->ignoresesskey = true;
2284          $this->resetAfterTest(true);
2285          $this->setUser($user);
2286  
2287          $result = external_api::clean_returnvalue(
2288              core_calendar_external::submit_create_update_form_returns(),
2289              core_calendar_external::submit_create_update_form($querystring)
2290          );
2291  
2292          $event = $result['event'];
2293          $this->assertEquals($user->id, $event['userid']);
2294          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2295          $this->assertEquals($formdata['name'], $event['name']);
2296          $this->assertEquals($group->id, $event['groupid']);
2297      }
2298  
2299      /**
2300       * A user should be able to create an event for any group in a course in which
2301       * they are enrolled and have the moodle/site:accessallgroups capability.
2302       */
2303      public function test_submit_create_update_form_create_group_event_access_all_groups() {
2304          $generator = $this->getDataGenerator();
2305          $user = $generator->create_user();
2306          $course = $generator->create_course();
2307          $group = $generator->create_group(array('courseid' => $course->id));
2308          $context = context_course::instance($course->id);
2309          $roleid = $generator->create_role();
2310          $timestart = new DateTime();
2311          $interval = new DateInterval("P1D"); // One day.
2312          $timedurationuntil = new DateTime();
2313          $timedurationuntil->add($interval);
2314          $formdata = [
2315              'id' => 0,
2316              'userid' => $user->id,
2317              'modulename' => '',
2318              'instance' => 0,
2319              'visible' => 1,
2320              'name' => 'Test',
2321              'timestart' => [
2322                  'day' => $timestart->format('j'),
2323                  'month' => $timestart->format('n'),
2324                  'year' => $timestart->format('Y'),
2325                  'hour' => $timestart->format('G'),
2326                  'minute' => 0,
2327              ],
2328              'eventtype' => 'group',
2329              'groupid' => $group->id,
2330              'groupcourseid' => $course->id,
2331              'description' => [
2332                  'text' => '',
2333                  'format' => 1,
2334                  'itemid' => 0
2335              ],
2336              'location' => 'Test',
2337              'duration' => 1,
2338              'timedurationuntil' => [
2339                  'day' => $timedurationuntil->format('j'),
2340                  'month' => $timedurationuntil->format('n'),
2341                  'year' => $timedurationuntil->format('Y'),
2342                  'hour' => $timedurationuntil->format('G'),
2343                  'minute' => 0,
2344              ]
2345          ];
2346  
2347          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2348          $querystring = http_build_query($formdata, '', '&');
2349  
2350          $generator->enrol_user($user->id, $course->id, 'student');
2351          $generator->role_assign($roleid, $user->id, $context->id);
2352  
2353          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2354          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context, true);
2355  
2356          $user->ignoresesskey = true;
2357          $this->resetAfterTest(true);
2358          $this->setUser($user);
2359  
2360          $result = external_api::clean_returnvalue(
2361              core_calendar_external::submit_create_update_form_returns(),
2362              core_calendar_external::submit_create_update_form($querystring)
2363          );
2364  
2365          $event = $result['event'];
2366          $this->assertEquals($user->id, $event['userid']);
2367          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2368          $this->assertEquals($formdata['name'], $event['name']);
2369          $this->assertEquals($group->id, $event['groupid']);
2370      }
2371  
2372      /**
2373       * A user should not be able to create an event for any group that they are not a
2374       * member of in a course in which they are enrolled but don't have the
2375       * moodle/site:accessallgroups capability.
2376       */
2377      public function test_submit_create_update_form_create_group_event_non_member_no_permission() {
2378          $generator = $this->getDataGenerator();
2379          $user = $generator->create_user();
2380          $course = $generator->create_course();
2381          $group = $generator->create_group(array('courseid' => $course->id));
2382          $context = context_course::instance($course->id);
2383          $roleid = $generator->create_role();
2384          $timestart = new DateTime();
2385          $interval = new DateInterval("P1D"); // One day.
2386          $timedurationuntil = new DateTime();
2387          $timedurationuntil->add($interval);
2388          $formdata = [
2389              'id' => 0,
2390              'userid' => $user->id,
2391              'modulename' => '',
2392              'instance' => 0,
2393              'visible' => 1,
2394              'name' => 'Test',
2395              'timestart' => [
2396                  'day' => $timestart->format('j'),
2397                  'month' => $timestart->format('n'),
2398                  'year' => $timestart->format('Y'),
2399                  'hour' => $timestart->format('G'),
2400                  'minute' => 0,
2401              ],
2402              'eventtype' => 'group',
2403              'groupid' => $group->id,
2404              'groupcourseid' => $course->id,
2405              'description' => [
2406                  'text' => '',
2407                  'format' => 1,
2408              ],
2409              'location' => 'Test',
2410              'duration' => 1,
2411              'timedurationuntil' => [
2412                  'day' => $timedurationuntil->format('j'),
2413                  'month' => $timedurationuntil->format('n'),
2414                  'year' => $timedurationuntil->format('Y'),
2415                  'hour' => $timedurationuntil->format('G'),
2416                  'minute' => 0,
2417              ]
2418          ];
2419  
2420          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2421          $querystring = http_build_query($formdata, '', '&');
2422  
2423          $generator->enrol_user($user->id, $course->id, 'student');
2424          $generator->role_assign($roleid, $user->id, $context->id);
2425  
2426          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2427          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
2428  
2429          $user->ignoresesskey = true;
2430          $this->resetAfterTest(true);
2431          $this->setUser($user);
2432  
2433          $result = external_api::clean_returnvalue(
2434              core_calendar_external::submit_create_update_form_returns(),
2435              core_calendar_external::submit_create_update_form($querystring)
2436          );
2437  
2438          $this->assertTrue($result['validationerror']);
2439      }
2440  
2441      /**
2442       * A user should not be able load the calendar monthly view for a course they cannot access.
2443       */
2444      public function test_get_calendar_monthly_view_no_course_permission() {
2445          global $USER;
2446          $this->resetAfterTest(true);
2447          $this->setAdminUser();
2448  
2449          $generator = $this->getDataGenerator();
2450          $user1 = $generator->create_user();
2451          $user2 = $generator->create_user();
2452          $course = $generator->create_course();
2453          $generator->enrol_user($user1->id, $course->id, 'student');
2454          $name = 'Course Event (course' . $course->id . ')';
2455          $record = new stdClass();
2456          $record->courseid = $course->id;
2457          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2458  
2459          $timestart = new DateTime();
2460          // Admin can load the course.
2461          $data = external_api::clean_returnvalue(
2462              core_calendar_external::get_calendar_monthly_view_returns(),
2463              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2464                                                                $course->id, null, false, true, $timestart->format('j'))
2465          );
2466          $this->assertEquals($data['courseid'], $course->id);
2467          // User enrolled in the course can load the course calendar.
2468          $this->setUser($user1);
2469          $data = external_api::clean_returnvalue(
2470              core_calendar_external::get_calendar_monthly_view_returns(),
2471              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2472                                                                $course->id, null, false, true, $timestart->format('j'))
2473          );
2474          $this->assertEquals($data['courseid'], $course->id);
2475          // User not enrolled in the course cannot load the course calendar.
2476          $this->setUser($user2);
2477          $this->expectException('require_login_exception');
2478          $data = external_api::clean_returnvalue(
2479              core_calendar_external::get_calendar_monthly_view_returns(),
2480              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2481                                                                $course->id, null, false, false, $timestart->format('j'))
2482          );
2483      }
2484  
2485      /**
2486       * Test get_calendar_monthly_view when a day parameter is provided.
2487       */
2488      public function test_get_calendar_monthly_view_with_day_provided() {
2489          $this->resetAfterTest();
2490          $this->setAdminUser();
2491  
2492          $timestart = new DateTime();
2493          $data = external_api::clean_returnvalue(
2494              core_calendar_external::get_calendar_monthly_view_returns(),
2495              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2496                                                                SITEID, null, false, true, $timestart->format('j'))
2497          );
2498          $this->assertEquals($data['date']['mday'], $timestart->format('d'));
2499      }
2500  
2501      /**
2502       * A user should not be able load the calendar day view for a course they cannot access.
2503       */
2504      public function test_get_calendar_day_view_no_course_permission() {
2505          global $USER;
2506          $this->resetAfterTest(true);
2507          $this->setAdminUser();
2508  
2509          $generator = $this->getDataGenerator();
2510          $user1 = $generator->create_user();
2511          $user2 = $generator->create_user();
2512          $course = $generator->create_course();
2513          $generator->enrol_user($user1->id, $course->id, 'student');
2514          $name = 'Course Event (course' . $course->id . ')';
2515          $record = new stdClass();
2516          $record->courseid = $course->id;
2517          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2518  
2519          $timestart = new DateTime();
2520          // Admin can load the course.
2521          $data = external_api::clean_returnvalue(
2522              core_calendar_external::get_calendar_day_view_returns(),
2523              core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'),
2524                                                            $timestart->format('j'), $course->id, null)
2525          );
2526          $this->assertEquals($data['courseid'], $course->id);
2527          // User enrolled in the course can load the course calendar.
2528          $this->setUser($user1);
2529          $data = external_api::clean_returnvalue(
2530              core_calendar_external::get_calendar_day_view_returns(),
2531              core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'),
2532                                                            $timestart->format('j'), $course->id, null)
2533          );
2534          $this->assertEquals($data['courseid'], $course->id);
2535          // User not enrolled in the course cannot load the course calendar.
2536          $this->setUser($user2);
2537          $this->expectException('require_login_exception');
2538          $data = external_api::clean_returnvalue(
2539              core_calendar_external::get_calendar_day_view_returns(),
2540              core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'),
2541                                                            $timestart->format('j'), $course->id, null)
2542          );
2543      }
2544  
2545      /**
2546       * A user should not be able load the calendar upcoming view for a course they cannot access.
2547       */
2548      public function test_get_calendar_upcoming_view_no_course_permission() {
2549          global $USER;
2550          $this->resetAfterTest(true);
2551          $this->setAdminUser();
2552  
2553          $generator = $this->getDataGenerator();
2554          $user1 = $generator->create_user();
2555          $user2 = $generator->create_user();
2556          $course = $generator->create_course();
2557          $generator->enrol_user($user1->id, $course->id, 'student');
2558          $name = 'Course Event (course' . $course->id . ')';
2559          $record = new stdClass();
2560          $record->courseid = $course->id;
2561          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2562  
2563          // Admin can load the course.
2564          $data = external_api::clean_returnvalue(
2565              core_calendar_external::get_calendar_upcoming_view_returns(),
2566              core_calendar_external::get_calendar_upcoming_view($course->id, null)
2567          );
2568          $this->assertEquals($data['courseid'], $course->id);
2569          // User enrolled in the course can load the course calendar.
2570          $this->setUser($user1);
2571          $data = external_api::clean_returnvalue(
2572              core_calendar_external::get_calendar_upcoming_view_returns(),
2573              core_calendar_external::get_calendar_upcoming_view($course->id, null)
2574          );
2575          $this->assertEquals($data['courseid'], $course->id);
2576          // User not enrolled in the course cannot load the course calendar.
2577          $this->setUser($user2);
2578          $this->expectException('require_login_exception');
2579          $data = external_api::clean_returnvalue(
2580              core_calendar_external::get_calendar_upcoming_view_returns(),
2581              core_calendar_external::get_calendar_upcoming_view($course->id, null)
2582          );
2583      }
2584  
2585      /**
2586       * A user should not be able load the calendar event for a course they cannot access.
2587       */
2588      public function test_get_calendar_event_by_id_no_course_permission() {
2589          global $USER;
2590          $this->resetAfterTest(true);
2591          $this->setAdminUser();
2592  
2593          $generator = $this->getDataGenerator();
2594          $user1 = $generator->create_user();
2595          $user2 = $generator->create_user();
2596          $course = $generator->create_course();
2597          $generator->enrol_user($user1->id, $course->id, 'student');
2598          $name = 'Course Event (course' . $course->id . ')';
2599          $record = new stdClass();
2600          $record->courseid = $course->id;
2601          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2602  
2603          // Admin can load the course event.
2604          $data = external_api::clean_returnvalue(
2605              core_calendar_external::get_calendar_event_by_id_returns(),
2606              core_calendar_external::get_calendar_event_by_id($courseevent->id)
2607          );
2608          $this->assertEquals($data['event']['id'], $courseevent->id);
2609          // User enrolled in the course can load the course event.
2610          $this->setUser($user1);
2611          $data = external_api::clean_returnvalue(
2612              core_calendar_external::get_calendar_event_by_id_returns(),
2613              core_calendar_external::get_calendar_event_by_id($courseevent->id)
2614          );
2615          $this->assertEquals($data['event']['id'], $courseevent->id);
2616          // User not enrolled in the course cannot load the course event.
2617          $this->setUser($user2);
2618          $this->expectException(moodle_exception::class);
2619          $data = external_api::clean_returnvalue(
2620              core_calendar_external::get_calendar_event_by_id_returns(),
2621              core_calendar_external::get_calendar_event_by_id($courseevent->id)
2622          );
2623      }
2624  
2625      /**
2626       * User data for testing reading calendar events.
2627       *
2628       * @return array
2629       */
2630      public function test_get_calendar_event_by_id_prevent_read_other_users_events_data_provider(): array {
2631          $syscontext = context_system::instance();
2632          $managerrole = 'manager';
2633          return [
2634              [true, false, $syscontext, $managerrole, true],
2635              [false, false, $syscontext, $managerrole, false],
2636              [false, false, null, null, true],
2637              [false, true, null, null, false],
2638          ];
2639      }
2640  
2641      /**
2642       * Prevent user from reading other user's event.
2643       *
2644       * @covers \core_calendar_external::get_calendar_event_by_id
2645       * @dataProvider test_get_calendar_event_by_id_prevent_read_other_users_events_data_provider
2646       *
2647       * @param bool          $isadminevent      Is admin's event
2648       * @param bool          $isadmin           Is current user admin user
2649       * @param null|stdClass $readerrolecontext Reader role context
2650       * @param null|string   $readerrolename    Role name
2651       * @param bool          $expectexception   Should the test throw exception
2652       */
2653      public function test_get_calendar_event_by_id_prevent_read_other_users_events(
2654              bool $isadminevent, bool $isadmin, ?stdClass $readerrolecontext,
2655              ?string $readerrolename, bool $expectexception) {
2656          global $USER, $DB;
2657  
2658          $this->resetAfterTest();
2659          $generator = $this->getDataGenerator();
2660  
2661          if ($isadminevent) {
2662              $this->setAdminUser();
2663          } else {
2664              $user = $generator->create_user();
2665              $this->setUser($user);
2666          }
2667          $userevent = $this->create_calendar_event('user event', $USER->id, 'user', 0, time());
2668          $results = external_api::clean_returnvalue(
2669              core_calendar_external::get_calendar_event_by_id_returns(),
2670              core_calendar_external::get_calendar_event_by_id($userevent->id)
2671          );
2672          $event = reset($results);
2673          $this->assertEquals($userevent->id, $event['id']);
2674  
2675          if ($isadmin) {
2676              $this->setAdminUser();
2677          } else {
2678              $reader = $generator->create_user();
2679              if ($readerrolename && $readerrolecontext) {
2680                  $managerroleid = $DB->get_field('role', 'id', ['shortname' => $readerrolename]);
2681                  role_assign($managerroleid, $reader->id, $readerrolecontext->id);
2682              }
2683              $this->setUser($reader);
2684          }
2685  
2686          if ($expectexception) {
2687              // Setup if exception is expected for the test.
2688              $this->expectException(moodle_exception::class);
2689          }
2690          external_api::clean_returnvalue(
2691              core_calendar_external::get_calendar_event_by_id_returns(),
2692              core_calendar_external::get_calendar_event_by_id($userevent->id)
2693          );
2694      }
2695  
2696      /**
2697       * User data for testing editing or deleting calendar events.
2698       *
2699       * @return array
2700       */
2701      public function test_edit_or_delete_other_users_events_data_provider(): array {
2702          $syscontext = context_system::instance();
2703          $managerrole = 'manager';
2704          return [
2705              [false, false, $syscontext, $managerrole, false],
2706              [false, true, $syscontext, $managerrole, true],
2707              [false, false, null, null, true],
2708              [true, false, null, null, false],
2709          ];
2710      }
2711  
2712      /**
2713       * Test the behavior of deleting other users' user events.
2714       *
2715       * @dataProvider test_edit_or_delete_other_users_events_data_provider
2716       * @covers \core_calendar_external::delete_calendar_events
2717       * @param bool          $isadmin Whether the current user is admin.
2718       * @param bool          $isadminevent Whether it's an admin event or not.
2719       * @param stdClass|null $writerrolecontext The reader role context.
2720       * @param string|null   $writerrolename The role name.
2721       * @param bool          $expectexception Whether the test should throw an exception or not.
2722       */
2723      public function test_delete_other_users_events(bool $isadmin, bool $isadminevent,
2724              ?stdClass $writerrolecontext, ?string $writerrolename, bool $expectexception) {
2725          global $DB, $USER;
2726  
2727          $this->resetAfterTest();
2728          $generator = $this->getDataGenerator();
2729  
2730          if ($isadminevent) {
2731              $this->setAdminUser();
2732              $user = $USER;
2733          } else {
2734              $user = $generator->create_user();
2735              $this->setUser($user);
2736          }
2737          $userevent = $this->create_calendar_event('user event', $user->id, 'user', 0, time());
2738  
2739          if ($isadmin) {
2740              $this->setAdminUser();
2741          } else {
2742              $writer = $generator->create_user();
2743              if ($writerrolename && $writerrolecontext) {
2744                  $managerroleid = $DB->get_field('role', 'id', ['shortname' => $writerrolename]);
2745                  role_assign($managerroleid, $writer->id, $writerrolecontext->id);
2746              }
2747              $this->setUser($writer);
2748          }
2749  
2750          if ($expectexception) {
2751              $this->expectException(moodle_exception::class);
2752          }
2753          $events = [
2754              ['eventid' => $userevent->id, 'repeat' => 0]
2755          ];
2756          core_calendar_external::delete_calendar_events($events);
2757      }
2758  
2759      /**
2760       * Test the behavior of editing other users' user events
2761       *
2762       * @dataProvider test_edit_or_delete_other_users_events_data_provider
2763       * @covers \core_calendar_external::submit_create_update_form
2764       * @param bool          $isadmin Whether the current user is admin.
2765       * @param bool          $isadminevent Whether it's an admin event or not.
2766       * @param stdClass|null $writerrolecontext The reader role context.
2767       * @param string|null   $writerrolename The role name.
2768       * @param bool          $expectexception Whether the test should throw an exception or not.
2769       */
2770      public function test_edit_other_users_events(bool $isadmin, bool $isadminevent,
2771              ?stdClass $writerrolecontext, ?string $writerrolename, bool $expectexception) {
2772          global $DB, $USER;
2773  
2774          $this->resetAfterTest();
2775  
2776          $generator = $this->getDataGenerator();
2777          if ($isadminevent) {
2778              $this->setAdminUser();
2779              $user = $USER;
2780          } else {
2781              $user = $generator->create_user();
2782          }
2783  
2784          $formdata = [
2785              'id' => 0,
2786              'userid' => $user->id,
2787              'modulename' => '',
2788              'instance' => 0,
2789              'visible' => 1,
2790              'eventtype' => 'user',
2791              'name' => 'Test',
2792              'timestart' => [
2793                  'day' => 1,
2794                  'month' => 1,
2795                  'year' => 2021,
2796                  'hour' => 1,
2797                  'minute' => 0,
2798              ],
2799              'description' => [
2800                  'text' => 'xxxxx',
2801                  'format' => 1,
2802                  'itemid' => 0
2803              ],
2804              'location' => 'Test',
2805              'duration' => 0,
2806          ];
2807          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2808  
2809          $querystring = http_build_query($formdata, '', '&');
2810  
2811          if ($isadmin) {
2812              $this->setAdminUser();
2813          } else {
2814              $writer = $generator->create_user();
2815              if ($writerrolename && $writerrolecontext) {
2816                  $managerroleid = $DB->get_field('role', 'id', ['shortname' => $writerrolename]);
2817                  role_assign($managerroleid, $writer->id, $writerrolecontext->id);
2818              }
2819              $this->setUser($writer);
2820          }
2821          $USER->ignoresesskey = true;
2822  
2823          if ($expectexception) {
2824              $this->expectException(moodle_exception::class);
2825          }
2826          core_calendar_external::submit_create_update_form($querystring);
2827      }
2828  
2829      /**
2830       * A user should not be able load the calendar events for a category they cannot see.
2831       */
2832      public function test_get_calendar_events_hidden_category() {
2833          global $USER;
2834          $this->resetAfterTest(true);
2835          $this->setAdminUser();
2836  
2837          $generator = $this->getDataGenerator();
2838          $user1 = $generator->create_user();
2839          $category = $generator->create_category(['visible' => 0]);
2840          $name = 'Category Event (category: ' . $category->id . ')';
2841          $record = new stdClass();
2842          $record->categoryid = $category->id;
2843          $categoryevent = $this->create_calendar_event($name, $USER->id, 'category', 0, time(), $record);
2844  
2845          $events = [
2846              'eventids' => [$categoryevent->id]
2847          ];
2848          $options = [];
2849          // Admin can load the category event.
2850          $data = external_api::clean_returnvalue(
2851              core_calendar_external::get_calendar_events_returns(),
2852              core_calendar_external::get_calendar_events($events, $options)
2853          );
2854          $this->assertEquals($data['events'][0]['id'], $categoryevent->id);
2855          // User with no special permission to see hidden categories will not see the event.
2856          $this->setUser($user1);
2857          $data = external_api::clean_returnvalue(
2858              core_calendar_external::get_calendar_events_returns(),
2859              core_calendar_external::get_calendar_events($events, $options)
2860          );
2861          $this->assertCount(0, $data['events']);
2862          $this->assertEquals('nopermissions', $data['warnings'][0]['warningcode']);
2863      }
2864  
2865      /**
2866       * Test get_calendar_access_information for admins.
2867       */
2868      public function test_get_calendar_access_information_for_admins() {
2869          global $CFG;
2870          $this->resetAfterTest(true);
2871          $this->setAdminUser();
2872  
2873          $CFG->calendar_adminseesall = 1;
2874  
2875          $data = external_api::clean_returnvalue(
2876              core_calendar_external::get_calendar_access_information_returns(),
2877              core_calendar_external::get_calendar_access_information()
2878          );
2879          $this->assertTrue($data['canmanageownentries']);
2880          $this->assertTrue($data['canmanagegroupentries']);
2881          $this->assertTrue($data['canmanageentries']);
2882      }
2883  
2884      /**
2885       * Test get_calendar_access_information for authenticated users.
2886       */
2887      public function test_get_calendar_access_information_for_authenticated_users() {
2888          $this->resetAfterTest(true);
2889          $this->setUser($this->getDataGenerator()->create_user());
2890  
2891          $data = external_api::clean_returnvalue(
2892              core_calendar_external::get_calendar_access_information_returns(),
2893              core_calendar_external::get_calendar_access_information()
2894          );
2895          $this->assertTrue($data['canmanageownentries']);
2896          $this->assertFalse($data['canmanagegroupentries']);
2897          $this->assertFalse($data['canmanageentries']);
2898      }
2899  
2900      /**
2901       * Test get_calendar_access_information for student users.
2902       */
2903      public function test_get_calendar_access_information_for_student_users() {
2904          global $DB;
2905          $this->resetAfterTest(true);
2906  
2907          $user = $this->getDataGenerator()->create_user();
2908          $course = $this->getDataGenerator()->create_course();
2909          $role = $DB->get_record('role', array('shortname' => 'student'));
2910          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
2911  
2912          $this->setUser($user);
2913  
2914          $data = external_api::clean_returnvalue(
2915              core_calendar_external::get_calendar_access_information_returns(),
2916              core_calendar_external::get_calendar_access_information($course->id)
2917          );
2918          $this->assertTrue($data['canmanageownentries']);
2919          $this->assertFalse($data['canmanagegroupentries']);
2920          $this->assertFalse($data['canmanageentries']);
2921      }
2922  
2923      /**
2924       * Test get_calendar_access_information for teacher users.
2925       */
2926      public function test_get_calendar_access_information_for_teacher_users() {
2927          global $DB;
2928          $this->resetAfterTest(true);
2929  
2930          $user = $this->getDataGenerator()->create_user();
2931          $course = $this->getDataGenerator()->create_course(['groupmode' => 1]);
2932          $role = $DB->get_record('role', array('shortname' => 'editingteacher'));
2933          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
2934          $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2935  
2936          $this->setUser($user);
2937  
2938          $data = external_api::clean_returnvalue(
2939              core_calendar_external::get_calendar_access_information_returns(),
2940              core_calendar_external::get_calendar_access_information($course->id)
2941          );
2942          $this->assertTrue($data['canmanageownentries']);
2943          $this->assertTrue($data['canmanagegroupentries']);
2944          $this->assertTrue($data['canmanageentries']);
2945      }
2946  
2947      /**
2948       * Test get_allowed_event_types for admins.
2949       */
2950      public function test_get_allowed_event_types_for_admins() {
2951          global $CFG;
2952          $this->resetAfterTest(true);
2953          $this->setAdminUser();
2954          $CFG->calendar_adminseesall = 1;
2955          $data = external_api::clean_returnvalue(
2956              core_calendar_external::get_allowed_event_types_returns(),
2957              core_calendar_external::get_allowed_event_types()
2958          );
2959          $this->assertEquals(['user', 'site', 'course', 'category'], $data['allowedeventtypes']);
2960      }
2961      /**
2962       * Test get_allowed_event_types for authenticated users.
2963       */
2964      public function test_get_allowed_event_types_for_authenticated_users() {
2965          $this->resetAfterTest(true);
2966          $this->setUser($this->getDataGenerator()->create_user());
2967          $data = external_api::clean_returnvalue(
2968              core_calendar_external::get_allowed_event_types_returns(),
2969              core_calendar_external::get_allowed_event_types()
2970          );
2971          $this->assertEquals(['user'], $data['allowedeventtypes']);
2972      }
2973      /**
2974       * Test get_allowed_event_types for student users.
2975       */
2976      public function test_get_allowed_event_types_for_student_users() {
2977          global $DB;
2978          $this->resetAfterTest(true);
2979          $user = $this->getDataGenerator()->create_user();
2980          $course = $this->getDataGenerator()->create_course();
2981          $role = $DB->get_record('role', array('shortname' => 'student'));
2982          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
2983          $this->setUser($user);
2984          $data = external_api::clean_returnvalue(
2985              core_calendar_external::get_allowed_event_types_returns(),
2986              core_calendar_external::get_allowed_event_types($course->id)
2987          );
2988          $this->assertEquals(['user'], $data['allowedeventtypes']);
2989      }
2990      /**
2991       * Test get_allowed_event_types for teacher users.
2992       */
2993      public function test_get_allowed_event_types_for_teacher_users() {
2994          global $DB;
2995          $this->resetAfterTest(true);
2996          $user = $this->getDataGenerator()->create_user();
2997          $course = $this->getDataGenerator()->create_course(['groupmode' => 1]);
2998          $role = $DB->get_record('role', array('shortname' => 'editingteacher'));
2999          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
3000          $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3001          $this->setUser($user);
3002          $data = external_api::clean_returnvalue(
3003              core_calendar_external::get_allowed_event_types_returns(),
3004              core_calendar_external::get_allowed_event_types($course->id)
3005          );
3006          $this->assertEquals(['user', 'course', 'group'], $data['allowedeventtypes']);
3007      }
3008  
3009      /**
3010       * Test get_timestamps with string keys, with and without optional hour/minute values.
3011       */
3012      public function test_get_timestamps_string_keys() {
3013          $this->resetAfterTest(true);
3014          $this->setAdminUser();
3015  
3016          $time1 = new DateTime('2018-12-30 00:00:00');
3017          $time2 = new DateTime('2019-03-27 23:59:00');
3018  
3019          $dates = [
3020              [
3021                  'key' => 'from',
3022                  'year' => $time1->format('Y'),
3023                  'month' => $time1->format('m'),
3024                  'day' => $time1->format('d'),
3025              ],
3026              [
3027                  'key' => 'to',
3028                  'year' => $time2->format('Y'),
3029                  'month' => (int) $time2->format('m'),
3030                  'day' => $time2->format('d'),
3031                  'hour' => $time2->format('H'),
3032                  'minute' => $time2->format('i'),
3033              ],
3034          ];
3035  
3036          $expectedtimestamps = [
3037              'from' => $time1->getTimestamp(),
3038              'to' => $time2->getTimestamp(),
3039          ];
3040  
3041          $result = core_calendar_external::get_timestamps($dates);
3042  
3043          $this->assertEquals(['timestamps'], array_keys($result));
3044          $this->assertEquals(2, count($result['timestamps']));
3045  
3046          foreach ($result['timestamps'] as $data) {
3047              $this->assertTrue(in_array($data['key'], ['from', 'to']));
3048              $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']);
3049          }
3050      }
3051  
3052      /**
3053       * Test get_timestamps with no keys specified, with and without optional hour/minute values.
3054       */
3055      public function test_get_timestamps_no_keys() {
3056          $this->resetAfterTest(true);
3057          $this->setAdminUser();
3058  
3059          $time1 = new DateTime('2018-12-30 00:00:00');
3060          $time2 = new DateTime('2019-03-27 23:59:00');
3061  
3062          $dates = [
3063              [
3064                  'year' => $time1->format('Y'),
3065                  'month' => $time1->format('m'),
3066                  'day' => $time1->format('d'),
3067              ],
3068              [
3069                  'year' => $time2->format('Y'),
3070                  'month' => (int) $time2->format('m'),
3071                  'day' => $time2->format('d'),
3072                  'hour' => $time2->format('H'),
3073                  'minute' => $time2->format('i'),
3074              ],
3075          ];
3076  
3077          $expectedtimestamps = [
3078              0 => $time1->getTimestamp(),
3079              1 => $time2->getTimestamp(),
3080          ];
3081  
3082          $result = core_calendar_external::get_timestamps($dates);
3083  
3084          $this->assertEquals(['timestamps'], array_keys($result));
3085          $this->assertEquals(2, count($result['timestamps']));
3086  
3087          foreach ($result['timestamps'] as $data) {
3088              $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']);
3089          }
3090      }
3091  }