Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_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 get_calendar_action_events_by_course with search feature
1360       */
1361      public function test_get_calendar_action_events_by_course_with_search() {
1362          // Generate data.
1363          $user = $this->getDataGenerator()->create_user();
1364          $course = $this->getDataGenerator()->create_course();
1365          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1366          $instance = $generator->create_instance(['course' => $course->id]);
1367  
1368          // Enrol.
1369          $this->getDataGenerator()->enrol_user($user->id, $course->id);
1370          $this->resetAfterTest(true);
1371          $this->setUser($user);
1372  
1373          for ($i = 1; $i < 5; $i++) {
1374              $this->create_calendar_event(
1375                  sprintf('Event %d', $i),
1376                  $user->id,
1377                  'user',
1378                  0,
1379                  1,
1380                  [
1381                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1382                      'courseid' => $course->id,
1383                      'timesort' => $i,
1384                      'modulename' => 'assign',
1385                      'instance' => $instance->id,
1386                  ]
1387              );
1388          }
1389  
1390          // No result found for fake search.
1391          $result = core_calendar_external::get_calendar_action_events_by_course($course->id, null, null, 0, 20, 'Fake search');
1392          $result = \external_api::clean_returnvalue(
1393              core_calendar_external::get_calendar_action_events_by_course_returns(),
1394              $result
1395          );
1396          $result = $result['events'];
1397          $this->assertEmpty($result);
1398  
1399          // Search for event name called 'Event 1'.
1400          $result = core_calendar_external::get_calendar_action_events_by_course($course->id, null, null, 0, 20, 'Event 1');
1401          $result = \external_api::clean_returnvalue(
1402              core_calendar_external::get_calendar_action_events_by_course_returns(),
1403              $result
1404          );
1405          $result = $result['events'];
1406          $this->assertCount(1, $result);
1407          $this->assertEquals('Event 1', $result[0]['name']);
1408  
1409          // Search for activity type called 'assign'.
1410          $result = core_calendar_external::get_calendar_action_events_by_course($course->id, null, null, 0, 20, 'assign');
1411          $result = \external_api::clean_returnvalue(
1412              core_calendar_external::get_calendar_action_events_by_course_returns(),
1413              $result
1414          );
1415          $result = $result['events'];
1416          $this->assertCount(4, $result);
1417          $this->assertEquals('Event 1', $result[0]['name']);
1418          $this->assertEquals('Event 2', $result[1]['name']);
1419          $this->assertEquals('Event 3', $result[2]['name']);
1420          $this->assertEquals('Event 4', $result[3]['name']);
1421      }
1422  
1423      /**
1424       * Test that get_action_events_by_courses will return a list of events for each
1425       * course you provided as long as the user is enrolled in the course.
1426       */
1427      public function test_get_action_events_by_courses() {
1428          $user = $this->getDataGenerator()->create_user();
1429          $course1 = $this->getDataGenerator()->create_course();
1430          $course2 = $this->getDataGenerator()->create_course();
1431          $course3 = $this->getDataGenerator()->create_course();
1432          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1433          $instance1 = $generator->create_instance(['course' => $course1->id]);
1434          $instance2 = $generator->create_instance(['course' => $course2->id]);
1435          $instance3 = $generator->create_instance(['course' => $course3->id]);
1436          $records = [];
1437          $mapresult = function($result) {
1438              $groupedbycourse = [];
1439              foreach ($result['groupedbycourse'] as $group) {
1440                  $events = $group['events'];
1441                  $courseid = $group['courseid'];
1442                  $groupedbycourse[$courseid] = $events;
1443              }
1444  
1445              return $groupedbycourse;
1446          };
1447  
1448          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1449          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1450          $this->resetAfterTest(true);
1451          $this->setUser($user);
1452  
1453          for ($i = 1; $i < 10; $i++) {
1454              if ($i < 3) {
1455                  $courseid = $course1->id;
1456                  $instance = $instance1->id;
1457              } else if ($i < 6) {
1458                  $courseid = $course2->id;
1459                  $instance = $instance2->id;
1460              } else {
1461                  $courseid = $course3->id;
1462                  $instance = $instance3->id;
1463              }
1464  
1465              $records[] = $this->create_calendar_event(
1466                  sprintf('Event %d', $i),
1467                  $user->id,
1468                  'user',
1469                  0,
1470                  1,
1471                  [
1472                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1473                      'courseid' => $courseid,
1474                      'timesort' => $i,
1475                      'modulename' => 'assign',
1476                      'instance' => $instance,
1477                  ]
1478              );
1479          }
1480  
1481          $result = core_calendar_external::get_calendar_action_events_by_courses([], 1);
1482          $result = \external_api::clean_returnvalue(
1483              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1484              $result
1485          );
1486          $result = $result['groupedbycourse'];
1487  
1488          $this->assertEmpty($result);
1489  
1490          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id], 3);
1491          $result = \external_api::clean_returnvalue(
1492              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1493              $result
1494          );
1495  
1496          $groupedbycourse = $mapresult($result);
1497  
1498          $this->assertEmpty($groupedbycourse[$course1->id]);
1499  
1500          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id], 1);
1501          $result = \external_api::clean_returnvalue(
1502              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1503              $result
1504          );
1505          $groupedbycourse = $mapresult($result);
1506  
1507          $this->assertCount(2, $groupedbycourse[$course1->id]);
1508          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1509          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']);
1510  
1511          $result = core_calendar_external::get_calendar_action_events_by_courses(
1512              [$course1->id, $course2->id], 1);
1513          $result = \external_api::clean_returnvalue(
1514              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1515              $result
1516          );
1517          $groupedbycourse = $mapresult($result);
1518  
1519          $this->assertCount(2, $groupedbycourse[$course1->id]);
1520          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1521          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']);
1522          $this->assertCount(3, $groupedbycourse[$course2->id]);
1523          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1524          $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']);
1525          $this->assertEquals('Event 5', $groupedbycourse[$course2->id][2]['name']);
1526  
1527          $result = core_calendar_external::get_calendar_action_events_by_courses(
1528              [$course1->id, $course2->id], 2, 4);
1529          $result = \external_api::clean_returnvalue(
1530              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1531              $result
1532          );
1533          $groupedbycourse = $mapresult($result);
1534  
1535          $this->assertCount(2, $groupedbycourse);
1536          $this->assertCount(1, $groupedbycourse[$course1->id]);
1537          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][0]['name']);
1538          $this->assertCount(2, $groupedbycourse[$course2->id]);
1539          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1540          $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']);
1541  
1542          $result = core_calendar_external::get_calendar_action_events_by_courses(
1543              [$course1->id, $course2->id], 1, null, 1);
1544          $result = \external_api::clean_returnvalue(
1545              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1546              $result
1547          );
1548          $groupedbycourse = $mapresult($result);
1549  
1550          $this->assertCount(2, $groupedbycourse);
1551          $this->assertCount(1, $groupedbycourse[$course1->id]);
1552          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1553          $this->assertCount(1, $groupedbycourse[$course2->id]);
1554          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1555      }
1556  
1557      /**
1558       * Test get_action_events_by_courses with search feature
1559       */
1560      public function test_get_action_events_by_courses_with_search() {
1561          // Generate data.
1562          $user = $this->getDataGenerator()->create_user();
1563          $course1 = $this->getDataGenerator()->create_course();
1564          $course2 = $this->getDataGenerator()->create_course();
1565          $course3 = $this->getDataGenerator()->create_course();
1566          $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1567          $instance1 = $generator->create_instance(['course' => $course1->id]);
1568          $instance2 = $generator->create_instance(['course' => $course2->id]);
1569          $instance3 = $generator->create_instance(['course' => $course3->id]);
1570  
1571          $this->getDataGenerator()->enrol_user($user->id, $course1->id);
1572          $this->getDataGenerator()->enrol_user($user->id, $course2->id);
1573          $this->resetAfterTest(true);
1574          $this->setUser($user);
1575  
1576          $mapresult = function($result) {
1577              $groupedbycourse = [];
1578              foreach ($result['groupedbycourse'] as $group) {
1579                  $events = $group['events'];
1580                  $courseid = $group['courseid'];
1581                  $groupedbycourse[$courseid] = $events;
1582              }
1583  
1584              return $groupedbycourse;
1585          };
1586  
1587          for ($i = 1; $i < 10; $i++) {
1588              if ($i < 3) {
1589                  $courseid = $course1->id;
1590                  $instance = $instance1->id;
1591              } else if ($i < 6) {
1592                  $courseid = $course2->id;
1593                  $instance = $instance2->id;
1594              } else {
1595                  $courseid = $course3->id;
1596                  $instance = $instance3->id;
1597              }
1598  
1599              $records[] = $this->create_calendar_event(
1600                  sprintf('Event %d', $i),
1601                  $user->id,
1602                  'user',
1603                  0,
1604                  1,
1605                  [
1606                      'type' => CALENDAR_EVENT_TYPE_ACTION,
1607                      'courseid' => $courseid,
1608                      'timesort' => $i,
1609                      'modulename' => 'assign',
1610                      'instance' => $instance,
1611                  ]
1612              );
1613          }
1614  
1615          // No result found for fake search.
1616          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id],
1617              1, null, 20, 'Fake search');
1618          $result = \external_api::clean_returnvalue(
1619              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1620              $result
1621          );
1622          $groupedbycourse = $mapresult($result);
1623  
1624          $this->assertEmpty($groupedbycourse[$course1->id]);
1625          $this->assertEmpty($groupedbycourse[$course2->id]);
1626          $this->assertArrayNotHasKey($course3->id, $groupedbycourse);
1627  
1628          // Search for event name called 'Event 1'.
1629          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id],
1630              1, null, 20, 'Event 1');
1631          $result = \external_api::clean_returnvalue(
1632              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1633              $result
1634          );
1635          $groupedbycourse = $mapresult($result);
1636  
1637          $this->assertArrayNotHasKey($course3->id, $groupedbycourse);
1638          $this->assertCount(2, $groupedbycourse);
1639          $this->assertCount(1, $groupedbycourse[$course1->id]);
1640          $this->assertCount(0, $groupedbycourse[$course2->id]);
1641          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1642  
1643          // Search for activity type called 'assign'.
1644          $result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id],
1645              1, null, 20, 'assign');
1646          $result = \external_api::clean_returnvalue(
1647              core_calendar_external::get_calendar_action_events_by_courses_returns(),
1648              $result
1649          );
1650          $groupedbycourse = $mapresult($result);
1651  
1652          $this->assertArrayNotHasKey($course3->id, $groupedbycourse);
1653          $this->assertCount(2, $groupedbycourse);
1654          $this->assertCount(2, $groupedbycourse[$course1->id]);
1655          $this->assertCount(3, $groupedbycourse[$course2->id]);
1656          $this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
1657          $this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']);
1658          $this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
1659          $this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']);
1660          $this->assertEquals('Event 5', $groupedbycourse[$course2->id][2]['name']);
1661      }
1662  
1663      /**
1664       * Test for deleting module events.
1665       */
1666      public function test_delete_calendar_events_for_modules() {
1667          $this->resetAfterTest();
1668          $this->setAdminUser();
1669          $course = $this->getDataGenerator()->create_course();
1670          $nexttime = time() + DAYSECS;
1671          $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'duedate' => $nexttime]);
1672          $events = calendar_get_events(time(), $nexttime, true, true, true);
1673          $this->assertCount(1, $events);
1674          $params = [];
1675          foreach ($events as $event) {
1676              $params[] = [
1677                  'eventid' => $event->id,
1678                  'repeat' => false
1679              ];
1680          }
1681  
1682          $this->expectException(\moodle_exception::class);
1683          core_calendar_external::delete_calendar_events($params);
1684      }
1685  
1686      /**
1687       * Updating the event start day should change the date value but leave
1688       * the time of day unchanged.
1689       */
1690      public function test_update_event_start_day() {
1691          $generator = $this->getDataGenerator();
1692          $user = $generator->create_user();
1693          $roleid = $generator->create_role();
1694          $context = \context_system::instance();
1695          $originalstarttime = new \DateTimeImmutable('2017-01-1T15:00:00+08:00');
1696          $newstartdate = new \DateTimeImmutable('2018-02-2T10:00:00+08:00');
1697          $expected = new \DateTimeImmutable('2018-02-2T15:00:00+08:00');
1698  
1699          $generator->role_assign($roleid, $user->id, $context->id);
1700          assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
1701  
1702          $this->setUser($user);
1703          $this->resetAfterTest(true);
1704  
1705          $event = $this->create_calendar_event(
1706              'Test event',
1707              $user->id,
1708              'user',
1709              0,
1710              null,
1711              [
1712                  'courseid' => 0,
1713                  'timestart' => $originalstarttime->getTimestamp()
1714              ]
1715          );
1716  
1717          $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp());
1718          $result = \external_api::clean_returnvalue(
1719              core_calendar_external::update_event_start_day_returns(),
1720              $result
1721          );
1722  
1723          $this->assertEquals($expected->getTimestamp(), $result['event']['timestart']);
1724      }
1725  
1726      /**
1727       * A user should not be able to edit an event that they don't have
1728       * capabilities for.
1729       */
1730      public function test_update_event_start_day_no_permission() {
1731          $generator = $this->getDataGenerator();
1732          $user = $generator->create_user();
1733          $roleid = $generator->create_role();
1734          $context = \context_system::instance();
1735          $originalstarttime = new \DateTimeImmutable('2017-01-1T15:00:00+08:00');
1736          $newstartdate = new \DateTimeImmutable('2018-02-2T10:00:00+08:00');
1737          $expected = new \DateTimeImmutable('2018-02-2T15:00:00+08:00');
1738  
1739          $generator->role_assign($roleid, $user->id, $context->id);
1740          assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
1741  
1742          $this->setUser($user);
1743          $this->resetAfterTest(true);
1744  
1745          $event = $this->create_calendar_event(
1746              'Test event',
1747              $user->id,
1748              'user',
1749              0,
1750              null,
1751              [
1752                  'courseid' => 0,
1753                  'timestart' => $originalstarttime->getTimestamp()
1754              ]
1755          );
1756  
1757          assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
1758          $this->expectException(\moodle_exception::class);
1759          $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp());
1760          $result = \external_api::clean_returnvalue(
1761              core_calendar_external::update_event_start_day_returns(),
1762              $result
1763          );
1764      }
1765  
1766      /**
1767       * A user should not be able to update a module event.
1768       */
1769      public function test_update_event_start_day_module_event() {
1770          $generator = $this->getDataGenerator();
1771          $user = $generator->create_user();
1772          $course = $generator->create_course();
1773          $plugingenerator = $generator->get_plugin_generator('mod_assign');
1774          $moduleinstance = $plugingenerator->create_instance(['course' => $course->id]);
1775          $roleid = $generator->create_role();
1776          $context = \context_course::instance($course->id);
1777          $originalstarttime = new \DateTimeImmutable('2017-01-1T15:00:00+08:00');
1778          $newstartdate = new \DateTimeImmutable('2018-02-2T10:00:00+08:00');
1779          $expected = new \DateTimeImmutable('2018-02-2T15:00:00+08:00');
1780  
1781          $generator->role_assign($roleid, $user->id, $context->id);
1782          $generator->enrol_user($user->id, $course->id);
1783  
1784          $this->setUser($user);
1785          $this->resetAfterTest(true);
1786  
1787          $event = $this->create_calendar_event(
1788              'Test event',
1789              $user->id,
1790              'user',
1791              0,
1792              null,
1793              [
1794                  'modulename' => 'assign',
1795                  'instance' => $moduleinstance->id,
1796                  'courseid' => $course->id,
1797                  'timestart' => $originalstarttime->getTimestamp()
1798              ]
1799          );
1800  
1801          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
1802          $this->expectException(\moodle_exception::class);
1803          $result = core_calendar_external::update_event_start_day($event->id, $newstartdate->getTimestamp());
1804          $result = \external_api::clean_returnvalue(
1805              core_calendar_external::update_event_start_day_returns(),
1806              $result
1807          );
1808      }
1809  
1810      /**
1811       * Submit a request where the time duration until is earlier than the time
1812       * start in order to get a validation error from the server.
1813       */
1814      public function test_submit_create_update_form_validation_error() {
1815          $user = $this->getDataGenerator()->create_user();
1816          $timestart = new \DateTime();
1817          $interval = new \DateInterval("P1D"); // One day.
1818          $timedurationuntil = new \DateTime();
1819          $timedurationuntil->sub($interval);
1820          $formdata = [
1821              'id' => 0,
1822              'userid' => $user->id,
1823              'modulename' => '',
1824              'instance' => 0,
1825              'visible' => 1,
1826              'name' => 'Test',
1827              'timestart' => [
1828                  'day' => $timestart->format('j'),
1829                  'month' => $timestart->format('n'),
1830                  'year' => $timestart->format('Y'),
1831                  'hour' => $timestart->format('G'),
1832                  'minute' => 0,
1833              ],
1834              'eventtype' => 'user',
1835              'description' => [
1836                  'text' => '',
1837                  'format' => 1,
1838              ],
1839              'location' => 'Test',
1840              'duration' => 1,
1841              'timedurationuntil' => [
1842                  'day' => $timedurationuntil->format('j'),
1843                  'month' => $timedurationuntil->format('n'),
1844                  'year' => $timedurationuntil->format('Y'),
1845                  'hour' => $timedurationuntil->format('G'),
1846                  'minute' => 0,
1847              ]
1848          ];
1849  
1850          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1851  
1852          $querystring = http_build_query($formdata, '', '&amp;');
1853  
1854          $this->resetAfterTest(true);
1855          $this->setUser($user);
1856  
1857          $result = \external_api::clean_returnvalue(
1858              core_calendar_external::submit_create_update_form_returns(),
1859              core_calendar_external::submit_create_update_form($querystring)
1860          );
1861  
1862          $this->assertTrue($result['validationerror']);
1863      }
1864  
1865      /**
1866       * A user with the moodle/calendar:manageownentries capability at the
1867       * system context should be able to create a user event.
1868       */
1869      public function test_submit_create_update_form_create_user_event() {
1870          $generator = $this->getDataGenerator();
1871          $user = $generator->create_user();
1872          $roleid = $generator->create_role();
1873          $context = \context_system::instance();
1874          $timestart = new \DateTime();
1875          $interval = new \DateInterval("P1D"); // One day.
1876          $timedurationuntil = new \DateTime();
1877          $timedurationuntil->add($interval);
1878          $formdata = [
1879              'id' => 0,
1880              'userid' => $user->id,
1881              'modulename' => '',
1882              'instance' => 0,
1883              'visible' => 1,
1884              'name' => 'Test',
1885              'timestart' => [
1886                  'day' => $timestart->format('j'),
1887                  'month' => $timestart->format('n'),
1888                  'year' => $timestart->format('Y'),
1889                  'hour' => $timestart->format('G'),
1890                  'minute' => 0,
1891              ],
1892              'eventtype' => 'user',
1893              'description' => [
1894                  'text' => '',
1895                  'format' => 1,
1896                  'itemid' => 0
1897              ],
1898              'location' => 'Test',
1899              'duration' => 1,
1900              'timedurationuntil' => [
1901                  'day' => $timedurationuntil->format('j'),
1902                  'month' => $timedurationuntil->format('n'),
1903                  'year' => $timedurationuntil->format('Y'),
1904                  'hour' => $timedurationuntil->format('G'),
1905                  'minute' => 0,
1906              ]
1907          ];
1908  
1909          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1910          $querystring = http_build_query($formdata, '', '&');
1911  
1912          $generator->role_assign($roleid, $user->id, $context->id);
1913          assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
1914  
1915          $user->ignoresesskey = true;
1916          $this->resetAfterTest(true);
1917          $this->setUser($user);
1918  
1919          $result = \external_api::clean_returnvalue(
1920              core_calendar_external::submit_create_update_form_returns(),
1921              core_calendar_external::submit_create_update_form($querystring)
1922          );
1923  
1924          $event = $result['event'];
1925          $this->assertEquals($user->id, $event['userid']);
1926          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
1927          $this->assertEquals($formdata['name'], $event['name']);
1928      }
1929  
1930      /**
1931       * A user without the moodle/calendar:manageownentries capability at the
1932       * system context should not be able to create a user event.
1933       */
1934      public function test_submit_create_update_form_create_user_event_no_permission() {
1935          $generator = $this->getDataGenerator();
1936          $user = $generator->create_user();
1937          $roleid = $generator->create_role();
1938          $context = \context_system::instance();
1939          $timestart = new \DateTime();
1940          $interval = new \DateInterval("P1D"); // One day.
1941          $timedurationuntil = new \DateTime();
1942          $timedurationuntil->add($interval);
1943          $formdata = [
1944              'id' => 0,
1945              'userid' => $user->id,
1946              'modulename' => '',
1947              'instance' => 0,
1948              'visible' => 1,
1949              'name' => 'Test',
1950              'timestart' => [
1951                  'day' => $timestart->format('j'),
1952                  'month' => $timestart->format('n'),
1953                  'year' => $timestart->format('Y'),
1954                  'hour' => $timestart->format('G'),
1955                  'minute' => 0,
1956              ],
1957              'eventtype' => 'user',
1958              'description' => [
1959                  'text' => '',
1960                  'format' => 1,
1961              ],
1962              'location' => 'Test',
1963              'duration' => 1,
1964              'timedurationuntil' => [
1965                  'day' => $timedurationuntil->format('j'),
1966                  'month' => $timedurationuntil->format('n'),
1967                  'year' => $timedurationuntil->format('Y'),
1968                  'hour' => $timedurationuntil->format('G'),
1969                  'minute' => 0,
1970              ]
1971          ];
1972  
1973          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
1974          $querystring = http_build_query($formdata, '', '&');
1975  
1976          $generator->role_assign($roleid, $user->id, $context->id);
1977          assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
1978  
1979          $user->ignoresesskey = true;
1980          $this->resetAfterTest(true);
1981          $this->setUser($user);
1982  
1983          $this->expectException(\moodle_exception::class);
1984  
1985          \external_api::clean_returnvalue(
1986              core_calendar_external::submit_create_update_form_returns(),
1987              core_calendar_external::submit_create_update_form($querystring)
1988          );
1989      }
1990  
1991      /**
1992       * A user with the moodle/calendar:manageentries capability at the
1993       * site course context should be able to create a site event.
1994       */
1995      public function test_submit_create_update_form_create_site_event() {
1996          $generator = $this->getDataGenerator();
1997          $user = $generator->create_user();
1998          $context = \context_system::instance();
1999          $roleid = $generator->create_role();
2000          $timestart = new \DateTime();
2001          $interval = new \DateInterval("P1D"); // One day.
2002          $timedurationuntil = new \DateTime();
2003          $timedurationuntil->add($interval);
2004          $formdata = [
2005              'id' => 0,
2006              'userid' => $user->id,
2007              'modulename' => '',
2008              'instance' => 0,
2009              'visible' => 1,
2010              'name' => 'Test',
2011              'timestart' => [
2012                  'day' => $timestart->format('j'),
2013                  'month' => $timestart->format('n'),
2014                  'year' => $timestart->format('Y'),
2015                  'hour' => $timestart->format('G'),
2016                  'minute' => 0,
2017              ],
2018              'eventtype' => 'site',
2019              'description' => [
2020                  'text' => '',
2021                  'format' => 1,
2022                  'itemid' => 0
2023              ],
2024              'location' => 'Test',
2025              'duration' => 1,
2026              'timedurationuntil' => [
2027                  'day' => $timedurationuntil->format('j'),
2028                  'month' => $timedurationuntil->format('n'),
2029                  'year' => $timedurationuntil->format('Y'),
2030                  'hour' => $timedurationuntil->format('G'),
2031                  'minute' => 0,
2032              ]
2033          ];
2034  
2035          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2036          $querystring = http_build_query($formdata, '', '&');
2037  
2038          $generator->role_assign($roleid, $user->id, $context->id);
2039  
2040          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2041  
2042          $user->ignoresesskey = true;
2043          $this->resetAfterTest(true);
2044          $this->setUser($user);
2045  
2046          $result = \external_api::clean_returnvalue(
2047              core_calendar_external::submit_create_update_form_returns(),
2048              core_calendar_external::submit_create_update_form($querystring)
2049          );
2050  
2051          $event = $result['event'];
2052          $this->assertEquals($user->id, $event['userid']);
2053          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2054          $this->assertEquals($formdata['name'], $event['name']);
2055      }
2056  
2057      /**
2058       * A user without the moodle/calendar:manageentries capability at the
2059       * site course context should not be able to create a site event.
2060       */
2061      public function test_submit_create_update_form_create_site_event_no_permission() {
2062          $generator = $this->getDataGenerator();
2063          $user = $generator->create_user();
2064          $context = \context_course::instance(SITEID);
2065          $roleid = $generator->create_role();
2066          $timestart = new \DateTime();
2067          $interval = new \DateInterval("P1D"); // One day.
2068          $timedurationuntil = new \DateTime();
2069          $timedurationuntil->add($interval);
2070          $formdata = [
2071              'id' => 0,
2072              'userid' => $user->id,
2073              'modulename' => '',
2074              'instance' => 0,
2075              'visible' => 1,
2076              'name' => 'Test',
2077              'timestart' => [
2078                  'day' => $timestart->format('j'),
2079                  'month' => $timestart->format('n'),
2080                  'year' => $timestart->format('Y'),
2081                  'hour' => $timestart->format('G'),
2082                  'minute' => 0,
2083              ],
2084              'eventtype' => 'site',
2085              'description' => [
2086                  'text' => '',
2087                  'format' => 1,
2088              ],
2089              'location' => 'Test',
2090              'duration' => 1,
2091              'timedurationuntil' => [
2092                  'day' => $timedurationuntil->format('j'),
2093                  'month' => $timedurationuntil->format('n'),
2094                  'year' => $timedurationuntil->format('Y'),
2095                  'hour' => $timedurationuntil->format('G'),
2096                  'minute' => 0,
2097              ]
2098          ];
2099  
2100          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2101          $querystring = http_build_query($formdata, '', '&');
2102  
2103          $generator->role_assign($roleid, $user->id, $context->id);
2104  
2105          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
2106  
2107          $user->ignoresesskey = true;
2108          $this->resetAfterTest(true);
2109          $this->setUser($user);
2110  
2111          $result = \external_api::clean_returnvalue(
2112              core_calendar_external::submit_create_update_form_returns(),
2113              core_calendar_external::submit_create_update_form($querystring)
2114          );
2115  
2116          $this->assertTrue($result['validationerror']);
2117      }
2118  
2119      /**
2120       * A user that has the moodle/calendar:manageentries in a course that they
2121       * are enrolled in should be able to create a course event in that course.
2122       */
2123      public function test_submit_create_update_form_create_course_event() {
2124          $generator = $this->getDataGenerator();
2125          $user = $generator->create_user();
2126          $course = $generator->create_course();
2127          $context = \context_course::instance($course->id);
2128          $roleid = $generator->create_role();
2129          $timestart = new \DateTime();
2130          $interval = new \DateInterval("P1D"); // One day.
2131          $timedurationuntil = new \DateTime();
2132          $timedurationuntil->add($interval);
2133          $formdata = [
2134              'id' => 0,
2135              'userid' => $user->id,
2136              'modulename' => '',
2137              'instance' => 0,
2138              'visible' => 1,
2139              'name' => 'Test',
2140              'timestart' => [
2141                  'day' => $timestart->format('j'),
2142                  'month' => $timestart->format('n'),
2143                  'year' => $timestart->format('Y'),
2144                  'hour' => $timestart->format('G'),
2145                  'minute' => 0,
2146              ],
2147              'eventtype' => 'course',
2148              'courseid' => $course->id,
2149              'description' => [
2150                  'text' => '',
2151                  'format' => 1,
2152                  'itemid' => 0,
2153              ],
2154              'location' => 'Test',
2155              'duration' => 1,
2156              'timedurationuntil' => [
2157                  'day' => $timedurationuntil->format('j'),
2158                  'month' => $timedurationuntil->format('n'),
2159                  'year' => $timedurationuntil->format('Y'),
2160                  'hour' => $timedurationuntil->format('G'),
2161                  'minute' => 0,
2162              ]
2163          ];
2164  
2165          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2166          $querystring = http_build_query($formdata, '', '&');
2167  
2168          $generator->enrol_user($user->id, $course->id, 'student');
2169          $generator->role_assign($roleid, $user->id, $context->id);
2170  
2171          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2172  
2173          $user->ignoresesskey = true;
2174          $this->resetAfterTest(true);
2175          $this->setUser($user);
2176  
2177          $result = \external_api::clean_returnvalue(
2178              core_calendar_external::submit_create_update_form_returns(),
2179              core_calendar_external::submit_create_update_form($querystring)
2180          );
2181  
2182          $event = $result['event'];
2183          $this->assertEquals($user->id, $event['userid']);
2184          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2185          $this->assertEquals($formdata['name'], $event['name']);
2186          $this->assertEquals($formdata['courseid'], $event['course']['id']);
2187      }
2188  
2189      /**
2190       * A user without the moodle/calendar:manageentries capability in a course
2191       * that they are enrolled in should not be able to create a course event in that course.
2192       */
2193      public function test_submit_create_update_form_create_course_event_no_permission() {
2194          $generator = $this->getDataGenerator();
2195          $user = $generator->create_user();
2196          $course = $generator->create_course();
2197          $context = \context_course::instance($course->id);
2198          $roleid = $generator->create_role();
2199          $timestart = new \DateTime();
2200          $interval = new \DateInterval("P1D"); // One day.
2201          $timedurationuntil = new \DateTime();
2202          $timedurationuntil->add($interval);
2203          $formdata = [
2204              'id' => 0,
2205              'userid' => $user->id,
2206              'modulename' => '',
2207              'instance' => 0,
2208              'visible' => 1,
2209              'name' => 'Test',
2210              'timestart' => [
2211                  'day' => $timestart->format('j'),
2212                  'month' => $timestart->format('n'),
2213                  'year' => $timestart->format('Y'),
2214                  'hour' => $timestart->format('G'),
2215                  'minute' => 0,
2216              ],
2217              'eventtype' => 'course',
2218              'courseid' => $course->id,
2219              'description' => [
2220                  'text' => '',
2221                  'format' => 1,
2222              ],
2223              'location' => 'Test',
2224              'duration' => 1,
2225              'timedurationuntil' => [
2226                  'day' => $timedurationuntil->format('j'),
2227                  'month' => $timedurationuntil->format('n'),
2228                  'year' => $timedurationuntil->format('Y'),
2229                  'hour' => $timedurationuntil->format('G'),
2230                  'minute' => 0,
2231              ]
2232          ];
2233  
2234          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2235          $querystring = http_build_query($formdata, '', '&');
2236  
2237          $generator->enrol_user($user->id, $course->id, 'student');
2238          $generator->role_assign($roleid, $user->id, $context->id);
2239  
2240          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
2241  
2242          $user->ignoresesskey = true;
2243          $this->resetAfterTest(true);
2244          $this->setUser($user);
2245  
2246          $result = \external_api::clean_returnvalue(
2247              core_calendar_external::submit_create_update_form_returns(),
2248              core_calendar_external::submit_create_update_form($querystring)
2249          );
2250  
2251          $this->assertTrue($result['validationerror']);
2252      }
2253  
2254      /**
2255       * A user should not be able to create an event for a course that they are
2256       * not enrolled in.
2257       */
2258      public function test_submit_create_update_form_create_course_event_not_enrolled() {
2259          $generator = $this->getDataGenerator();
2260          $user = $generator->create_user();
2261          $course = $generator->create_course();
2262          $course2 = $generator->create_course();
2263          $context = \context_course::instance($course->id);
2264          $roleid = $generator->create_role();
2265          $timestart = new \DateTime();
2266          $interval = new \DateInterval("P1D"); // One day.
2267          $timedurationuntil = new \DateTime();
2268          $timedurationuntil->add($interval);
2269          $formdata = [
2270              'id' => 0,
2271              'userid' => $user->id,
2272              'modulename' => '',
2273              'instance' => 0,
2274              'visible' => 1,
2275              'name' => 'Test',
2276              'timestart' => [
2277                  'day' => $timestart->format('j'),
2278                  'month' => $timestart->format('n'),
2279                  'year' => $timestart->format('Y'),
2280                  'hour' => $timestart->format('G'),
2281                  'minute' => 0,
2282              ],
2283              'eventtype' => 'course',
2284              'courseid' => $course2->id, // Not enrolled.
2285              'description' => [
2286                  'text' => '',
2287                  'format' => 1,
2288              ],
2289              'location' => 'Test',
2290              'duration' => 1,
2291              'timedurationuntil' => [
2292                  'day' => $timedurationuntil->format('j'),
2293                  'month' => $timedurationuntil->format('n'),
2294                  'year' => $timedurationuntil->format('Y'),
2295                  'hour' => $timedurationuntil->format('G'),
2296                  'minute' => 0,
2297              ]
2298          ];
2299  
2300          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2301          $querystring = http_build_query($formdata, '', '&');
2302  
2303          $generator->enrol_user($user->id, $course->id, 'student');
2304          $generator->role_assign($roleid, $user->id, $context->id);
2305  
2306          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2307  
2308          $user->ignoresesskey = true;
2309          $this->resetAfterTest(true);
2310          $this->setUser($user);
2311  
2312          $result = \external_api::clean_returnvalue(
2313              core_calendar_external::submit_create_update_form_returns(),
2314              core_calendar_external::submit_create_update_form($querystring)
2315          );
2316  
2317          $this->assertTrue($result['validationerror']);
2318      }
2319  
2320      /**
2321       * A user should be able to create an event for a group that they are a member of in
2322       * a course in which they are enrolled and have the moodle/calendar:manageentries capability.
2323       */
2324      public function test_submit_create_update_form_create_group_event_group_member_manage_course() {
2325          $generator = $this->getDataGenerator();
2326          $user = $generator->create_user();
2327          $course = $generator->create_course();
2328          $group = $generator->create_group(array('courseid' => $course->id));
2329          $context = \context_course::instance($course->id);
2330          $roleid = $generator->create_role();
2331          $timestart = new \DateTime();
2332          $interval = new \DateInterval("P1D"); // One day.
2333          $timedurationuntil = new \DateTime();
2334          $timedurationuntil->add($interval);
2335          $formdata = [
2336              'id' => 0,
2337              'userid' => $user->id,
2338              'modulename' => '',
2339              'instance' => 0,
2340              'visible' => 1,
2341              'name' => 'Test',
2342              'timestart' => [
2343                  'day' => $timestart->format('j'),
2344                  'month' => $timestart->format('n'),
2345                  'year' => $timestart->format('Y'),
2346                  'hour' => $timestart->format('G'),
2347                  'minute' => 0,
2348              ],
2349              'eventtype' => 'group',
2350              'groupid' => $group->id,
2351              'groupcourseid' => $course->id,
2352              'description' => [
2353                  'text' => '',
2354                  'format' => 1,
2355                  'itemid' => 0
2356              ],
2357              'location' => 'Test',
2358              'duration' => 1,
2359              'timedurationuntil' => [
2360                  'day' => $timedurationuntil->format('j'),
2361                  'month' => $timedurationuntil->format('n'),
2362                  'year' => $timedurationuntil->format('Y'),
2363                  'hour' => $timedurationuntil->format('G'),
2364                  'minute' => 0,
2365              ]
2366          ];
2367  
2368          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2369          $querystring = http_build_query($formdata, '', '&');
2370  
2371          $generator->enrol_user($user->id, $course->id, 'student');
2372          $generator->role_assign($roleid, $user->id, $context->id);
2373          $generator->create_group_member(['groupid' => $group->id, 'userid' => $user->id]);
2374  
2375          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2376  
2377          $user->ignoresesskey = true;
2378          $this->resetAfterTest(true);
2379          $this->setUser($user);
2380  
2381          $result = \external_api::clean_returnvalue(
2382              core_calendar_external::submit_create_update_form_returns(),
2383              core_calendar_external::submit_create_update_form($querystring)
2384          );
2385  
2386          $event = $result['event'];
2387          $this->assertEquals($user->id, $event['userid']);
2388          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2389          $this->assertEquals($formdata['name'], $event['name']);
2390          $this->assertEquals($group->id, $event['groupid']);
2391      }
2392  
2393      /**
2394       * A user should be able to create an event for a group that they are a member of in
2395       * a course in which they are enrolled and have the moodle/calendar:managegroupentries capability.
2396       */
2397      public function test_submit_create_update_form_create_group_event_group_member_manage_group_entries() {
2398          $generator = $this->getDataGenerator();
2399          $user = $generator->create_user();
2400          $course = $generator->create_course();
2401          $group = $generator->create_group(array('courseid' => $course->id));
2402          $context = \context_course::instance($course->id);
2403          $roleid = $generator->create_role();
2404          $timestart = new \DateTime();
2405          $interval = new \DateInterval("P1D"); // One day.
2406          $timedurationuntil = new \DateTime();
2407          $timedurationuntil->add($interval);
2408          $formdata = [
2409              'id' => 0,
2410              'userid' => $user->id,
2411              'modulename' => '',
2412              'instance' => 0,
2413              'visible' => 1,
2414              'name' => 'Test',
2415              'timestart' => [
2416                  'day' => $timestart->format('j'),
2417                  'month' => $timestart->format('n'),
2418                  'year' => $timestart->format('Y'),
2419                  'hour' => $timestart->format('G'),
2420                  'minute' => 0,
2421              ],
2422              'eventtype' => 'group',
2423              'groupid' => $group->id,
2424              'groupcourseid' => $course->id,
2425              'description' => [
2426                  'text' => '',
2427                  'format' => 1,
2428                  'itemid' => 0
2429              ],
2430              'location' => 'Test',
2431              'duration' => 1,
2432              'timedurationuntil' => [
2433                  'day' => $timedurationuntil->format('j'),
2434                  'month' => $timedurationuntil->format('n'),
2435                  'year' => $timedurationuntil->format('Y'),
2436                  'hour' => $timedurationuntil->format('G'),
2437                  'minute' => 0,
2438              ]
2439          ];
2440  
2441          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2442          $querystring = http_build_query($formdata, '', '&');
2443  
2444          $generator->enrol_user($user->id, $course->id, 'student');
2445          $generator->role_assign($roleid, $user->id, $context->id);
2446          $generator->create_group_member(['groupid' => $group->id, 'userid' => $user->id]);
2447  
2448          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
2449          assign_capability('moodle/calendar:managegroupentries', CAP_ALLOW, $roleid, $context, true);
2450  
2451          $user->ignoresesskey = true;
2452          $this->resetAfterTest(true);
2453          $this->setUser($user);
2454  
2455          $result = \external_api::clean_returnvalue(
2456              core_calendar_external::submit_create_update_form_returns(),
2457              core_calendar_external::submit_create_update_form($querystring)
2458          );
2459  
2460          $event = $result['event'];
2461          $this->assertEquals($user->id, $event['userid']);
2462          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2463          $this->assertEquals($formdata['name'], $event['name']);
2464          $this->assertEquals($group->id, $event['groupid']);
2465      }
2466  
2467      /**
2468       * A user should be able to create an event for any group in a course in which
2469       * they are enrolled and have the moodle/site:accessallgroups capability.
2470       */
2471      public function test_submit_create_update_form_create_group_event_access_all_groups() {
2472          $generator = $this->getDataGenerator();
2473          $user = $generator->create_user();
2474          $course = $generator->create_course();
2475          $group = $generator->create_group(array('courseid' => $course->id));
2476          $context = \context_course::instance($course->id);
2477          $roleid = $generator->create_role();
2478          $timestart = new \DateTime();
2479          $interval = new \DateInterval("P1D"); // One day.
2480          $timedurationuntil = new \DateTime();
2481          $timedurationuntil->add($interval);
2482          $formdata = [
2483              'id' => 0,
2484              'userid' => $user->id,
2485              'modulename' => '',
2486              'instance' => 0,
2487              'visible' => 1,
2488              'name' => 'Test',
2489              'timestart' => [
2490                  'day' => $timestart->format('j'),
2491                  'month' => $timestart->format('n'),
2492                  'year' => $timestart->format('Y'),
2493                  'hour' => $timestart->format('G'),
2494                  'minute' => 0,
2495              ],
2496              'eventtype' => 'group',
2497              'groupid' => $group->id,
2498              'groupcourseid' => $course->id,
2499              'description' => [
2500                  'text' => '',
2501                  'format' => 1,
2502                  'itemid' => 0
2503              ],
2504              'location' => 'Test',
2505              'duration' => 1,
2506              'timedurationuntil' => [
2507                  'day' => $timedurationuntil->format('j'),
2508                  'month' => $timedurationuntil->format('n'),
2509                  'year' => $timedurationuntil->format('Y'),
2510                  'hour' => $timedurationuntil->format('G'),
2511                  'minute' => 0,
2512              ]
2513          ];
2514  
2515          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2516          $querystring = http_build_query($formdata, '', '&');
2517  
2518          $generator->enrol_user($user->id, $course->id, 'student');
2519          $generator->role_assign($roleid, $user->id, $context->id);
2520  
2521          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2522          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context, true);
2523  
2524          $user->ignoresesskey = true;
2525          $this->resetAfterTest(true);
2526          $this->setUser($user);
2527  
2528          $result = \external_api::clean_returnvalue(
2529              core_calendar_external::submit_create_update_form_returns(),
2530              core_calendar_external::submit_create_update_form($querystring)
2531          );
2532  
2533          $event = $result['event'];
2534          $this->assertEquals($user->id, $event['userid']);
2535          $this->assertEquals($formdata['eventtype'], $event['eventtype']);
2536          $this->assertEquals($formdata['name'], $event['name']);
2537          $this->assertEquals($group->id, $event['groupid']);
2538      }
2539  
2540      /**
2541       * A user should not be able to create an event for any group that they are not a
2542       * member of in a course in which they are enrolled but don't have the
2543       * moodle/site:accessallgroups capability.
2544       */
2545      public function test_submit_create_update_form_create_group_event_non_member_no_permission() {
2546          $generator = $this->getDataGenerator();
2547          $user = $generator->create_user();
2548          $course = $generator->create_course();
2549          $group = $generator->create_group(array('courseid' => $course->id));
2550          $context = \context_course::instance($course->id);
2551          $roleid = $generator->create_role();
2552          $timestart = new \DateTime();
2553          $interval = new \DateInterval("P1D"); // One day.
2554          $timedurationuntil = new \DateTime();
2555          $timedurationuntil->add($interval);
2556          $formdata = [
2557              'id' => 0,
2558              'userid' => $user->id,
2559              'modulename' => '',
2560              'instance' => 0,
2561              'visible' => 1,
2562              'name' => 'Test',
2563              'timestart' => [
2564                  'day' => $timestart->format('j'),
2565                  'month' => $timestart->format('n'),
2566                  'year' => $timestart->format('Y'),
2567                  'hour' => $timestart->format('G'),
2568                  'minute' => 0,
2569              ],
2570              'eventtype' => 'group',
2571              'groupid' => $group->id,
2572              'groupcourseid' => $course->id,
2573              'description' => [
2574                  'text' => '',
2575                  'format' => 1,
2576              ],
2577              'location' => 'Test',
2578              'duration' => 1,
2579              'timedurationuntil' => [
2580                  'day' => $timedurationuntil->format('j'),
2581                  'month' => $timedurationuntil->format('n'),
2582                  'year' => $timedurationuntil->format('Y'),
2583                  'hour' => $timedurationuntil->format('G'),
2584                  'minute' => 0,
2585              ]
2586          ];
2587  
2588          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2589          $querystring = http_build_query($formdata, '', '&');
2590  
2591          $generator->enrol_user($user->id, $course->id, 'student');
2592          $generator->role_assign($roleid, $user->id, $context->id);
2593  
2594          assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
2595          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
2596  
2597          $user->ignoresesskey = true;
2598          $this->resetAfterTest(true);
2599          $this->setUser($user);
2600  
2601          $result = \external_api::clean_returnvalue(
2602              core_calendar_external::submit_create_update_form_returns(),
2603              core_calendar_external::submit_create_update_form($querystring)
2604          );
2605  
2606          $this->assertTrue($result['validationerror']);
2607      }
2608  
2609      /**
2610       * A user should not be able load the calendar monthly view for a course they cannot access.
2611       */
2612      public function test_get_calendar_monthly_view_no_course_permission() {
2613          global $USER;
2614          $this->resetAfterTest(true);
2615          $this->setAdminUser();
2616  
2617          $generator = $this->getDataGenerator();
2618          $user1 = $generator->create_user();
2619          $user2 = $generator->create_user();
2620          $course = $generator->create_course();
2621          $generator->enrol_user($user1->id, $course->id, 'student');
2622          $name = 'Course Event (course' . $course->id . ')';
2623          $record = new \stdClass();
2624          $record->courseid = $course->id;
2625          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2626  
2627          $timestart = new \DateTime();
2628          // Admin can load the course.
2629          $data = \external_api::clean_returnvalue(
2630              core_calendar_external::get_calendar_monthly_view_returns(),
2631              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2632                                                                $course->id, null, false, true, $timestart->format('j'))
2633          );
2634          $this->assertEquals($data['courseid'], $course->id);
2635          // User enrolled in the course can load the course calendar.
2636          $this->setUser($user1);
2637          $data = \external_api::clean_returnvalue(
2638              core_calendar_external::get_calendar_monthly_view_returns(),
2639              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2640                                                                $course->id, null, false, true, $timestart->format('j'))
2641          );
2642          $this->assertEquals($data['courseid'], $course->id);
2643          // User not enrolled in the course cannot load the course calendar.
2644          $this->setUser($user2);
2645          $this->expectException(\require_login_exception::class);
2646          $data = \external_api::clean_returnvalue(
2647              core_calendar_external::get_calendar_monthly_view_returns(),
2648              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2649                                                                $course->id, null, false, false, $timestart->format('j'))
2650          );
2651      }
2652  
2653      /**
2654       * Test get_calendar_monthly_view when a day parameter is provided.
2655       */
2656      public function test_get_calendar_monthly_view_with_day_provided() {
2657          $this->resetAfterTest();
2658          $this->setAdminUser();
2659  
2660          $timestart = new \DateTime();
2661          $data = \external_api::clean_returnvalue(
2662              core_calendar_external::get_calendar_monthly_view_returns(),
2663              core_calendar_external::get_calendar_monthly_view($timestart->format('Y'), $timestart->format('n'),
2664                                                                SITEID, null, false, true, $timestart->format('j'))
2665          );
2666          $this->assertEquals($data['date']['mday'], $timestart->format('d'));
2667      }
2668  
2669      /**
2670       * A user should not be able load the calendar day view for a course they cannot access.
2671       */
2672      public function test_get_calendar_day_view_no_course_permission() {
2673          global $USER;
2674          $this->resetAfterTest(true);
2675          $this->setAdminUser();
2676  
2677          $generator = $this->getDataGenerator();
2678          $user1 = $generator->create_user();
2679          $user2 = $generator->create_user();
2680          $course = $generator->create_course();
2681          $generator->enrol_user($user1->id, $course->id, 'student');
2682          $name = 'Course Event (course' . $course->id . ')';
2683          $record = new \stdClass();
2684          $record->courseid = $course->id;
2685          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2686  
2687          $timestart = new \DateTime();
2688          // Admin can load the course.
2689          $data = \external_api::clean_returnvalue(
2690              core_calendar_external::get_calendar_day_view_returns(),
2691              core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'),
2692                                                            $timestart->format('j'), $course->id, null)
2693          );
2694          $this->assertEquals($data['courseid'], $course->id);
2695          // User enrolled in the course can load the course calendar.
2696          $this->setUser($user1);
2697          $data = \external_api::clean_returnvalue(
2698              core_calendar_external::get_calendar_day_view_returns(),
2699              core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'),
2700                                                            $timestart->format('j'), $course->id, null)
2701          );
2702          $this->assertEquals($data['courseid'], $course->id);
2703          // User not enrolled in the course cannot load the course calendar.
2704          $this->setUser($user2);
2705          $this->expectException(\require_login_exception::class);
2706          $data = \external_api::clean_returnvalue(
2707              core_calendar_external::get_calendar_day_view_returns(),
2708              core_calendar_external::get_calendar_day_view($timestart->format('Y'), $timestart->format('n'),
2709                                                            $timestart->format('j'), $course->id, null)
2710          );
2711      }
2712  
2713      /**
2714       * A user should not be able load the calendar upcoming view for a course they cannot access.
2715       */
2716      public function test_get_calendar_upcoming_view_no_course_permission() {
2717          global $USER;
2718          $this->resetAfterTest(true);
2719          $this->setAdminUser();
2720  
2721          $generator = $this->getDataGenerator();
2722          $user1 = $generator->create_user();
2723          $user2 = $generator->create_user();
2724          $course = $generator->create_course();
2725          $generator->enrol_user($user1->id, $course->id, 'student');
2726          $name = 'Course Event (course' . $course->id . ')';
2727          $record = new \stdClass();
2728          $record->courseid = $course->id;
2729          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2730  
2731          // Admin can load the course.
2732          $data = \external_api::clean_returnvalue(
2733              core_calendar_external::get_calendar_upcoming_view_returns(),
2734              core_calendar_external::get_calendar_upcoming_view($course->id, null)
2735          );
2736          $this->assertEquals($data['courseid'], $course->id);
2737          // User enrolled in the course can load the course calendar.
2738          $this->setUser($user1);
2739          $data = \external_api::clean_returnvalue(
2740              core_calendar_external::get_calendar_upcoming_view_returns(),
2741              core_calendar_external::get_calendar_upcoming_view($course->id, null)
2742          );
2743          $this->assertEquals($data['courseid'], $course->id);
2744          // User not enrolled in the course cannot load the course calendar.
2745          $this->setUser($user2);
2746          $this->expectException(\require_login_exception::class);
2747          $data = \external_api::clean_returnvalue(
2748              core_calendar_external::get_calendar_upcoming_view_returns(),
2749              core_calendar_external::get_calendar_upcoming_view($course->id, null)
2750          );
2751      }
2752  
2753      /**
2754       * A user should not be able load the calendar event for a course they cannot access.
2755       */
2756      public function test_get_calendar_event_by_id_no_course_permission() {
2757          global $USER;
2758          $this->resetAfterTest(true);
2759          $this->setAdminUser();
2760  
2761          $generator = $this->getDataGenerator();
2762          $user1 = $generator->create_user();
2763          $user2 = $generator->create_user();
2764          $course = $generator->create_course();
2765          $generator->enrol_user($user1->id, $course->id, 'student');
2766          $name = 'Course Event (course' . $course->id . ')';
2767          $record = new \stdClass();
2768          $record->courseid = $course->id;
2769          $courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
2770  
2771          // Admin can load the course event.
2772          $data = \external_api::clean_returnvalue(
2773              core_calendar_external::get_calendar_event_by_id_returns(),
2774              core_calendar_external::get_calendar_event_by_id($courseevent->id)
2775          );
2776          $this->assertEquals($data['event']['id'], $courseevent->id);
2777          // User enrolled in the course can load the course event.
2778          $this->setUser($user1);
2779          $data = \external_api::clean_returnvalue(
2780              core_calendar_external::get_calendar_event_by_id_returns(),
2781              core_calendar_external::get_calendar_event_by_id($courseevent->id)
2782          );
2783          $this->assertEquals($data['event']['id'], $courseevent->id);
2784          // User not enrolled in the course cannot load the course event.
2785          $this->setUser($user2);
2786          $this->expectException(\moodle_exception::class);
2787          $data = \external_api::clean_returnvalue(
2788              core_calendar_external::get_calendar_event_by_id_returns(),
2789              core_calendar_external::get_calendar_event_by_id($courseevent->id)
2790          );
2791      }
2792  
2793      /**
2794       * User data for testing reading calendar events.
2795       *
2796       * @return array
2797       */
2798      public function get_calendar_event_by_id_prevent_read_other_users_events_data_provider(): array {
2799          $syscontext = \context_system::instance();
2800          $managerrole = 'manager';
2801          return [
2802              [true, false, $syscontext, $managerrole, true],
2803              [false, false, $syscontext, $managerrole, false],
2804              [false, false, null, null, true],
2805              [false, true, null, null, false],
2806          ];
2807      }
2808  
2809      /**
2810       * Prevent user from reading other user's event.
2811       *
2812       * @covers \core_calendar_external::get_calendar_event_by_id
2813       * @dataProvider get_calendar_event_by_id_prevent_read_other_users_events_data_provider
2814       *
2815       * @param bool          $isadminevent      Is admin's event
2816       * @param bool          $isadmin           Is current user admin user
2817       * @param null|stdClass $readerrolecontext Reader role context
2818       * @param null|string   $readerrolename    Role name
2819       * @param bool          $expectexception   Should the test throw exception
2820       */
2821      public function test_get_calendar_event_by_id_prevent_read_other_users_events(
2822              bool $isadminevent, bool $isadmin, ?\stdClass $readerrolecontext,
2823              ?string $readerrolename, bool $expectexception) {
2824          global $USER, $DB;
2825  
2826          $this->resetAfterTest();
2827          $generator = $this->getDataGenerator();
2828  
2829          if ($isadminevent) {
2830              $this->setAdminUser();
2831          } else {
2832              $user = $generator->create_user();
2833              $this->setUser($user);
2834          }
2835          $userevent = $this->create_calendar_event('user event', $USER->id, 'user', 0, time());
2836          $results = \external_api::clean_returnvalue(
2837              core_calendar_external::get_calendar_event_by_id_returns(),
2838              core_calendar_external::get_calendar_event_by_id($userevent->id)
2839          );
2840          $event = reset($results);
2841          $this->assertEquals($userevent->id, $event['id']);
2842  
2843          if ($isadmin) {
2844              $this->setAdminUser();
2845          } else {
2846              $reader = $generator->create_user();
2847              if ($readerrolename && $readerrolecontext) {
2848                  $managerroleid = $DB->get_field('role', 'id', ['shortname' => $readerrolename]);
2849                  role_assign($managerroleid, $reader->id, $readerrolecontext->id);
2850              }
2851              $this->setUser($reader);
2852          }
2853  
2854          if ($expectexception) {
2855              // Setup if exception is expected for the test.
2856              $this->expectException(\moodle_exception::class);
2857          }
2858          \external_api::clean_returnvalue(
2859              core_calendar_external::get_calendar_event_by_id_returns(),
2860              core_calendar_external::get_calendar_event_by_id($userevent->id)
2861          );
2862      }
2863  
2864      /**
2865       * User data for testing editing or deleting calendar events.
2866       *
2867       * @return array
2868       */
2869      public function edit_or_delete_other_users_events_data_provider(): array {
2870          $syscontext = \context_system::instance();
2871          $managerrole = 'manager';
2872          return [
2873              [false, false, $syscontext, $managerrole, false],
2874              [false, true, $syscontext, $managerrole, true],
2875              [false, false, null, null, true],
2876              [true, false, null, null, false],
2877          ];
2878      }
2879  
2880      /**
2881       * Test the behavior of deleting other users' user events.
2882       *
2883       * @dataProvider edit_or_delete_other_users_events_data_provider
2884       * @covers \core_calendar_external::delete_calendar_events
2885       * @param bool          $isadmin Whether the current user is admin.
2886       * @param bool          $isadminevent Whether it's an admin event or not.
2887       * @param stdClass|null $writerrolecontext The reader role context.
2888       * @param string|null   $writerrolename The role name.
2889       * @param bool          $expectexception Whether the test should throw an exception or not.
2890       */
2891      public function test_delete_other_users_events(bool $isadmin, bool $isadminevent,
2892              ?\stdClass $writerrolecontext, ?string $writerrolename, bool $expectexception) {
2893          global $DB, $USER;
2894  
2895          $this->resetAfterTest();
2896          $generator = $this->getDataGenerator();
2897  
2898          if ($isadminevent) {
2899              $this->setAdminUser();
2900              $user = $USER;
2901          } else {
2902              $user = $generator->create_user();
2903              $this->setUser($user);
2904          }
2905          $userevent = $this->create_calendar_event('user event', $user->id, 'user', 0, time());
2906  
2907          if ($isadmin) {
2908              $this->setAdminUser();
2909          } else {
2910              $writer = $generator->create_user();
2911              if ($writerrolename && $writerrolecontext) {
2912                  $managerroleid = $DB->get_field('role', 'id', ['shortname' => $writerrolename]);
2913                  role_assign($managerroleid, $writer->id, $writerrolecontext->id);
2914              }
2915              $this->setUser($writer);
2916          }
2917  
2918          if ($expectexception) {
2919              $this->expectException(\moodle_exception::class);
2920          }
2921          $events = [
2922              ['eventid' => $userevent->id, 'repeat' => 0]
2923          ];
2924          core_calendar_external::delete_calendar_events($events);
2925      }
2926  
2927      /**
2928       * Test the behavior of editing other users' user events
2929       *
2930       * @dataProvider edit_or_delete_other_users_events_data_provider
2931       * @covers \core_calendar_external::submit_create_update_form
2932       * @param bool          $isadmin Whether the current user is admin.
2933       * @param bool          $isadminevent Whether it's an admin event or not.
2934       * @param stdClass|null $writerrolecontext The reader role context.
2935       * @param string|null   $writerrolename The role name.
2936       * @param bool          $expectexception Whether the test should throw an exception or not.
2937       */
2938      public function test_edit_other_users_events(bool $isadmin, bool $isadminevent,
2939              ?\stdClass $writerrolecontext, ?string $writerrolename, bool $expectexception) {
2940          global $DB, $USER;
2941  
2942          $this->resetAfterTest();
2943  
2944          $generator = $this->getDataGenerator();
2945          if ($isadminevent) {
2946              $this->setAdminUser();
2947              $user = $USER;
2948          } else {
2949              $user = $generator->create_user();
2950          }
2951  
2952          $formdata = [
2953              'id' => 0,
2954              'userid' => $user->id,
2955              'modulename' => '',
2956              'instance' => 0,
2957              'visible' => 1,
2958              'eventtype' => 'user',
2959              'name' => 'Test',
2960              'timestart' => [
2961                  'day' => 1,
2962                  'month' => 1,
2963                  'year' => 2021,
2964                  'hour' => 1,
2965                  'minute' => 0,
2966              ],
2967              'description' => [
2968                  'text' => 'xxxxx',
2969                  'format' => 1,
2970                  'itemid' => 0
2971              ],
2972              'location' => 'Test',
2973              'duration' => 0,
2974          ];
2975          $formdata = \core_calendar\local\event\forms\create::mock_generate_submit_keys($formdata);
2976  
2977          $querystring = http_build_query($formdata, '', '&');
2978  
2979          if ($isadmin) {
2980              $this->setAdminUser();
2981          } else {
2982              $writer = $generator->create_user();
2983              if ($writerrolename && $writerrolecontext) {
2984                  $managerroleid = $DB->get_field('role', 'id', ['shortname' => $writerrolename]);
2985                  role_assign($managerroleid, $writer->id, $writerrolecontext->id);
2986              }
2987              $this->setUser($writer);
2988          }
2989          $USER->ignoresesskey = true;
2990  
2991          if ($expectexception) {
2992              $this->expectException(\moodle_exception::class);
2993          }
2994          core_calendar_external::submit_create_update_form($querystring);
2995      }
2996  
2997      /**
2998       * A user should not be able load the calendar events for a category they cannot see.
2999       */
3000      public function test_get_calendar_events_hidden_category() {
3001          global $USER;
3002          $this->resetAfterTest(true);
3003          $this->setAdminUser();
3004  
3005          $generator = $this->getDataGenerator();
3006          $user1 = $generator->create_user();
3007          $category = $generator->create_category(['visible' => 0]);
3008          $name = 'Category Event (category: ' . $category->id . ')';
3009          $record = new \stdClass();
3010          $record->categoryid = $category->id;
3011          $categoryevent = $this->create_calendar_event($name, $USER->id, 'category', 0, time(), $record);
3012  
3013          $events = [
3014              'eventids' => [$categoryevent->id]
3015          ];
3016          $options = [];
3017          // Admin can load the category event.
3018          $data = \external_api::clean_returnvalue(
3019              core_calendar_external::get_calendar_events_returns(),
3020              core_calendar_external::get_calendar_events($events, $options)
3021          );
3022          $this->assertEquals($data['events'][0]['id'], $categoryevent->id);
3023          // User with no special permission to see hidden categories will not see the event.
3024          $this->setUser($user1);
3025          $data = \external_api::clean_returnvalue(
3026              core_calendar_external::get_calendar_events_returns(),
3027              core_calendar_external::get_calendar_events($events, $options)
3028          );
3029          $this->assertCount(0, $data['events']);
3030          $this->assertEquals('nopermissions', $data['warnings'][0]['warningcode']);
3031      }
3032  
3033      /**
3034       * Test get_calendar_access_information for admins.
3035       */
3036      public function test_get_calendar_access_information_for_admins() {
3037          global $CFG;
3038          $this->resetAfterTest(true);
3039          $this->setAdminUser();
3040  
3041          $CFG->calendar_adminseesall = 1;
3042  
3043          $data = \external_api::clean_returnvalue(
3044              core_calendar_external::get_calendar_access_information_returns(),
3045              core_calendar_external::get_calendar_access_information()
3046          );
3047          $this->assertTrue($data['canmanageownentries']);
3048          $this->assertTrue($data['canmanagegroupentries']);
3049          $this->assertTrue($data['canmanageentries']);
3050      }
3051  
3052      /**
3053       * Test get_calendar_access_information for authenticated users.
3054       */
3055      public function test_get_calendar_access_information_for_authenticated_users() {
3056          $this->resetAfterTest(true);
3057          $this->setUser($this->getDataGenerator()->create_user());
3058  
3059          $data = \external_api::clean_returnvalue(
3060              core_calendar_external::get_calendar_access_information_returns(),
3061              core_calendar_external::get_calendar_access_information()
3062          );
3063          $this->assertTrue($data['canmanageownentries']);
3064          $this->assertFalse($data['canmanagegroupentries']);
3065          $this->assertFalse($data['canmanageentries']);
3066      }
3067  
3068      /**
3069       * Test get_calendar_access_information for student users.
3070       */
3071      public function test_get_calendar_access_information_for_student_users() {
3072          global $DB;
3073          $this->resetAfterTest(true);
3074  
3075          $user = $this->getDataGenerator()->create_user();
3076          $course = $this->getDataGenerator()->create_course();
3077          $role = $DB->get_record('role', array('shortname' => 'student'));
3078          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
3079  
3080          $this->setUser($user);
3081  
3082          $data = \external_api::clean_returnvalue(
3083              core_calendar_external::get_calendar_access_information_returns(),
3084              core_calendar_external::get_calendar_access_information($course->id)
3085          );
3086          $this->assertTrue($data['canmanageownentries']);
3087          $this->assertFalse($data['canmanagegroupentries']);
3088          $this->assertFalse($data['canmanageentries']);
3089      }
3090  
3091      /**
3092       * Test get_calendar_access_information for teacher users.
3093       */
3094      public function test_get_calendar_access_information_for_teacher_users() {
3095          global $DB;
3096          $this->resetAfterTest(true);
3097  
3098          $user = $this->getDataGenerator()->create_user();
3099          $course = $this->getDataGenerator()->create_course(['groupmode' => 1]);
3100          $role = $DB->get_record('role', array('shortname' => 'editingteacher'));
3101          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
3102          $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3103  
3104          $this->setUser($user);
3105  
3106          $data = \external_api::clean_returnvalue(
3107              core_calendar_external::get_calendar_access_information_returns(),
3108              core_calendar_external::get_calendar_access_information($course->id)
3109          );
3110          $this->assertTrue($data['canmanageownentries']);
3111          $this->assertTrue($data['canmanagegroupentries']);
3112          $this->assertTrue($data['canmanageentries']);
3113      }
3114  
3115      /**
3116       * Test get_allowed_event_types for admins.
3117       */
3118      public function test_get_allowed_event_types_for_admins() {
3119          global $CFG;
3120          $this->resetAfterTest(true);
3121          $this->setAdminUser();
3122          $CFG->calendar_adminseesall = 1;
3123          $data = \external_api::clean_returnvalue(
3124              core_calendar_external::get_allowed_event_types_returns(),
3125              core_calendar_external::get_allowed_event_types()
3126          );
3127          $this->assertEquals(['user', 'site', 'course', 'category'], $data['allowedeventtypes']);
3128      }
3129      /**
3130       * Test get_allowed_event_types for authenticated users.
3131       */
3132      public function test_get_allowed_event_types_for_authenticated_users() {
3133          $this->resetAfterTest(true);
3134          $this->setUser($this->getDataGenerator()->create_user());
3135          $data = \external_api::clean_returnvalue(
3136              core_calendar_external::get_allowed_event_types_returns(),
3137              core_calendar_external::get_allowed_event_types()
3138          );
3139          $this->assertEquals(['user'], $data['allowedeventtypes']);
3140      }
3141      /**
3142       * Test get_allowed_event_types for student users.
3143       */
3144      public function test_get_allowed_event_types_for_student_users() {
3145          global $DB;
3146          $this->resetAfterTest(true);
3147          $user = $this->getDataGenerator()->create_user();
3148          $course = $this->getDataGenerator()->create_course();
3149          $role = $DB->get_record('role', array('shortname' => 'student'));
3150          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
3151          $this->setUser($user);
3152          $data = \external_api::clean_returnvalue(
3153              core_calendar_external::get_allowed_event_types_returns(),
3154              core_calendar_external::get_allowed_event_types($course->id)
3155          );
3156          $this->assertEquals(['user'], $data['allowedeventtypes']);
3157      }
3158      /**
3159       * Test get_allowed_event_types for teacher users.
3160       */
3161      public function test_get_allowed_event_types_for_teacher_users() {
3162          global $DB;
3163          $this->resetAfterTest(true);
3164          $user = $this->getDataGenerator()->create_user();
3165          $course = $this->getDataGenerator()->create_course(['groupmode' => 1]);
3166          $role = $DB->get_record('role', array('shortname' => 'editingteacher'));
3167          $this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
3168          $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3169          $this->setUser($user);
3170          $data = \external_api::clean_returnvalue(
3171              core_calendar_external::get_allowed_event_types_returns(),
3172              core_calendar_external::get_allowed_event_types($course->id)
3173          );
3174          $this->assertEquals(['user', 'course', 'group'], $data['allowedeventtypes']);
3175      }
3176  
3177      /**
3178       * Test get_timestamps with string keys, with and without optional hour/minute values.
3179       */
3180      public function test_get_timestamps_string_keys() {
3181          $this->resetAfterTest(true);
3182          $this->setAdminUser();
3183  
3184          $time1 = new \DateTime('2018-12-30 00:00:00');
3185          $time2 = new \DateTime('2019-03-27 23:59:00');
3186  
3187          $dates = [
3188              [
3189                  'key' => 'from',
3190                  'year' => $time1->format('Y'),
3191                  'month' => $time1->format('m'),
3192                  'day' => $time1->format('d'),
3193              ],
3194              [
3195                  'key' => 'to',
3196                  'year' => $time2->format('Y'),
3197                  'month' => (int) $time2->format('m'),
3198                  'day' => $time2->format('d'),
3199                  'hour' => $time2->format('H'),
3200                  'minute' => $time2->format('i'),
3201              ],
3202          ];
3203  
3204          $expectedtimestamps = [
3205              'from' => $time1->getTimestamp(),
3206              'to' => $time2->getTimestamp(),
3207          ];
3208  
3209          $result = core_calendar_external::get_timestamps($dates);
3210  
3211          $this->assertEquals(['timestamps'], array_keys($result));
3212          $this->assertEquals(2, count($result['timestamps']));
3213  
3214          foreach ($result['timestamps'] as $data) {
3215              $this->assertTrue(in_array($data['key'], ['from', 'to']));
3216              $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']);
3217          }
3218      }
3219  
3220      /**
3221       * Test get_timestamps with no keys specified, with and without optional hour/minute values.
3222       */
3223      public function test_get_timestamps_no_keys() {
3224          $this->resetAfterTest(true);
3225          $this->setAdminUser();
3226  
3227          $time1 = new \DateTime('2018-12-30 00:00:00');
3228          $time2 = new \DateTime('2019-03-27 23:59:00');
3229  
3230          $dates = [
3231              [
3232                  'year' => $time1->format('Y'),
3233                  'month' => $time1->format('m'),
3234                  'day' => $time1->format('d'),
3235              ],
3236              [
3237                  'year' => $time2->format('Y'),
3238                  'month' => (int) $time2->format('m'),
3239                  'day' => $time2->format('d'),
3240                  'hour' => $time2->format('H'),
3241                  'minute' => $time2->format('i'),
3242              ],
3243          ];
3244  
3245          $expectedtimestamps = [
3246              0 => $time1->getTimestamp(),
3247              1 => $time2->getTimestamp(),
3248          ];
3249  
3250          $result = core_calendar_external::get_timestamps($dates);
3251  
3252          $this->assertEquals(['timestamps'], array_keys($result));
3253          $this->assertEquals(2, count($result['timestamps']));
3254  
3255          foreach ($result['timestamps'] as $data) {
3256              $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']);
3257          }
3258      }
3259  }