Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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