Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

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