Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

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