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.
  • /calendar/ -> lib.php (source)

    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  /**
      18   * Calendar extension
      19   *
      20   * @package    core_calendar
      21   * @copyright  2004 Greek School Network (http://www.sch.gr), Jon Papaioannou,
      22   *             Avgoustos Tsinakos
      23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      24   */
      25  
      26  if (!defined('MOODLE_INTERNAL')) {
      27      die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
      28  }
      29  
      30  /**
      31   *  These are read by the administration component to provide default values
      32   */
      33  
      34  /**
      35   * CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD - default value of upcoming event preference
      36   */
      37  define('CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD', 21);
      38  
      39  /**
      40   * CALENDAR_DEFAULT_UPCOMING_MAXEVENTS - default value to display the maximum number of upcoming event
      41   */
      42  define('CALENDAR_DEFAULT_UPCOMING_MAXEVENTS', 10);
      43  
      44  /**
      45   * CALENDAR_DEFAULT_STARTING_WEEKDAY - default value to display the starting weekday
      46   */
      47  define('CALENDAR_DEFAULT_STARTING_WEEKDAY', 1);
      48  
      49  // This is a packed bitfield: day X is "weekend" if $field & (1 << X) is true
      50  // Default value = 65 = 64 + 1 = 2^6 + 2^0 = Saturday & Sunday
      51  
      52  /**
      53   * CALENDAR_DEFAULT_WEEKEND - default value for weekend (Saturday & Sunday)
      54   */
      55  define('CALENDAR_DEFAULT_WEEKEND', 65);
      56  
      57  /**
      58   * CALENDAR_URL - path to calendar's folder
      59   */
      60  define('CALENDAR_URL', $CFG->wwwroot.'/calendar/');
      61  
      62  /**
      63   * CALENDAR_TF_24 - Calendar time in 24 hours format
      64   */
      65  define('CALENDAR_TF_24', '%H:%M');
      66  
      67  /**
      68   * CALENDAR_TF_12 - Calendar time in 12 hours format
      69   */
      70  define('CALENDAR_TF_12', '%I:%M %p');
      71  
      72  /**
      73   * CALENDAR_EVENT_GLOBAL - Site calendar event types
      74   * @deprecated since 3.8
      75   */
      76  define('CALENDAR_EVENT_GLOBAL', 1);
      77  
      78  /**
      79   * CALENDAR_EVENT_SITE - Site calendar event types
      80   */
      81  define('CALENDAR_EVENT_SITE', 1);
      82  
      83  /**
      84   * CALENDAR_EVENT_COURSE - Course calendar event types
      85   */
      86  define('CALENDAR_EVENT_COURSE', 2);
      87  
      88  /**
      89   * CALENDAR_EVENT_GROUP - group calendar event types
      90   */
      91  define('CALENDAR_EVENT_GROUP', 4);
      92  
      93  /**
      94   * CALENDAR_EVENT_USER - user calendar event types
      95   */
      96  define('CALENDAR_EVENT_USER', 8);
      97  
      98  /**
      99   * CALENDAR_EVENT_COURSECAT - Course category calendar event types
     100   */
     101  define('CALENDAR_EVENT_COURSECAT', 16);
     102  
     103  /**
     104   * CALENDAR_IMPORT_FROM_FILE - import the calendar from a file
     105   */
     106  define('CALENDAR_IMPORT_FROM_FILE', 0);
     107  
     108  /**
     109   * CALENDAR_IMPORT_FROM_URL - import the calendar from a URL
     110   */
     111  define('CALENDAR_IMPORT_FROM_URL',  1);
     112  
     113  /**
     114   * CALENDAR_IMPORT_EVENT_UPDATED_SKIPPED - imported event was skipped
     115   */
     116  define('CALENDAR_IMPORT_EVENT_SKIPPED',  -1);
     117  
     118  /**
     119   * CALENDAR_IMPORT_EVENT_UPDATED - imported event was updated
     120   */
     121  define('CALENDAR_IMPORT_EVENT_UPDATED',  1);
     122  
     123  /**
     124   * CALENDAR_IMPORT_EVENT_INSERTED - imported event was added by insert
     125   */
     126  define('CALENDAR_IMPORT_EVENT_INSERTED', 2);
     127  
     128  /**
     129   * CALENDAR_SUBSCRIPTION_UPDATE - Used to represent update action for subscriptions in various forms.
     130   */
     131  define('CALENDAR_SUBSCRIPTION_UPDATE', 1);
     132  
     133  /**
     134   * CALENDAR_SUBSCRIPTION_REMOVE - Used to represent remove action for subscriptions in various forms.
     135   */
     136  define('CALENDAR_SUBSCRIPTION_REMOVE', 2);
     137  
     138  /**
     139   * CALENDAR_EVENT_USER_OVERRIDE_PRIORITY - Constant for the user override priority.
     140   */
     141  define('CALENDAR_EVENT_USER_OVERRIDE_PRIORITY', 0);
     142  
     143  /**
     144   * CALENDAR_EVENT_TYPE_STANDARD - Standard events.
     145   */
     146  define('CALENDAR_EVENT_TYPE_STANDARD', 0);
     147  
     148  /**
     149   * CALENDAR_EVENT_TYPE_ACTION - Action events.
     150   */
     151  define('CALENDAR_EVENT_TYPE_ACTION', 1);
     152  
     153  /**
     154   * Manage calendar events.
     155   *
     156   * This class provides the required functionality in order to manage calendar events.
     157   * It was introduced as part of Moodle 2.0 and was created in order to provide a
     158   * better framework for dealing with calendar events in particular regard to file
     159   * handling through the new file API.
     160   *
     161   * @package    core_calendar
     162   * @category   calendar
     163   * @copyright  2009 Sam Hemelryk
     164   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     165   *
     166   * @property int $id The id within the event table
     167   * @property string $name The name of the event
     168   * @property string $description The description of the event
     169   * @property int $format The format of the description FORMAT_?
     170   * @property int $courseid The course the event is associated with (0 if none)
     171   * @property int $groupid The group the event is associated with (0 if none)
     172   * @property int $userid The user the event is associated with (0 if none)
     173   * @property int $repeatid If this is a repeated event this will be set to the
     174   *                          id of the original
     175   * @property string $component If created by a plugin/component (other than module), the full frankenstyle name of a component
     176   * @property string $modulename If added by a module this will be the module name
     177   * @property int $instance If added by a module this will be the module instance
     178   * @property string $eventtype The event type
     179   * @property int $timestart The start time as a timestamp
     180   * @property int $timeduration The duration of the event in seconds
     181   * @property int $timeusermidnight User midnight for the event
     182   * @property int $visible 1 if the event is visible
     183   * @property int $uuid ?
     184   * @property int $sequence ?
     185   * @property int $timemodified The time last modified as a timestamp
     186   */
     187  class calendar_event {
     188  
     189      /** @var array An object containing the event properties can be accessed via the magic __get/set methods */
     190      protected $properties = null;
     191  
     192      /** @var string The converted event discription with file paths resolved.
     193       *              This gets populated when someone requests description for the first time */
     194      protected $_description = null;
     195  
     196      /** @var array The options to use with this description editor */
     197      protected $editoroptions = array(
     198          'subdirs' => false,
     199          'forcehttps' => false,
     200          'maxfiles' => -1,
     201          'maxbytes' => null,
     202          'trusttext' => false);
     203  
     204      /** @var object The context to use with the description editor */
     205      protected $editorcontext = null;
     206  
     207      /**
     208       * Instantiates a new event and optionally populates its properties with the data provided.
     209       *
     210       * @param \stdClass $data Optional. An object containing the properties to for
     211       *                  an event
     212       */
     213      public function __construct($data = null) {
     214          global $CFG, $USER;
     215  
     216          // First convert to object if it is not already (should either be object or assoc array).
     217          if (!is_object($data)) {
     218              $data = (object) $data;
     219          }
     220  
     221          $this->editoroptions['maxbytes'] = $CFG->maxbytes;
     222  
     223          $data->eventrepeats = 0;
     224  
     225          if (empty($data->id)) {
     226              $data->id = null;
     227          }
     228  
     229          if (!empty($data->subscriptionid)) {
     230              $data->subscription = calendar_get_subscription($data->subscriptionid);
     231          }
     232  
     233          // Default to a user event.
     234          if (empty($data->eventtype)) {
     235              $data->eventtype = 'user';
     236          }
     237  
     238          // Default to the current user.
     239          if (empty($data->userid)) {
     240              $data->userid = $USER->id;
     241          }
     242  
     243          if (!empty($data->timeduration) && is_array($data->timeduration)) {
     244              $data->timeduration = make_timestamp(
     245                      $data->timeduration['year'], $data->timeduration['month'], $data->timeduration['day'],
     246                      $data->timeduration['hour'], $data->timeduration['minute']) - $data->timestart;
     247          }
     248  
     249          if (!empty($data->description) && is_array($data->description)) {
     250              $data->format = $data->description['format'];
     251              $data->description = $data->description['text'];
     252          } else if (empty($data->description)) {
     253              $data->description = '';
     254              $data->format = editors_get_preferred_format();
     255          }
     256  
     257          // Ensure form is defaulted correctly.
     258          if (empty($data->format)) {
     259              $data->format = editors_get_preferred_format();
     260          }
     261  
     262          if (empty($data->component)) {
     263              $data->component = null;
     264          }
     265  
     266          $this->properties = $data;
     267      }
     268  
     269      /**
     270       * Magic set method.
     271       *
     272       * Attempts to call a set_$key method if one exists otherwise falls back
     273       * to simply set the property.
     274       *
     275       * @param string $key property name
     276       * @param mixed $value value of the property
     277       */
     278      public function __set($key, $value) {
     279          if (method_exists($this, 'set_'.$key)) {
     280              $this->{'set_'.$key}($value);
     281          }
     282          $this->properties->{$key} = $value;
     283      }
     284  
     285      /**
     286       * Magic get method.
     287       *
     288       * Attempts to call a get_$key method to return the property and ralls over
     289       * to return the raw property.
     290       *
     291       * @param string $key property name
     292       * @return mixed property value
     293       * @throws \coding_exception
     294       */
     295      public function __get($key) {
     296          if (method_exists($this, 'get_'.$key)) {
     297              return $this->{'get_'.$key}();
     298          }
     299          if (!property_exists($this->properties, $key)) {
     300              throw new \coding_exception('Undefined property requested');
     301          }
     302          return $this->properties->{$key};
     303      }
     304  
     305      /**
     306       * Magic isset method.
     307       *
     308       * PHP needs an isset magic method if you use the get magic method and
     309       * still want empty calls to work.
     310       *
     311       * @param string $key $key property name
     312       * @return bool|mixed property value, false if property is not exist
     313       */
     314      public function __isset($key) {
     315          return !empty($this->properties->{$key});
     316      }
     317  
     318      /**
     319       * Calculate the context value needed for an event.
     320       *
     321       * Event's type can be determine by the available value store in $data
     322       * It is important to check for the existence of course/courseid to determine
     323       * the course event.
     324       * Default value is set to CONTEXT_USER
     325       *
     326       * @return \stdClass The context object.
     327       */
     328      protected function calculate_context() {
     329          global $USER, $DB;
     330  
     331          $context = null;
     332          if (isset($this->properties->categoryid) && $this->properties->categoryid > 0) {
     333              $context = \context_coursecat::instance($this->properties->categoryid);
     334          } else if (isset($this->properties->courseid) && $this->properties->courseid > 0) {
     335              $context = \context_course::instance($this->properties->courseid);
     336          } else if (isset($this->properties->course) && $this->properties->course > 0) {
     337              $context = \context_course::instance($this->properties->course);
     338          } else if (isset($this->properties->groupid) && $this->properties->groupid > 0) {
     339              $group = $DB->get_record('groups', array('id' => $this->properties->groupid));
     340              $context = \context_course::instance($group->courseid);
     341          } else if (isset($this->properties->userid) && $this->properties->userid > 0
     342              && $this->properties->userid == $USER->id) {
     343              $context = \context_user::instance($this->properties->userid);
     344          } else if (isset($this->properties->userid) && $this->properties->userid > 0
     345              && $this->properties->userid != $USER->id &&
     346              !empty($this->properties->modulename) &&
     347              isset($this->properties->instance) && $this->properties->instance > 0) {
     348              $cm = get_coursemodule_from_instance($this->properties->modulename, $this->properties->instance, 0,
     349                  false, MUST_EXIST);
     350              $context = \context_course::instance($cm->course);
     351          } else {
     352              $context = \context_user::instance($this->properties->userid);
     353          }
     354  
     355          return $context;
     356      }
     357  
     358      /**
     359       * Returns the context for this event. The context is calculated
     360       * the first time is is requested and then stored in a member
     361       * variable to be returned each subsequent time.
     362       *
     363       * This is a magical getter function that will be called when
     364       * ever the context property is accessed, e.g. $event->context.
     365       *
     366       * @return context
     367       */
     368      protected function get_context() {
     369          if (!isset($this->properties->context)) {
     370              $this->properties->context = $this->calculate_context();
     371          }
     372  
     373          return $this->properties->context;
     374      }
     375  
     376      /**
     377       * Returns an array of editoroptions for this event.
     378       *
     379       * @return array event editor options
     380       */
     381      protected function get_editoroptions() {
     382          return $this->editoroptions;
     383      }
     384  
     385      /**
     386       * Returns an event description: Called by __get
     387       * Please use $blah = $event->description;
     388       *
     389       * @return string event description
     390       */
     391      protected function get_description() {
     392          global $CFG;
     393  
     394          require_once($CFG->libdir . '/filelib.php');
     395  
     396          if ($this->_description === null) {
     397              // Check if we have already resolved the context for this event.
     398              if ($this->editorcontext === null) {
     399                  // Switch on the event type to decide upon the appropriate context to use for this event.
     400                  $this->editorcontext = $this->get_context();
     401                  if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
     402                      return clean_text($this->properties->description, $this->properties->format);
     403                  }
     404              }
     405  
     406              // Work out the item id for the editor, if this is a repeated event
     407              // then the files will be associated with the original.
     408              if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
     409                  $itemid = $this->properties->repeatid;
     410              } else {
     411                  $itemid = $this->properties->id;
     412              }
     413  
     414              // Convert file paths in the description so that things display correctly.
     415              $this->_description = file_rewrite_pluginfile_urls($this->properties->description, 'pluginfile.php',
     416                  $this->editorcontext->id, 'calendar', 'event_description', $itemid);
     417              // Clean the text so no nasties get through.
     418              $this->_description = clean_text($this->_description, $this->properties->format);
     419          }
     420  
     421          // Finally return the description.
     422          return $this->_description;
     423      }
     424  
     425      /**
     426       * Return the number of repeat events there are in this events series.
     427       *
     428       * @return int number of event repeated
     429       */
     430      public function count_repeats() {
     431          global $DB;
     432          if (!empty($this->properties->repeatid)) {
     433              $this->properties->eventrepeats = $DB->count_records('event',
     434                  array('repeatid' => $this->properties->repeatid));
     435              // We don't want to count ourselves.
     436              $this->properties->eventrepeats--;
     437          }
     438          return $this->properties->eventrepeats;
     439      }
     440  
     441      /**
     442       * Update or create an event within the database
     443       *
     444       * Pass in a object containing the event properties and this function will
     445       * insert it into the database and deal with any associated files
     446       *
     447       * Capability checking should be performed if the user is directly manipulating the event
     448       * and no other capability has been tested. However if the event is not being manipulated
     449       * directly by the user and another capability has been checked for them to do this then
     450       * capabilites should not be checked.
     451       *
     452       * For example if a user is editing an event in the calendar the check should be true,
     453       * but if you are updating an event in an activities settings are changed then the calendar
     454       * capabilites should not be checked.
     455       *
     456       * @see self::create()
     457       * @see self::update()
     458       *
     459       * @param \stdClass $data object of event
     460       * @param bool $checkcapability If Moodle should check the user can manage the calendar events for this call or not.
     461       * @return bool event updated
     462       */
     463      public function update($data, $checkcapability=true) {
     464          global $DB, $USER;
     465  
     466          foreach ($data as $key => $value) {
     467              $this->properties->$key = $value;
     468          }
     469  
     470          $this->properties->timemodified = time();
     471          $usingeditor = (!empty($this->properties->description) && is_array($this->properties->description));
     472  
     473          // Prepare event data.
     474          $eventargs = array(
     475              'context' => $this->get_context(),
     476              'objectid' => $this->properties->id,
     477              'other' => array(
     478                  'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
     479                  'timestart' => $this->properties->timestart,
     480                  'name' => $this->properties->name
     481              )
     482          );
     483  
     484          if (empty($this->properties->id) || $this->properties->id < 1) {
     485              if ($checkcapability) {
     486                  if (!calendar_add_event_allowed($this->properties)) {
     487                      print_error('nopermissiontoupdatecalendar');
     488                  }
     489              }
     490  
     491              if ($usingeditor) {
     492                  switch ($this->properties->eventtype) {
     493                      case 'user':
     494                          $this->properties->courseid = 0;
     495                          $this->properties->course = 0;
     496                          $this->properties->groupid = 0;
     497                          $this->properties->userid = $USER->id;
     498                          break;
     499                      case 'site':
     500                          $this->properties->courseid = SITEID;
     501                          $this->properties->course = SITEID;
     502                          $this->properties->groupid = 0;
     503                          $this->properties->userid = $USER->id;
     504                          break;
     505                      case 'course':
     506                          $this->properties->groupid = 0;
     507                          $this->properties->userid = $USER->id;
     508                          break;
     509                      case 'category':
     510                          $this->properties->groupid = 0;
     511                          $this->properties->category = 0;
     512                          $this->properties->userid = $USER->id;
     513                          break;
     514                      case 'group':
     515                          $this->properties->userid = $USER->id;
     516                          break;
     517                      default:
     518                          // We should NEVER get here, but just incase we do lets fail gracefully.
     519                          $usingeditor = false;
     520                          break;
     521                  }
     522  
     523                  // If we are actually using the editor, we recalculate the context because some default values
     524                  // were set when calculate_context() was called from the constructor.
     525                  if ($usingeditor) {
     526                      $this->properties->context = $this->calculate_context();
     527                      $this->editorcontext = $this->get_context();
     528                  }
     529  
     530                  $editor = $this->properties->description;
     531                  $this->properties->format = $this->properties->description['format'];
     532                  $this->properties->description = $this->properties->description['text'];
     533              }
     534  
     535              // Insert the event into the database.
     536              $this->properties->id = $DB->insert_record('event', $this->properties);
     537  
     538              if ($usingeditor) {
     539                  $this->properties->description = file_save_draft_area_files(
     540                      $editor['itemid'],
     541                      $this->editorcontext->id,
     542                      'calendar',
     543                      'event_description',
     544                      $this->properties->id,
     545                      $this->editoroptions,
     546                      $editor['text'],
     547                      $this->editoroptions['forcehttps']);
     548                  $DB->set_field('event', 'description', $this->properties->description,
     549                      array('id' => $this->properties->id));
     550              }
     551  
     552              // Log the event entry.
     553              $eventargs['objectid'] = $this->properties->id;
     554              $eventargs['context'] = $this->get_context();
     555              $event = \core\event\calendar_event_created::create($eventargs);
     556              $event->trigger();
     557  
     558              $repeatedids = array();
     559  
     560              if (!empty($this->properties->repeat)) {
     561                  $this->properties->repeatid = $this->properties->id;
     562                  $DB->set_field('event', 'repeatid', $this->properties->repeatid, array('id' => $this->properties->id));
     563  
     564                  $eventcopy = clone($this->properties);
     565                  unset($eventcopy->id);
     566  
     567                  $timestart = new \DateTime('@' . $eventcopy->timestart);
     568                  $timestart->setTimezone(\core_date::get_user_timezone_object());
     569  
     570                  for ($i = 1; $i < $eventcopy->repeats; $i++) {
     571  
     572                      $timestart->add(new \DateInterval('P7D'));
     573                      $eventcopy->timestart = $timestart->getTimestamp();
     574  
     575                      // Get the event id for the log record.
     576                      $eventcopyid = $DB->insert_record('event', $eventcopy);
     577  
     578                      // If the context has been set delete all associated files.
     579                      if ($usingeditor) {
     580                          $fs = get_file_storage();
     581                          $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description',
     582                              $this->properties->id);
     583                          foreach ($files as $file) {
     584                              $fs->create_file_from_storedfile(array('itemid' => $eventcopyid), $file);
     585                          }
     586                      }
     587  
     588                      $repeatedids[] = $eventcopyid;
     589  
     590                      // Trigger an event.
     591                      $eventargs['objectid'] = $eventcopyid;
     592                      $eventargs['other']['timestart'] = $eventcopy->timestart;
     593                      $event = \core\event\calendar_event_created::create($eventargs);
     594                      $event->trigger();
     595                  }
     596              }
     597  
     598              return true;
     599          } else {
     600  
     601              if ($checkcapability) {
     602                  if (!calendar_edit_event_allowed($this->properties)) {
     603                      print_error('nopermissiontoupdatecalendar');
     604                  }
     605              }
     606  
     607              if ($usingeditor) {
     608                  if ($this->editorcontext !== null) {
     609                      $this->properties->description = file_save_draft_area_files(
     610                          $this->properties->description['itemid'],
     611                          $this->editorcontext->id,
     612                          'calendar',
     613                          'event_description',
     614                          $this->properties->id,
     615                          $this->editoroptions,
     616                          $this->properties->description['text'],
     617                          $this->editoroptions['forcehttps']);
     618                  } else {
     619                      $this->properties->format = $this->properties->description['format'];
     620                      $this->properties->description = $this->properties->description['text'];
     621                  }
     622              }
     623  
     624              $event = $DB->get_record('event', array('id' => $this->properties->id));
     625  
     626              $updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
     627  
     628              if ($updaterepeated) {
     629  
     630                  $sqlset = 'name = ?,
     631                             description = ?,
     632                             timeduration = ?,
     633                             timemodified = ?,
     634                             groupid = ?,
     635                             courseid = ?';
     636  
     637                  // Note: Group and course id may not be set. If not, keep their current values.
     638                  $params = [
     639                      $this->properties->name,
     640                      $this->properties->description,
     641                      $this->properties->timeduration,
     642                      time(),
     643                      isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
     644                      isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
     645                  ];
     646  
     647                  // Note: Only update start date, if it was changed by the user.
     648                  if ($this->properties->timestart != $event->timestart) {
     649                      $timestartoffset = $this->properties->timestart - $event->timestart;
     650                      $sqlset .= ', timestart = timestart + ?';
     651                      $params[] = $timestartoffset;
     652                  }
     653  
     654                  // Note: Only update location, if it was changed by the user.
     655                  $updatelocation = (!empty($this->properties->location) && $this->properties->location !== $event->location);
     656                  if ($updatelocation) {
     657                      $sqlset .= ', location = ?';
     658                      $params[] = $this->properties->location;
     659                  }
     660  
     661                  // Update all.
     662                  $sql = "UPDATE {event}
     663                             SET $sqlset
     664                           WHERE repeatid = ?";
     665  
     666                  $params[] = $event->repeatid;
     667                  $DB->execute($sql, $params);
     668  
     669                  // Trigger an update event for each of the calendar event.
     670                  $events = $DB->get_records('event', array('repeatid' => $event->repeatid), '', '*');
     671                  foreach ($events as $calendarevent) {
     672                      $eventargs['objectid'] = $calendarevent->id;
     673                      $eventargs['other']['timestart'] = $calendarevent->timestart;
     674                      $event = \core\event\calendar_event_updated::create($eventargs);
     675                      $event->add_record_snapshot('event', $calendarevent);
     676                      $event->trigger();
     677                  }
     678              } else {
     679                  $DB->update_record('event', $this->properties);
     680                  $event = self::load($this->properties->id);
     681                  $this->properties = $event->properties();
     682  
     683                  // Trigger an update event.
     684                  $event = \core\event\calendar_event_updated::create($eventargs);
     685                  $event->add_record_snapshot('event', $this->properties);
     686                  $event->trigger();
     687              }
     688  
     689              return true;
     690          }
     691      }
     692  
     693      /**
     694       * Deletes an event and if selected an repeated events in the same series
     695       *
     696       * This function deletes an event, any associated events if $deleterepeated=true,
     697       * and cleans up any files associated with the events.
     698       *
     699       * @see self::delete()
     700       *
     701       * @param bool $deleterepeated  delete event repeatedly
     702       * @return bool succession of deleting event
     703       */
     704      public function delete($deleterepeated = false) {
     705          global $DB;
     706  
     707          // If $this->properties->id is not set then something is wrong.
     708          if (empty($this->properties->id)) {
     709              debugging('Attempting to delete an event before it has been loaded', DEBUG_DEVELOPER);
     710              return false;
     711          }
     712          $calevent = $DB->get_record('event',  array('id' => $this->properties->id), '*', MUST_EXIST);
     713          // Delete the event.
     714          $DB->delete_records('event', array('id' => $this->properties->id));
     715  
     716          // Trigger an event for the delete action.
     717          $eventargs = array(
     718              'context' => $this->get_context(),
     719              'objectid' => $this->properties->id,
     720              'other' => array(
     721                  'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
     722                  'timestart' => $this->properties->timestart,
     723                  'name' => $this->properties->name
     724              ));
     725          $event = \core\event\calendar_event_deleted::create($eventargs);
     726          $event->add_record_snapshot('event', $calevent);
     727          $event->trigger();
     728  
     729          // If we are deleting parent of a repeated event series, promote the next event in the series as parent.
     730          if (($this->properties->id == $this->properties->repeatid) && !$deleterepeated) {
     731              $newparent = $DB->get_field_sql("SELECT id from {event} where repeatid = ? order by id ASC",
     732                  array($this->properties->id), IGNORE_MULTIPLE);
     733              if (!empty($newparent)) {
     734                  $DB->execute("UPDATE {event} SET repeatid = ? WHERE repeatid = ?",
     735                      array($newparent, $this->properties->id));
     736                  // Get all records where the repeatid is the same as the event being removed.
     737                  $events = $DB->get_records('event', array('repeatid' => $newparent));
     738                  // For each of the returned events trigger an update event.
     739                  foreach ($events as $calendarevent) {
     740                      // Trigger an event for the update.
     741                      $eventargs['objectid'] = $calendarevent->id;
     742                      $eventargs['other']['timestart'] = $calendarevent->timestart;
     743                      $event = \core\event\calendar_event_updated::create($eventargs);
     744                      $event->add_record_snapshot('event', $calendarevent);
     745                      $event->trigger();
     746                  }
     747              }
     748          }
     749  
     750          // If the editor context hasn't already been set then set it now.
     751          if ($this->editorcontext === null) {
     752              $this->editorcontext = $this->get_context();
     753          }
     754  
     755          // If the context has been set delete all associated files.
     756          if ($this->editorcontext !== null) {
     757              $fs = get_file_storage();
     758              $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
     759              foreach ($files as $file) {
     760                  $file->delete();
     761              }
     762          }
     763  
     764          // If we need to delete repeated events then we will fetch them all and delete one by one.
     765          if ($deleterepeated && !empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
     766              // Get all records where the repeatid is the same as the event being removed.
     767              $events = $DB->get_records('event', array('repeatid' => $this->properties->repeatid));
     768              // For each of the returned events populate an event object and call delete.
     769              // make sure the arg passed is false as we are already deleting all repeats.
     770              foreach ($events as $event) {
     771                  $event = new calendar_event($event);
     772                  $event->delete(false);
     773              }
     774          }
     775  
     776          return true;
     777      }
     778  
     779      /**
     780       * Fetch all event properties.
     781       *
     782       * This function returns all of the events properties as an object and optionally
     783       * can prepare an editor for the description field at the same time. This is
     784       * designed to work when the properties are going to be used to set the default
     785       * values of a moodle forms form.
     786       *
     787       * @param bool $prepareeditor If set to true a editor is prepared for use with
     788       *              the mforms editor element. (for description)
     789       * @return \stdClass Object containing event properties
     790       */
     791      public function properties($prepareeditor = false) {
     792          global $DB;
     793  
     794          // First take a copy of the properties. We don't want to actually change the
     795          // properties or we'd forever be converting back and forwards between an
     796          // editor formatted description and not.
     797          $properties = clone($this->properties);
     798          // Clean the description here.
     799          $properties->description = clean_text($properties->description, $properties->format);
     800  
     801          // If set to true we need to prepare the properties for use with an editor
     802          // and prepare the file area.
     803          if ($prepareeditor) {
     804  
     805              // We may or may not have a property id. If we do then we need to work
     806              // out the context so we can copy the existing files to the draft area.
     807              if (!empty($properties->id)) {
     808  
     809                  if ($properties->eventtype === 'site') {
     810                      // Site context.
     811                      $this->editorcontext = $this->get_context();
     812                  } else if ($properties->eventtype === 'user') {
     813                      // User context.
     814                      $this->editorcontext = $this->get_context();
     815                  } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
     816                      // First check the course is valid.
     817                      $course = $DB->get_record('course', array('id' => $properties->courseid));
     818                      if (!$course) {
     819                          print_error('invalidcourse');
     820                      }
     821                      // Course context.
     822                      $this->editorcontext = $this->get_context();
     823                      // We have a course and are within the course context so we had
     824                      // better use the courses max bytes value.
     825                      $this->editoroptions['maxbytes'] = $course->maxbytes;
     826                  } else if ($properties->eventtype === 'category') {
     827                      // First check the course is valid.
     828                      \core_course_category::get($properties->categoryid, MUST_EXIST, true);
     829                      // Course context.
     830                      $this->editorcontext = $this->get_context();
     831                  } else {
     832                      // If we get here we have a custom event type as used by some
     833                      // modules. In this case the event will have been added by
     834                      // code and we won't need the editor.
     835                      $this->editoroptions['maxbytes'] = 0;
     836                      $this->editoroptions['maxfiles'] = 0;
     837                  }
     838  
     839                  if (empty($this->editorcontext) || empty($this->editorcontext->id)) {
     840                      $contextid = false;
     841                  } else {
     842                      // Get the context id that is what we really want.
     843                      $contextid = $this->editorcontext->id;
     844                  }
     845              } else {
     846  
     847                  // If we get here then this is a new event in which case we don't need a
     848                  // context as there is no existing files to copy to the draft area.
     849                  $contextid = null;
     850              }
     851  
     852              // If the contextid === false we don't support files so no preparing
     853              // a draft area.
     854              if ($contextid !== false) {
     855                  // Just encase it has already been submitted.
     856                  $draftiddescription = file_get_submitted_draft_itemid('description');
     857                  // Prepare the draft area, this copies existing files to the draft area as well.
     858                  $properties->description = file_prepare_draft_area($draftiddescription, $contextid, 'calendar',
     859                      'event_description', $properties->id, $this->editoroptions, $properties->description);
     860              } else {
     861                  $draftiddescription = 0;
     862              }
     863  
     864              // Structure the description field as the editor requires.
     865              $properties->description = array('text' => $properties->description, 'format' => $properties->format,
     866                  'itemid' => $draftiddescription);
     867          }
     868  
     869          // Finally return the properties.
     870          return $properties;
     871      }
     872  
     873      /**
     874       * Toggles the visibility of an event
     875       *
     876       * @param null|bool $force If it is left null the events visibility is flipped,
     877       *                   If it is false the event is made hidden, if it is true it
     878       *                   is made visible.
     879       * @return bool if event is successfully updated, toggle will be visible
     880       */
     881      public function toggle_visibility($force = null) {
     882          global $DB;
     883  
     884          // Set visible to the default if it is not already set.
     885          if (empty($this->properties->visible)) {
     886              $this->properties->visible = 1;
     887          }
     888  
     889          if ($force === true || ($force !== false && $this->properties->visible == 0)) {
     890              // Make this event visible.
     891              $this->properties->visible = 1;
     892          } else {
     893              // Make this event hidden.
     894              $this->properties->visible = 0;
     895          }
     896  
     897          // Update the database to reflect this change.
     898          $success = $DB->set_field('event', 'visible', $this->properties->visible, array('id' => $this->properties->id));
     899          $calendarevent = $DB->get_record('event',  array('id' => $this->properties->id), '*', MUST_EXIST);
     900  
     901          // Prepare event data.
     902          $eventargs = array(
     903              'context' => $this->get_context(),
     904              'objectid' => $this->properties->id,
     905              'other' => array(
     906                  'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
     907                  'timestart' => $this->properties->timestart,
     908                  'name' => $this->properties->name
     909              )
     910          );
     911          $event = \core\event\calendar_event_updated::create($eventargs);
     912          $event->add_record_snapshot('event', $calendarevent);
     913          $event->trigger();
     914  
     915          return $success;
     916      }
     917  
     918      /**
     919       * Returns an event object when provided with an event id.
     920       *
     921       * This function makes use of MUST_EXIST, if the event id passed in is invalid
     922       * it will result in an exception being thrown.
     923       *
     924       * @param int|object $param event object or event id
     925       * @return calendar_event
     926       */
     927      public static function load($param) {
     928          global $DB;
     929          if (is_object($param)) {
     930              $event = new calendar_event($param);
     931          } else {
     932              $event = $DB->get_record('event', array('id' => (int)$param), '*', MUST_EXIST);
     933              $event = new calendar_event($event);
     934          }
     935          return $event;
     936      }
     937  
     938      /**
     939       * Creates a new event and returns an event object.
     940       *
     941       * Capability checking should be performed if the user is directly creating the event
     942       * and no other capability has been tested. However if the event is not being created
     943       * directly by the user and another capability has been checked for them to do this then
     944       * capabilites should not be checked.
     945       *
     946       * For example if a user is creating an event in the calendar the check should be true,
     947       * but if you are creating an event in an activity when it is created then the calendar
     948       * capabilites should not be checked.
     949       *
     950       * @param \stdClass|array $properties An object containing event properties
     951       * @param bool $checkcapability If Moodle should check the user can manage the calendar events for this call or not.
     952       * @throws \coding_exception
     953       *
     954       * @return calendar_event|bool The event object or false if it failed
     955       */
     956      public static function create($properties, $checkcapability = true) {
     957          if (is_array($properties)) {
     958              $properties = (object)$properties;
     959          }
     960          if (!is_object($properties)) {
     961              throw new \coding_exception('When creating an event properties should be either an object or an assoc array');
     962          }
     963          $event = new calendar_event($properties);
     964          if ($event->update($properties, $checkcapability)) {
     965              return $event;
     966          } else {
     967              return false;
     968          }
     969      }
     970  
     971      /**
     972       * Format the event name using the external API.
     973       *
     974       * This function should we used when text formatting is required in external functions.
     975       *
     976       * @return string Formatted name.
     977       */
     978      public function format_external_name() {
     979          if ($this->editorcontext === null) {
     980              // Switch on the event type to decide upon the appropriate context to use for this event.
     981              $this->editorcontext = $this->get_context();
     982          }
     983  
     984          return external_format_string($this->properties->name, $this->editorcontext->id);
     985      }
     986  
     987      /**
     988       * Format the text using the external API.
     989       *
     990       * This function should we used when text formatting is required in external functions.
     991       *
     992       * @return array an array containing the text formatted and the text format
     993       */
     994      public function format_external_text() {
     995  
     996          if ($this->editorcontext === null) {
     997              // Switch on the event type to decide upon the appropriate context to use for this event.
     998              $this->editorcontext = $this->get_context();
     999  
    1000              if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
    1001                  // We don't have a context here, do a normal format_text.
    1002                  return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id);
    1003              }
    1004          }
    1005  
    1006          // Work out the item id for the editor, if this is a repeated event then the files will be associated with the original.
    1007          if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
    1008              $itemid = $this->properties->repeatid;
    1009          } else {
    1010              $itemid = $this->properties->id;
    1011          }
    1012  
    1013          return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id,
    1014              'calendar', 'event_description', $itemid);
    1015      }
    1016  }
    1017  
    1018  /**
    1019   * Calendar information class
    1020   *
    1021   * This class is used simply to organise the information pertaining to a calendar
    1022   * and is used primarily to make information easily available.
    1023   *
    1024   * @package core_calendar
    1025   * @category calendar
    1026   * @copyright 2010 Sam Hemelryk
    1027   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1028   */
    1029  class calendar_information {
    1030  
    1031      /**
    1032       * @var int The timestamp
    1033       *
    1034       * Rather than setting the day, month and year we will set a timestamp which will be able
    1035       * to be used by multiple calendars.
    1036       */
    1037      public $time;
    1038  
    1039      /** @var int A course id */
    1040      public $courseid = null;
    1041  
    1042      /** @var array An array of categories */
    1043      public $categories = array();
    1044  
    1045      /** @var int The current category */
    1046      public $categoryid = null;
    1047  
    1048      /** @var array An array of courses */
    1049      public $courses = array();
    1050  
    1051      /** @var array An array of groups */
    1052      public $groups = array();
    1053  
    1054      /** @var array An array of users */
    1055      public $users = array();
    1056  
    1057      /** @var context The anticipated context that the calendar is viewed in */
    1058      public $context = null;
    1059  
    1060      /**
    1061       * Creates a new instance
    1062       *
    1063       * @param int $day the number of the day
    1064       * @param int $month the number of the month
    1065       * @param int $year the number of the year
    1066       * @param int $time the unixtimestamp representing the date we want to view, this is used instead of $calmonth
    1067       *     and $calyear to support multiple calendars
    1068       */
    1069      public function __construct($day = 0, $month = 0, $year = 0, $time = 0) {
    1070          // If a day, month and year were passed then convert it to a timestamp. If these were passed
    1071          // then we can assume the day, month and year are passed as Gregorian, as no where in core
    1072          // should we be passing these values rather than the time. This is done for BC.
    1073          if (!empty($day) || !empty($month) || !empty($year)) {
    1074              $date = usergetdate(time());
    1075              if (empty($day)) {
    1076                  $day = $date['mday'];
    1077              }
    1078              if (empty($month)) {
    1079                  $month = $date['mon'];
    1080              }
    1081              if (empty($year)) {
    1082                  $year =  $date['year'];
    1083              }
    1084              if (checkdate($month, $day, $year)) {
    1085                  $time = make_timestamp($year, $month, $day);
    1086              } else {
    1087                  $time = time();
    1088              }
    1089          }
    1090  
    1091          $this->set_time($time);
    1092      }
    1093  
    1094      /**
    1095       * Creates and set up a instance.
    1096       *
    1097       * @param   int                     $time the unixtimestamp representing the date we want to view.
    1098       * @param   int                     $courseid The ID of the course the user wishes to view.
    1099       * @param   int                     $categoryid The ID of the category the user wishes to view
    1100       *                                  If a courseid is specified, this value is ignored.
    1101       * @return  calendar_information
    1102       */
    1103      public static function create($time, int $courseid, int $categoryid = null) : calendar_information {
    1104          $calendar = new static(0, 0, 0, $time);
    1105          if ($courseid != SITEID && !empty($courseid)) {
    1106              // Course ID must be valid and existing.
    1107              $course = get_course($courseid);
    1108              $calendar->context = context_course::instance($course->id);
    1109  
    1110              if (!$course->visible && !is_role_switched($course->id)) {
    1111                  require_capability('moodle/course:viewhiddencourses', $calendar->context);
    1112              }
    1113  
    1114              $courses = [$course->id => $course];
    1115              $category = (\core_course_category::get($course->category, MUST_EXIST, true))->get_db_record();
    1116          } else if (!empty($categoryid)) {
    1117              $course = get_site();
    1118              $courses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce');
    1119  
    1120              // Filter available courses to those within this category or it's children.
    1121              $ids = [$categoryid];
    1122              $category = \core_course_category::get($categoryid);
    1123              $ids = array_merge($ids, array_keys($category->get_children()));
    1124              $courses = array_filter($courses, function($course) use ($ids) {
    1125                  return array_search($course->category, $ids) !== false;
    1126              });
    1127              $category = $category->get_db_record();
    1128  
    1129              $calendar->context = context_coursecat::instance($categoryid);
    1130          } else {
    1131              $course = get_site();
    1132              $courses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce');
    1133              $category = null;
    1134  
    1135              $calendar->context = context_system::instance();
    1136          }
    1137  
    1138          $calendar->set_sources($course, $courses, $category);
    1139  
    1140          return $calendar;
    1141      }
    1142  
    1143      /**
    1144       * Set the time period of this instance.
    1145       *
    1146       * @param   int $time the unixtimestamp representing the date we want to view.
    1147       * @return  $this
    1148       */
    1149      public function set_time($time = null) {
    1150          if (empty($time)) {
    1151              $this->time = time();
    1152          } else {
    1153              $this->time = $time;
    1154          }
    1155  
    1156          return $this;
    1157      }
    1158  
    1159      /**
    1160       * Initialize calendar information
    1161       *
    1162       * @deprecated 3.4
    1163       * @param stdClass $course object
    1164       * @param array $coursestoload An array of courses [$course->id => $course]
    1165       * @param bool $ignorefilters options to use filter
    1166       */
    1167      public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) {
    1168          debugging('The prepare_for_view() function has been deprecated. Please update your code to use set_sources()',
    1169                  DEBUG_DEVELOPER);
    1170          $this->set_sources($course, $coursestoload);
    1171      }
    1172  
    1173      /**
    1174       * Set the sources for events within the calendar.
    1175       *
    1176       * If no category is provided, then the category path for the current
    1177       * course will be used.
    1178       *
    1179       * @param   stdClass    $course The current course being viewed.
    1180       * @param   stdClass[]  $courses The list of all courses currently accessible.
    1181       * @param   stdClass    $category The current category to show.
    1182       */
    1183      public function set_sources(stdClass $course, array $courses, stdClass $category = null) {
    1184          global $USER;
    1185  
    1186          // A cousre must always be specified.
    1187          $this->course = $course;
    1188          $this->courseid = $course->id;
    1189  
    1190          list($courseids, $group, $user) = calendar_set_filters($courses);
    1191          $this->courses = $courseids;
    1192          $this->groups = $group;
    1193          $this->users = $user;
    1194  
    1195          // Do not show category events by default.
    1196          $this->categoryid = null;
    1197          $this->categories = null;
    1198  
    1199          // Determine the correct category information to show.
    1200          // When called with a course, the category of that course is usually included too.
    1201          // When a category was specifically requested, it should be requested with the site id.
    1202          if (SITEID !== $this->courseid) {
    1203              // A specific course was requested.
    1204              // Fetch the category that this course is in, along with all parents.
    1205              // Do not include child categories of this category, as the user many not have enrolments in those siblings or children.
    1206              $category = \core_course_category::get($course->category, MUST_EXIST, true);
    1207              $this->categoryid = $category->id;
    1208  
    1209              $this->categories = $category->get_parents();
    1210              $this->categories[] = $category->id;
    1211          } else if (null !== $category && $category->id > 0) {
    1212              // A specific category was requested.
    1213              // Fetch all parents of this category, along with all children too.
    1214              $category = \core_course_category::get($category->id);
    1215              $this->categoryid = $category->id;
    1216  
    1217              // Build the category list.
    1218              // This includes the current category.
    1219              $this->categories = $category->get_parents();
    1220              $this->categories[] = $category->id;
    1221              $this->categories = array_merge($this->categories, $category->get_all_children_ids());
    1222          } else if (SITEID === $this->courseid) {
    1223              // The site was requested.
    1224              // Fetch all categories where this user has any enrolment, and all categories that this user can manage.
    1225  
    1226              // Grab the list of categories that this user has courses in.
    1227              $coursecategories = array_flip(array_map(function($course) {
    1228                  return $course->category;
    1229              }, $courses));
    1230  
    1231              $calcatcache = cache::make('core', 'calendar_categories');
    1232              $this->categories = $calcatcache->get('site');
    1233              if ($this->categories === false) {
    1234                  // Use the category id as the key in the following array. That way we do not have to remove duplicates.
    1235                  $categories = [];
    1236                  foreach (\core_course_category::get_all() as $category) {
    1237                      if (isset($coursecategories[$category->id]) ||
    1238                              has_capability('moodle/category:manage', $category->get_context(), $USER, false)) {
    1239                          // If the user has access to a course in this category or can manage the category,
    1240                          // then they can see all parent categories too.
    1241                          $categories[$category->id] = true;
    1242                          foreach ($category->get_parents() as $catid) {
    1243                              $categories[$catid] = true;
    1244                          }
    1245                      }
    1246                  }
    1247                  $this->categories = array_keys($categories);
    1248                  $calcatcache->set('site', $this->categories);
    1249              }
    1250          }
    1251      }
    1252  
    1253      /**
    1254       * Ensures the date for the calendar is correct and either sets it to now
    1255       * or throws a moodle_exception if not
    1256       *
    1257       * @param bool $defaultonow use current time
    1258       * @throws moodle_exception
    1259       * @return bool validation of checkdate
    1260       */
    1261      public function checkdate($defaultonow = true) {
    1262          if (!checkdate($this->month, $this->day, $this->year)) {
    1263              if ($defaultonow) {
    1264                  $now = usergetdate(time());
    1265                  $this->day = intval($now['mday']);
    1266                  $this->month = intval($now['mon']);
    1267                  $this->year = intval($now['year']);
    1268                  return true;
    1269              } else {
    1270                  throw new moodle_exception('invaliddate');
    1271              }
    1272          }
    1273          return true;
    1274      }
    1275  
    1276      /**
    1277       * Gets todays timestamp for the calendar
    1278       *
    1279       * @return int today timestamp
    1280       */
    1281      public function timestamp_today() {
    1282          return $this->time;
    1283      }
    1284      /**
    1285       * Gets tomorrows timestamp for the calendar
    1286       *
    1287       * @return int tomorrow timestamp
    1288       */
    1289      public function timestamp_tomorrow() {
    1290          return strtotime('+1 day', $this->time);
    1291      }
    1292      /**
    1293       * Adds the pretend blocks for the calendar
    1294       *
    1295       * @param core_calendar_renderer $renderer
    1296       * @param bool $showfilters display filters, false is set as default
    1297       * @param string|null $view preference view options (eg: day, month, upcoming)
    1298       */
    1299      public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
    1300          global $PAGE;
    1301  
    1302          if (!has_capability('moodle/block:view', $PAGE->context) ) {
    1303              return;
    1304          }
    1305  
    1306          if ($showfilters) {
    1307              $filters = new block_contents();
    1308              $filters->content = $renderer->event_filter();
    1309              $filters->footer = '';
    1310              $filters->title = get_string('eventskey', 'calendar');
    1311              $renderer->add_pretend_calendar_block($filters, BLOCK_POS_RIGHT);
    1312          }
    1313          $block = new block_contents;
    1314          $block->content = $renderer->fake_block_threemonths($this);
    1315          $block->footer = '';
    1316          $block->title = get_string('monthlyview', 'calendar');
    1317          $renderer->add_pretend_calendar_block($block, BLOCK_POS_RIGHT);
    1318      }
    1319  }
    1320  
    1321  /**
    1322   * Get calendar events.
    1323   *
    1324   * @param int $tstart Start time of time range for events
    1325   * @param int $tend End time of time range for events
    1326   * @param array|int|boolean $users array of users, user id or boolean for all/no user events
    1327   * @param array|int|boolean $groups array of groups, group id or boolean for all/no group events
    1328   * @param array|int|boolean $courses array of courses, course id or boolean for all/no course events
    1329   * @param boolean $withduration whether only events starting within time range selected
    1330   *                              or events in progress/already started selected as well
    1331   * @param boolean $ignorehidden whether to select only visible events or all events
    1332   * @param array|int|boolean $categories array of categories, category id or boolean for all/no course events
    1333   * @return array $events of selected events or an empty array if there aren't any (or there was an error)
    1334   */
    1335  function calendar_get_events($tstart, $tend, $users, $groups, $courses,
    1336          $withduration = true, $ignorehidden = true, $categories = []) {
    1337      global $DB;
    1338  
    1339      $whereclause = '';
    1340      $params = array();
    1341      // Quick test.
    1342      if (empty($users) && empty($groups) && empty($courses) && empty($categories)) {
    1343          return array();
    1344      }
    1345  
    1346      if ((is_array($users) && !empty($users)) or is_numeric($users)) {
    1347          // Events from a number of users
    1348          if(!empty($whereclause)) $whereclause .= ' OR';
    1349          list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
    1350          $whereclause .= " (e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
    1351          $params = array_merge($params, $inparamsusers);
    1352      } else if($users === true) {
    1353          // Events from ALL users
    1354          if(!empty($whereclause)) $whereclause .= ' OR';
    1355          $whereclause .= ' (e.userid != 0 AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)';
    1356      } else if($users === false) {
    1357          // No user at all, do nothing
    1358      }
    1359  
    1360      if ((is_array($groups) && !empty($groups)) or is_numeric($groups)) {
    1361          // Events from a number of groups
    1362          if(!empty($whereclause)) $whereclause .= ' OR';
    1363          list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED);
    1364          $whereclause .= " e.groupid $insqlgroups ";
    1365          $params = array_merge($params, $inparamsgroups);
    1366      } else if($groups === true) {
    1367          // Events from ALL groups
    1368          if(!empty($whereclause)) $whereclause .= ' OR ';
    1369          $whereclause .= ' e.groupid != 0';
    1370      }
    1371      // boolean false (no groups at all): we don't need to do anything
    1372  
    1373      if ((is_array($courses) && !empty($courses)) or is_numeric($courses)) {
    1374          if(!empty($whereclause)) $whereclause .= ' OR';
    1375          list($insqlcourses, $inparamscourses) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
    1376          $whereclause .= " (e.groupid = 0 AND e.courseid $insqlcourses)";
    1377          $params = array_merge($params, $inparamscourses);
    1378      } else if ($courses === true) {
    1379          // Events from ALL courses
    1380          if(!empty($whereclause)) $whereclause .= ' OR';
    1381          $whereclause .= ' (e.groupid = 0 AND e.courseid != 0)';
    1382      }
    1383  
    1384      if ((is_array($categories) && !empty($categories)) || is_numeric($categories)) {
    1385          if (!empty($whereclause)) {
    1386              $whereclause .= ' OR';
    1387          }
    1388          list($insqlcategories, $inparamscategories) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
    1389          $whereclause .= " (e.groupid = 0 AND e.courseid = 0 AND e.categoryid $insqlcategories)";
    1390          $params = array_merge($params, $inparamscategories);
    1391      } else if ($categories === true) {
    1392          // Events from ALL categories.
    1393          if (!empty($whereclause)) {
    1394              $whereclause .= ' OR';
    1395          }
    1396          $whereclause .= ' (e.groupid = 0 AND e.courseid = 0 AND e.categoryid != 0)';
    1397      }
    1398  
    1399      // Security check: if, by now, we have NOTHING in $whereclause, then it means
    1400      // that NO event-selecting clauses were defined. Thus, we won't be returning ANY
    1401      // events no matter what. Allowing the code to proceed might return a completely
    1402      // valid query with only time constraints, thus selecting ALL events in that time frame!
    1403      if(empty($whereclause)) {
    1404          return array();
    1405      }
    1406  
    1407      if($withduration) {
    1408          $timeclause = '(e.timestart >= '.$tstart.' OR e.timestart + e.timeduration > '.$tstart.') AND e.timestart <= '.$tend;
    1409      }
    1410      else {
    1411          $timeclause = 'e.timestart >= '.$tstart.' AND e.timestart <= '.$tend;
    1412      }
    1413      if(!empty($whereclause)) {
    1414          // We have additional constraints
    1415          $whereclause = $timeclause.' AND ('.$whereclause.')';
    1416      }
    1417      else {
    1418          // Just basic time filtering
    1419          $whereclause = $timeclause;
    1420      }
    1421  
    1422      if ($ignorehidden) {
    1423          $whereclause .= ' AND e.visible = 1';
    1424      }
    1425  
    1426      $sql = "SELECT e.*
    1427                FROM {event} e
    1428           LEFT JOIN {modules} m ON e.modulename = m.name
    1429                  -- Non visible modules will have a value of 0.
    1430               WHERE (m.visible = 1 OR m.visible IS NULL) AND $whereclause
    1431            ORDER BY e.timestart";
    1432      $events = $DB->get_records_sql($sql, $params);
    1433  
    1434      if ($events === false) {
    1435          $events = array();
    1436      }
    1437      return $events;
    1438  }
    1439  
    1440  /**
    1441   * Return the days of the week.
    1442   *
    1443   * @return array array of days
    1444   */
    1445  function calendar_get_days() {
    1446      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    1447      return $calendartype->get_weekdays();
    1448  }
    1449  
    1450  /**
    1451   * Get the subscription from a given id.
    1452   *
    1453   * @since Moodle 2.5
    1454   * @param int $id id of the subscription
    1455   * @return stdClass Subscription record from DB
    1456   * @throws moodle_exception for an invalid id
    1457   */
    1458  function calendar_get_subscription($id) {
    1459      global $DB;
    1460  
    1461      $cache = \cache::make('core', 'calendar_subscriptions');
    1462      $subscription = $cache->get($id);
    1463      if (empty($subscription)) {
    1464          $subscription = $DB->get_record('event_subscriptions', array('id' => $id), '*', MUST_EXIST);
    1465          $cache->set($id, $subscription);
    1466      }
    1467  
    1468      return $subscription;
    1469  }
    1470  
    1471  /**
    1472   * Gets the first day of the week.
    1473   *
    1474   * Used to be define('CALENDAR_STARTING_WEEKDAY', blah);
    1475   *
    1476   * @return int
    1477   */
    1478  function calendar_get_starting_weekday() {
    1479      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    1480      return $calendartype->get_starting_weekday();
    1481  }
    1482  
    1483  /**
    1484   * Get a HTML link to a course.
    1485   *
    1486   * @param int|stdClass $course the course id or course object
    1487   * @return string a link to the course (as HTML); empty if the course id is invalid
    1488   */
    1489  function calendar_get_courselink($course) {
    1490      if (!$course) {
    1491          return '';
    1492      }
    1493  
    1494      if (!is_object($course)) {
    1495          $course = calendar_get_course_cached($coursecache, $course);
    1496      }
    1497      $context = \context_course::instance($course->id);
    1498      $fullname = format_string($course->fullname, true, array('context' => $context));
    1499      $url = new \moodle_url('/course/view.php', array('id' => $course->id));
    1500      $link = \html_writer::link($url, $fullname);
    1501  
    1502      return $link;
    1503  }
    1504  
    1505  /**
    1506   * Get current module cache.
    1507   *
    1508   * Only use this method if you do not know courseid. Otherwise use:
    1509   * get_fast_modinfo($courseid)->instances[$modulename][$instance]
    1510   *
    1511   * @param array $modulecache in memory module cache
    1512   * @param string $modulename name of the module
    1513   * @param int $instance module instance number
    1514   * @return stdClass|bool $module information
    1515   */
    1516  function calendar_get_module_cached(&$modulecache, $modulename, $instance) {
    1517      if (!isset($modulecache[$modulename . '_' . $instance])) {
    1518          $modulecache[$modulename . '_' . $instance] = get_coursemodule_from_instance($modulename, $instance);
    1519      }
    1520  
    1521      return $modulecache[$modulename . '_' . $instance];
    1522  }
    1523  
    1524  /**
    1525   * Get current course cache.
    1526   *
    1527   * @param array $coursecache list of course cache
    1528   * @param int $courseid id of the course
    1529   * @return stdClass $coursecache[$courseid] return the specific course cache
    1530   */
    1531  function calendar_get_course_cached(&$coursecache, $courseid) {
    1532      if (!isset($coursecache[$courseid])) {
    1533          $coursecache[$courseid] = get_course($courseid);
    1534      }
    1535      return $coursecache[$courseid];
    1536  }
    1537  
    1538  /**
    1539   * Get group from groupid for calendar display
    1540   *
    1541   * @param int $groupid
    1542   * @return stdClass group object with fields 'id', 'name' and 'courseid'
    1543   */
    1544  function calendar_get_group_cached($groupid) {
    1545      static $groupscache = array();
    1546      if (!isset($groupscache[$groupid])) {
    1547          $groupscache[$groupid] = groups_get_group($groupid, 'id,name,courseid');
    1548      }
    1549      return $groupscache[$groupid];
    1550  }
    1551  
    1552  /**
    1553   * Add calendar event metadata
    1554   *
    1555   * @deprecated since 3.9
    1556   *
    1557   * @param stdClass $event event info
    1558   * @return stdClass $event metadata
    1559   */
    1560  function calendar_add_event_metadata($event) {
    1561      debugging('This function is no longer used', DEBUG_DEVELOPER);
    1562      global $CFG, $OUTPUT;
    1563  
    1564      // Support multilang in event->name.
    1565      $event->name = format_string($event->name, true);
    1566  
    1567      if (!empty($event->modulename)) { // Activity event.
    1568          // The module name is set. I will assume that it has to be displayed, and
    1569          // also that it is an automatically-generated event. And of course that the
    1570          // instace id and modulename are set correctly.
    1571          $instances = get_fast_modinfo($event->courseid)->get_instances_of($event->modulename);
    1572          if (!array_key_exists($event->instance, $instances)) {
    1573              return;
    1574          }
    1575          $module = $instances[$event->instance];
    1576  
    1577          $modulename = $module->get_module_type_name(false);
    1578          if (get_string_manager()->string_exists($event->eventtype, $event->modulename)) {
    1579              // Will be used as alt text if the event icon.
    1580              $eventtype = get_string($event->eventtype, $event->modulename);
    1581          } else {
    1582              $eventtype = '';
    1583          }
    1584  
    1585          $event->icon = '<img src="' . s($module->get_icon_url()) . '" alt="' . s($eventtype) .
    1586              '" title="' . s($modulename) . '" class="icon" />';
    1587          $event->referer = html_writer::link($module->url, $event->name);
    1588          $event->courselink = calendar_get_courselink($module->get_course());
    1589          $event->cmid = $module->id;
    1590      } else if ($event->courseid == SITEID) { // Site event.
    1591          $event->icon = '<img src="' . $OUTPUT->image_url('i/siteevent') . '" alt="' .
    1592              get_string('siteevent', 'calendar') . '" class="icon" />';
    1593          $event->cssclass = 'calendar_event_site';
    1594      } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event.
    1595          $event->icon = '<img src="' . $OUTPUT->image_url('i/courseevent') . '" alt="' .
    1596              get_string('courseevent', 'calendar') . '" class="icon" />';
    1597          $event->courselink = calendar_get_courselink($event->courseid);
    1598          $event->cssclass = 'calendar_event_course';
    1599      } else if ($event->groupid) { // Group event.
    1600          if ($group = calendar_get_group_cached($event->groupid)) {
    1601              $groupname = format_string($group->name, true, \context_course::instance($group->courseid));
    1602          } else {
    1603              $groupname = '';
    1604          }
    1605          $event->icon = \html_writer::empty_tag('image', array('src' => $OUTPUT->image_url('i/groupevent'),
    1606              'alt' => get_string('groupevent', 'calendar'), 'title' => $groupname, 'class' => 'icon'));
    1607          $event->courselink = calendar_get_courselink($event->courseid) . ', ' . $groupname;
    1608          $event->cssclass = 'calendar_event_group';
    1609      } else if ($event->userid) { // User event.
    1610          $event->icon = '<img src="' . $OUTPUT->image_url('i/userevent') . '" alt="' .
    1611              get_string('userevent', 'calendar') . '" class="icon" />';
    1612          $event->cssclass = 'calendar_event_user';
    1613      }
    1614  
    1615      return $event;
    1616  }
    1617  
    1618  /**
    1619   * Get calendar events by id.
    1620   *
    1621   * @since Moodle 2.5
    1622   * @param array $eventids list of event ids
    1623   * @return array Array of event entries, empty array if nothing found
    1624   */
    1625  function calendar_get_events_by_id($eventids) {
    1626      global $DB;
    1627  
    1628      if (!is_array($eventids) || empty($eventids)) {
    1629          return array();
    1630      }
    1631  
    1632      list($wheresql, $params) = $DB->get_in_or_equal($eventids);
    1633      $wheresql = "id $wheresql";
    1634  
    1635      return $DB->get_records_select('event', $wheresql, $params);
    1636  }
    1637  
    1638  /**
    1639   * Get control options for calendar.
    1640   *
    1641   * @param string $type of calendar
    1642   * @param array $data calendar information
    1643   * @return string $content return available control for the calender in html
    1644   */
    1645  function calendar_top_controls($type, $data) {
    1646      global $PAGE, $OUTPUT;
    1647  
    1648      // Get the calendar type we are using.
    1649      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    1650  
    1651      $content = '';
    1652  
    1653      // Ensure course id passed if relevant.
    1654      $courseid = '';
    1655      if (!empty($data['id'])) {
    1656          $courseid = '&amp;course=' . $data['id'];
    1657      }
    1658  
    1659      // If we are passing a month and year then we need to convert this to a timestamp to
    1660      // support multiple calendars. No where in core should these be passed, this logic
    1661      // here is for third party plugins that may use this function.
    1662      if (!empty($data['m']) && !empty($date['y'])) {
    1663          if (!isset($data['d'])) {
    1664              $data['d'] = 1;
    1665          }
    1666          if (!checkdate($data['m'], $data['d'], $data['y'])) {
    1667              $time = time();
    1668          } else {
    1669              $time = make_timestamp($data['y'], $data['m'], $data['d']);
    1670          }
    1671      } else if (!empty($data['time'])) {
    1672          $time = $data['time'];
    1673      } else {
    1674          $time = time();
    1675      }
    1676  
    1677      // Get the date for the calendar type.
    1678      $date = $calendartype->timestamp_to_date_array($time);
    1679  
    1680      $urlbase = $PAGE->url;
    1681  
    1682      // We need to get the previous and next months in certain cases.
    1683      if ($type == 'frontpage' || $type == 'course' || $type == 'month') {
    1684          $prevmonth = calendar_sub_month($date['mon'], $date['year']);
    1685          $prevmonthtime = $calendartype->convert_to_gregorian($prevmonth[1], $prevmonth[0], 1);
    1686          $prevmonthtime = make_timestamp($prevmonthtime['year'], $prevmonthtime['month'], $prevmonthtime['day'],
    1687              $prevmonthtime['hour'], $prevmonthtime['minute']);
    1688  
    1689          $nextmonth = calendar_add_month($date['mon'], $date['year']);
    1690          $nextmonthtime = $calendartype->convert_to_gregorian($nextmonth[1], $nextmonth[0], 1);
    1691          $nextmonthtime = make_timestamp($nextmonthtime['year'], $nextmonthtime['month'], $nextmonthtime['day'],
    1692              $nextmonthtime['hour'], $nextmonthtime['minute']);
    1693      }
    1694  
    1695      switch ($type) {
    1696          case 'frontpage':
    1697              $prevlink = calendar_get_link_previous(get_string('monthprev', 'calendar'), $urlbase, false, false, false,
    1698                  true, $prevmonthtime);
    1699              $nextlink = calendar_get_link_next(get_string('monthnext', 'calendar'), $urlbase, false, false, false, true,
    1700                  $nextmonthtime);
    1701              $calendarlink = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', array('view' => 'month')),
    1702                  false, false, false, $time);
    1703  
    1704              if (!empty($data['id'])) {
    1705                  $calendarlink->param('course', $data['id']);
    1706              }
    1707  
    1708              $right = $nextlink;
    1709  
    1710              $content .= \html_writer::start_tag('div', array('class' => 'calendar-controls'));
    1711              $content .= $prevlink . '<span class="hide"> | </span>';
    1712              $content .= \html_writer::tag('span', \html_writer::link($calendarlink,
    1713                  userdate($time, get_string('strftimemonthyear')), array('title' => get_string('monththis', 'calendar'))
    1714              ), array('class' => 'current'));
    1715              $content .= '<span class="hide"> | </span>' . $right;
    1716              $content .= "<span class=\"clearer\"><!-- --></span>\n";
    1717              $content .= \html_writer::end_tag('div');
    1718  
    1719              break;
    1720          case 'course':
    1721              $prevlink = calendar_get_link_previous(get_string('monthprev', 'calendar'), $urlbase, false, false, false,
    1722                  true, $prevmonthtime);
    1723              $nextlink = calendar_get_link_next(get_string('monthnext', 'calendar'), $urlbase, false, false, false,
    1724                  true, $nextmonthtime);
    1725              $calendarlink = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', array('view' => 'month')),
    1726                  false, false, false, $time);
    1727  
    1728              if (!empty($data['id'])) {
    1729                  $calendarlink->param('course', $data['id']);
    1730              }
    1731  
    1732              $content .= \html_writer::start_tag('div', array('class' => 'calendar-controls'));
    1733              $content .= $prevlink . '<span class="hide"> | </span>';
    1734              $content .= \html_writer::tag('span', \html_writer::link($calendarlink,
    1735                  userdate($time, get_string('strftimemonthyear')), array('title' => get_string('monththis', 'calendar'))
    1736              ), array('class' => 'current'));
    1737              $content .= '<span class="hide"> | </span>' . $nextlink;
    1738              $content .= "<span class=\"clearer\"><!-- --></span>";
    1739              $content .= \html_writer::end_tag('div');
    1740              break;
    1741          case 'upcoming':
    1742              $calendarlink = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', array('view' => 'upcoming')),
    1743                  false, false, false, $time);
    1744              if (!empty($data['id'])) {
    1745                  $calendarlink->param('course', $data['id']);
    1746              }
    1747              $calendarlink = \html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')));
    1748              $content .= \html_writer::tag('div', $calendarlink, array('class' => 'centered'));
    1749              break;
    1750          case 'display':
    1751              $calendarlink = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', array('view' => 'month')),
    1752                  false, false, false, $time);
    1753              if (!empty($data['id'])) {
    1754                  $calendarlink->param('course', $data['id']);
    1755              }
    1756              $calendarlink = \html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')));
    1757              $content .= \html_writer::tag('h3', $calendarlink);
    1758              break;
    1759          case 'month':
    1760              $prevlink = calendar_get_link_previous(userdate($prevmonthtime, get_string('strftimemonthyear')),
    1761                  'view.php?view=month' . $courseid . '&amp;', false, false, false, false, $prevmonthtime);
    1762              $nextlink = calendar_get_link_next(userdate($nextmonthtime, get_string('strftimemonthyear')),
    1763                  'view.php?view=month' . $courseid . '&amp;', false, false, false, false, $nextmonthtime);
    1764  
    1765              $content .= \html_writer::start_tag('div', array('class' => 'calendar-controls'));
    1766              $content .= $prevlink . '<span class="hide"> | </span>';
    1767              $content .= $OUTPUT->heading(userdate($time, get_string('strftimemonthyear')), 2, 'current');
    1768              $content .= '<span class="hide"> | </span>' . $nextlink;
    1769              $content .= '<span class="clearer"><!-- --></span>';
    1770              $content .= \html_writer::end_tag('div')."\n";
    1771              break;
    1772          case 'day':
    1773              $days = calendar_get_days();
    1774  
    1775              $prevtimestamp = strtotime('-1 day', $time);
    1776              $nexttimestamp = strtotime('+1 day', $time);
    1777  
    1778              $prevdate = $calendartype->timestamp_to_date_array($prevtimestamp);
    1779              $nextdate = $calendartype->timestamp_to_date_array($nexttimestamp);
    1780  
    1781              $prevname = $days[$prevdate['wday']]['fullname'];
    1782              $nextname = $days[$nextdate['wday']]['fullname'];
    1783              $prevlink = calendar_get_link_previous($prevname, 'view.php?view=day' . $courseid . '&amp;', false, false,
    1784                  false, false, $prevtimestamp);
    1785              $nextlink = calendar_get_link_next($nextname, 'view.php?view=day' . $courseid . '&amp;', false, false, false,
    1786                  false, $nexttimestamp);
    1787  
    1788              $content .= \html_writer::start_tag('div', array('class' => 'calendar-controls'));
    1789              $content .= $prevlink;
    1790              $content .= '<span class="hide"> | </span><span class="current">' .userdate($time,
    1791                      get_string('strftimedaydate')) . '</span>';
    1792              $content .= '<span class="hide"> | </span>' . $nextlink;
    1793              $content .= "<span class=\"clearer\"><!-- --></span>";
    1794              $content .= \html_writer::end_tag('div') . "\n";
    1795  
    1796              break;
    1797      }
    1798  
    1799      return $content;
    1800  }
    1801  
    1802  /**
    1803   * Return the representation day.
    1804   *
    1805   * @param int $tstamp Timestamp in GMT
    1806   * @param int|bool $now current Unix timestamp
    1807   * @param bool $usecommonwords
    1808   * @return string the formatted date/time
    1809   */
    1810  function calendar_day_representation($tstamp, $now = false, $usecommonwords = true) {
    1811      static $shortformat;
    1812  
    1813      if (empty($shortformat)) {
    1814          $shortformat = get_string('strftimedayshort');
    1815      }
    1816  
    1817      if ($now === false) {
    1818          $now = time();
    1819      }
    1820  
    1821      // To have it in one place, if a change is needed.
    1822      $formal = userdate($tstamp, $shortformat);
    1823  
    1824      $datestamp = usergetdate($tstamp);
    1825      $datenow = usergetdate($now);
    1826  
    1827      if ($usecommonwords == false) {
    1828          // We don't want words, just a date.
    1829          return $formal;
    1830      } else if ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday']) {
    1831          return get_string('today', 'calendar');
    1832      } else if (($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] - 1 ) ||
    1833          ($datestamp['year'] == $datenow['year'] - 1 && $datestamp['mday'] == 31 && $datestamp['mon'] == 12
    1834              && $datenow['yday'] == 1)) {
    1835          return get_string('yesterday', 'calendar');
    1836      } else if (($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] + 1 ) ||
    1837          ($datestamp['year'] == $datenow['year'] + 1 && $datenow['mday'] == 31 && $datenow['mon'] == 12
    1838              && $datestamp['yday'] == 1)) {
    1839          return get_string('tomorrow', 'calendar');
    1840      } else {
    1841          return $formal;
    1842      }
    1843  }
    1844  
    1845  /**
    1846   * return the formatted representation time.
    1847   *
    1848  
    1849   * @param int $time the timestamp in UTC, as obtained from the database
    1850   * @return string the formatted date/time
    1851   */
    1852  function calendar_time_representation($time) {
    1853      static $langtimeformat = null;
    1854  
    1855      if ($langtimeformat === null) {
    1856          $langtimeformat = get_string('strftimetime');
    1857      }
    1858  
    1859      $timeformat = get_user_preferences('calendar_timeformat');
    1860      if (empty($timeformat)) {
    1861          $timeformat = get_config(null, 'calendar_site_timeformat');
    1862      }
    1863  
    1864      // Allow language customization of selected time format.
    1865      if ($timeformat === CALENDAR_TF_12) {
    1866          $timeformat = get_string('strftimetime12', 'langconfig');
    1867      } else if ($timeformat === CALENDAR_TF_24) {
    1868          $timeformat = get_string('strftimetime24', 'langconfig');
    1869      }
    1870  
    1871      return userdate($time, empty($timeformat) ? $langtimeformat : $timeformat);
    1872  }
    1873  
    1874  /**
    1875   * Adds day, month, year arguments to a URL and returns a moodle_url object.
    1876   *
    1877   * @param string|moodle_url $linkbase
    1878   * @param int $d The number of the day.
    1879   * @param int $m The number of the month.
    1880   * @param int $y The number of the year.
    1881   * @param int $time the unixtime, used for multiple calendar support. The values $d,
    1882   *     $m and $y are kept for backwards compatibility.
    1883   * @return moodle_url|null $linkbase
    1884   */
    1885  function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) {
    1886      if (empty($linkbase)) {
    1887          return null;
    1888      }
    1889  
    1890      if (!($linkbase instanceof \moodle_url)) {
    1891          $linkbase = new \moodle_url($linkbase);
    1892      }
    1893  
    1894      $linkbase->param('time', calendar_get_timestamp($d, $m, $y, $time));
    1895  
    1896      return $linkbase;
    1897  }
    1898  
    1899  /**
    1900   * Build and return a previous month HTML link, with an arrow.
    1901   *
    1902   * @param string $text The text label.
    1903   * @param string|moodle_url $linkbase The URL stub.
    1904   * @param int $d The number of the date.
    1905   * @param int $m The number of the month.
    1906   * @param int $y year The number of the year.
    1907   * @param bool $accesshide Default visible, or hide from all except screenreaders.
    1908   * @param int $time the unixtime, used for multiple calendar support. The values $d,
    1909   *     $m and $y are kept for backwards compatibility.
    1910   * @return string HTML string.
    1911   */
    1912  function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) {
    1913      $href = calendar_get_link_href(new \moodle_url($linkbase), $d, $m, $y, $time);
    1914  
    1915      if (empty($href)) {
    1916          return $text;
    1917      }
    1918  
    1919      $attrs = [
    1920          'data-time' => calendar_get_timestamp($d, $m, $y, $time),
    1921          'data-drop-zone' => 'nav-link',
    1922      ];
    1923  
    1924      return link_arrow_left($text, $href->out(false), $accesshide, 'previous', $attrs);
    1925  }
    1926  
    1927  /**
    1928   * Build and return a next month HTML link, with an arrow.
    1929   *
    1930   * @param string $text The text label.
    1931   * @param string|moodle_url $linkbase The URL stub.
    1932   * @param int $d the number of the Day
    1933   * @param int $m The number of the month.
    1934   * @param int $y The number of the year.
    1935   * @param bool $accesshide Default visible, or hide from all except screenreaders.
    1936   * @param int $time the unixtime, used for multiple calendar support. The values $d,
    1937   *     $m and $y are kept for backwards compatibility.
    1938   * @return string HTML string.
    1939   */
    1940  function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) {
    1941      $href = calendar_get_link_href(new \moodle_url($linkbase), $d, $m, $y, $time);
    1942  
    1943      if (empty($href)) {
    1944          return $text;
    1945      }
    1946  
    1947      $attrs = [
    1948          'data-time' => calendar_get_timestamp($d, $m, $y, $time),
    1949          'data-drop-zone' => 'nav-link',
    1950      ];
    1951  
    1952      return link_arrow_right($text, $href->out(false), $accesshide, 'next', $attrs);
    1953  }
    1954  
    1955  /**
    1956   * Return the number of days in month.
    1957   *
    1958   * @param int $month the number of the month.
    1959   * @param int $year the number of the year
    1960   * @return int
    1961   */
    1962  function calendar_days_in_month($month, $year) {
    1963      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    1964      return $calendartype->get_num_days_in_month($year, $month);
    1965  }
    1966  
    1967  /**
    1968   * Get the next following month.
    1969   *
    1970   * @param int $month the number of the month.
    1971   * @param int $year the number of the year.
    1972   * @return array the following month
    1973   */
    1974  function calendar_add_month($month, $year) {
    1975      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    1976      return $calendartype->get_next_month($year, $month);
    1977  }
    1978  
    1979  /**
    1980   * Get the previous month.
    1981   *
    1982   * @param int $month the number of the month.
    1983   * @param int $year the number of the year.
    1984   * @return array previous month
    1985   */
    1986  function calendar_sub_month($month, $year) {
    1987      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    1988      return $calendartype->get_prev_month($year, $month);
    1989  }
    1990  
    1991  /**
    1992   * Get per-day basis events
    1993   *
    1994   * @param array $events list of events
    1995   * @param int $month the number of the month
    1996   * @param int $year the number of the year
    1997   * @param array $eventsbyday event on specific day
    1998   * @param array $durationbyday duration of the event in days
    1999   * @param array $typesbyday event type (eg: site, course, user, or group)
    2000   * @param array $courses list of courses
    2001   * @return void
    2002   */
    2003  function calendar_events_by_day($events, $month, $year, &$eventsbyday, &$durationbyday, &$typesbyday, &$courses) {
    2004      $calendartype = \core_calendar\type_factory::get_calendar_instance();
    2005  
    2006      $eventsbyday = array();
    2007      $typesbyday = array();
    2008      $durationbyday = array();
    2009  
    2010      if ($events === false) {
    2011          return;
    2012      }
    2013  
    2014      foreach ($events as $event) {
    2015          $startdate = $calendartype->timestamp_to_date_array($event->timestart);
    2016          if ($event->timeduration) {
    2017              $enddate = $calendartype->timestamp_to_date_array($event->timestart + $event->timeduration - 1);
    2018          } else {
    2019              $enddate = $startdate;
    2020          }
    2021  
    2022          // Simple arithmetic: $year * 13 + $month is a distinct integer for each distinct ($year, $month) pair.
    2023          if (!($startdate['year'] * 13 + $startdate['mon'] <= $year * 13 + $month) &&
    2024              ($enddate['year'] * 13 + $enddate['mon'] >= $year * 13 + $month)) {
    2025              continue;
    2026          }
    2027  
    2028          $eventdaystart = intval($startdate['mday']);
    2029  
    2030          if ($startdate['mon'] == $month && $startdate['year'] == $year) {
    2031              // Give the event to its day.
    2032              $eventsbyday[$eventdaystart][] = $event->id;
    2033  
    2034              // Mark the day as having such an event.
    2035              if ($event->courseid == SITEID && $event->groupid == 0) {
    2036                  $typesbyday[$eventdaystart]['startsite'] = true;
    2037                  // Set event class for site event.
    2038                  $events[$event->id]->class = 'calendar_event_site';
    2039              } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {
    2040                  $typesbyday[$eventdaystart]['startcourse'] = true;
    2041                  // Set event class for course event.
    2042                  $events[$event->id]->class = 'calendar_event_course';
    2043              } else if ($event->groupid) {
    2044                  $typesbyday[$eventdaystart]['startgroup'] = true;
    2045                  // Set event class for group event.
    2046                  $events[$event->id]->class = 'calendar_event_group';
    2047              } else if ($event->userid) {
    2048                  $typesbyday[$eventdaystart]['startuser'] = true;
    2049                  // Set event class for user event.
    2050                  $events[$event->id]->class = 'calendar_event_user';
    2051              }
    2052          }
    2053  
    2054          if ($event->timeduration == 0) {
    2055              // Proceed with the next.
    2056              continue;
    2057          }
    2058  
    2059          // The event starts on $month $year or before.
    2060          if ($startdate['mon'] == $month && $startdate['year'] == $year) {
    2061              $lowerbound = intval($startdate['mday']);
    2062          } else {
    2063              $lowerbound = 0;
    2064          }
    2065  
    2066          // Also, it ends on $month $year or later.
    2067          if ($enddate['mon'] == $month && $enddate['year'] == $year) {
    2068              $upperbound = intval($enddate['mday']);
    2069          } else {
    2070              $upperbound = calendar_days_in_month($month, $year);
    2071          }
    2072  
    2073          // Mark all days between $lowerbound and $upperbound (inclusive) as duration.
    2074          for ($i = $lowerbound + 1; $i <= $upperbound; ++$i) {
    2075              $durationbyday[$i][] = $event->id;
    2076              if ($event->courseid == SITEID && $event->groupid == 0) {
    2077                  $typesbyday[$i]['durationsite'] = true;
    2078              } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {
    2079                  $typesbyday[$i]['durationcourse'] = true;
    2080              } else if ($event->groupid) {
    2081                  $typesbyday[$i]['durationgroup'] = true;
    2082              } else if ($event->userid) {
    2083                  $typesbyday[$i]['durationuser'] = true;
    2084              }
    2085          }
    2086  
    2087      }
    2088      return;
    2089  }
    2090  
    2091  /**
    2092   * Returns the courses to load events for.
    2093   *
    2094   * @param array $courseeventsfrom An array of courses to load calendar events for
    2095   * @param bool $ignorefilters specify the use of filters, false is set as default
    2096   * @param stdClass $user The user object. This defaults to the global $USER object.
    2097   * @return array An array of courses, groups, and user to load calendar events for based upon filters
    2098   */
    2099  function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false, stdClass $user = null) {
    2100      global $CFG, $USER;
    2101  
    2102      if (is_null($user)) {
    2103          $user = $USER;
    2104      }
    2105  
    2106      $courses = array();
    2107      $userid = false;
    2108      $group = false;
    2109  
    2110      // Get the capabilities that allow seeing group events from all groups.
    2111      $allgroupscaps = array('moodle/site:accessallgroups', 'moodle/calendar:manageentries');
    2112  
    2113      $isvaliduser = !empty($user->id);
    2114  
    2115      if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_COURSE, $user)) {
    2116          $courses = array_keys($courseeventsfrom);
    2117      }
    2118      if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_SITE, $user)) {
    2119          $courses[] = SITEID;
    2120      }
    2121      $courses = array_unique($courses);
    2122      sort($courses);
    2123  
    2124      if (!empty($courses) && in_array(SITEID, $courses)) {
    2125          // Sort courses for consistent colour highlighting.
    2126          // Effectively ignoring SITEID as setting as last course id.
    2127          $key = array_search(SITEID, $courses);
    2128          unset($courses[$key]);
    2129          $courses[] = SITEID;
    2130      }
    2131  
    2132      if ($ignorefilters || ($isvaliduser && calendar_show_event_type(CALENDAR_EVENT_USER, $user))) {
    2133          $userid = $user->id;
    2134      }
    2135  
    2136      if (!empty($courseeventsfrom) && (calendar_show_event_type(CALENDAR_EVENT_GROUP, $user) || $ignorefilters)) {
    2137  
    2138          if (count($courseeventsfrom) == 1) {
    2139              $course = reset($courseeventsfrom);
    2140              if (has_any_capability($allgroupscaps, \context_course::instance($course->id))) {
    2141                  $coursegroups = groups_get_all_groups($course->id, 0, 0, 'g.id');
    2142                  $group = array_keys($coursegroups);
    2143              }
    2144          }
    2145          if ($group === false) {
    2146              if (!empty($CFG->calendar_adminseesall) && has_any_capability($allgroupscaps, \context_system::instance())) {
    2147                  $group = true;
    2148              } else if ($isvaliduser) {
    2149                  $groupids = array();
    2150                  foreach ($courseeventsfrom as $courseid => $course) {
    2151                      // If the user is an editing teacher in there.
    2152                      if (!empty($user->groupmember[$course->id])) {
    2153                          // We've already cached the users groups for this course so we can just use that.
    2154                          $groupids = array_merge($groupids, $user->groupmember[$course->id]);
    2155                      } else if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
    2156                          // If this course has groups, show events from all of those related to the current user.
    2157                          $coursegroups = groups_get_user_groups($course->id, $user->id);
    2158                          $groupids = array_merge($groupids, $coursegroups['0']);
    2159                      }
    2160                  }
    2161                  if (!empty($groupids)) {
    2162                      $group = $groupids;
    2163                  }
    2164              }
    2165          }
    2166      }
    2167      if (empty($courses)) {
    2168          $courses = false;
    2169      }
    2170  
    2171      return array($courses, $group, $userid);
    2172  }
    2173  
    2174  /**
    2175   * Can current user manage a non user event in system context.
    2176   *
    2177   * @param calendar_event|stdClass $event event object
    2178   * @return boolean
    2179   */
    2180  function calendar_can_manage_non_user_event_in_system($event) {
    2181      $sitecontext = \context_system::instance();
    2182      $isuserevent = $event->eventtype == 'user';
    2183      $canmanageentries = has_capability('moodle/calendar:manageentries', $sitecontext);
    2184      // If user has manageentries at site level and it's not user event, return true.
    2185      if ($canmanageentries && !$isuserevent) {
    2186          return true;
    2187      }
    2188  
    2189      return false;
    2190  }
    2191  
    2192  /**
    2193   * Return the capability for viewing a calendar event.
    2194   *
    2195   * @param calendar_event $event event object
    2196   * @return boolean
    2197   */
    2198  function calendar_view_event_allowed(calendar_event $event) {
    2199      global $USER;
    2200  
    2201      // Anyone can see site events.
    2202      if ($event->courseid && $event->courseid == SITEID) {
    2203          return true;
    2204      }
    2205  
    2206      if (calendar_can_manage_non_user_event_in_system($event)) {
    2207          return true;
    2208      }
    2209  
    2210      if (!empty($event->groupid)) {
    2211          // If it is a group event we need to be able to manage events in the course, or be in the group.
    2212          if (has_capability('moodle/calendar:manageentries', $event->context) ||
    2213                  has_capability('moodle/calendar:managegroupentries', $event->context)) {
    2214              return true;
    2215          }
    2216  
    2217          $mycourses = enrol_get_my_courses('id');
    2218          return isset($mycourses[$event->courseid]) && groups_is_member($event->groupid);
    2219      } else if ($event->modulename) {
    2220          // If this is a module event we need to be able to see the module.
    2221          $coursemodules = [];
    2222          $courseid = 0;
    2223          // Override events do not have the courseid set.
    2224          if ($event->courseid) {
    2225              $courseid = $event->courseid;
    2226              $coursemodules = get_fast_modinfo($event->courseid)->instances;
    2227          } else {
    2228              $cmraw = get_coursemodule_from_instance($event->modulename, $event->instance, 0, false, MUST_EXIST);
    2229              $courseid = $cmraw->course;
    2230              $coursemodules = get_fast_modinfo($cmraw->course)->instances;
    2231          }
    2232          $hasmodule = isset($coursemodules[$event->modulename]);
    2233          $hasinstance = isset($coursemodules[$event->modulename][$event->instance]);
    2234  
    2235          // If modinfo doesn't know about the module, return false to be safe.
    2236          if (!$hasmodule || !$hasinstance) {
    2237              return false;
    2238          }
    2239  
    2240          // Must be able to see the course and the module - MDL-59304.
    2241          $cm = $coursemodules[$event->modulename][$event->instance];
    2242          if (!$cm->uservisible) {
    2243              return false;
    2244          }
    2245          $mycourses = enrol_get_my_courses('id');
    2246          return isset($mycourses[$courseid]);
    2247      } else if ($event->categoryid) {
    2248          // If this is a category we need to be able to see the category.
    2249          $cat = \core_course_category::get($event->categoryid, IGNORE_MISSING);
    2250          if (!$cat) {
    2251              return false;
    2252          }
    2253          return true;
    2254      } else if (!empty($event->courseid)) {
    2255          // If it is a course event we need to be able to manage events in the course, or be in the course.
    2256          if (has_capability('moodle/calendar:manageentries', $event->context)) {
    2257              return true;
    2258          }
    2259  
    2260          return can_access_course(get_course($event->courseid));
    2261      } else if ($event->userid) {
    2262          return calendar_can_manage_user_event($event);
    2263      } else {
    2264          throw new moodle_exception('unknown event type');
    2265      }
    2266  
    2267      return false;
    2268  }
    2269  
    2270  /**
    2271   * Return the capability for editing calendar event.
    2272   *
    2273   * @param calendar_event $event event object
    2274   * @param bool $manualedit is the event being edited manually by the user
    2275   * @return bool capability to edit event
    2276   */
    2277  function calendar_edit_event_allowed($event, $manualedit = false) {
    2278      global $USER, $DB;
    2279  
    2280      // Must be logged in.
    2281      if (!isloggedin()) {
    2282          return false;
    2283      }
    2284  
    2285      // Can not be using guest account.
    2286      if (isguestuser()) {
    2287          return false;
    2288      }
    2289  
    2290      if ($manualedit && !empty($event->modulename)) {
    2291          $hascallback = component_callback_exists(
    2292              'mod_' . $event->modulename,
    2293              'core_calendar_event_timestart_updated'
    2294          );
    2295  
    2296          if (!$hascallback) {
    2297              // If the activity hasn't implemented the correct callback
    2298              // to handle changes to it's events then don't allow any
    2299              // manual changes to them.
    2300              return false;
    2301          }
    2302  
    2303          $coursemodules = get_fast_modinfo($event->courseid)->instances;
    2304          $hasmodule = isset($coursemodules[$event->modulename]);
    2305          $hasinstance = isset($coursemodules[$event->modulename][$event->instance]);
    2306  
    2307          // If modinfo doesn't know about the module, return false to be safe.
    2308          if (!$hasmodule || !$hasinstance) {
    2309              return false;
    2310          }
    2311  
    2312          $coursemodule = $coursemodules[$event->modulename][$event->instance];
    2313          $context = context_module::instance($coursemodule->id);
    2314          // This is the capability that allows a user to modify the activity
    2315          // settings. Since the activity generated this event we need to check
    2316          // that the current user has the same capability before allowing them
    2317          // to update the event because the changes to the event will be
    2318          // reflected within the activity.
    2319          return has_capability('moodle/course:manageactivities', $context);
    2320      }
    2321  
    2322      if ($manualedit && !empty($event->component)) {
    2323          // TODO possibly we can later add a callback similar to core_calendar_event_timestart_updated in the modules.
    2324          return false;
    2325      }
    2326  
    2327      // You cannot edit URL based calendar subscription events presently.
    2328      if (!empty($event->subscriptionid)) {
    2329          if (!empty($event->subscription->url)) {
    2330              // This event can be updated externally, so it cannot be edited.
    2331              return false;
    2332          }
    2333      }
    2334  
    2335      if (calendar_can_manage_non_user_event_in_system($event)) {
    2336          return true;
    2337      }
    2338  
    2339      // If groupid is set, it's definitely a group event.
    2340      if (!empty($event->groupid)) {
    2341          // Allow users to add/edit group events if -
    2342          // 1) They have manageentries for the course OR
    2343          // 2) They have managegroupentries AND are in the group.
    2344          $group = $DB->get_record('groups', array('id' => $event->groupid));
    2345          return $group && (
    2346                  has_capability('moodle/calendar:manageentries', $event->context) ||
    2347                  (has_capability('moodle/calendar:managegroupentries', $event->context)
    2348                      && groups_is_member($event->groupid)));
    2349      } else if (!empty($event->courseid)) {
    2350          // If groupid is not set, but course is set, it's definitely a course event.
    2351          return has_capability('moodle/calendar:manageentries', $event->context);
    2352      } else if (!empty($event->categoryid)) {
    2353          // If groupid is not set, but category is set, it's definitely a category event.
    2354          return has_capability('moodle/calendar:manageentries', $event->context);
    2355      } else if (!empty($event->userid) && $event->userid == $USER->id) {
    2356          // If course is not set, but userid id set, it's a user event.
    2357          return (has_capability('moodle/calendar:manageownentries', $event->context));
    2358      } else if (!empty($event->userid)) {
    2359          return calendar_can_manage_user_event($event);
    2360      }
    2361  
    2362      return false;
    2363  }
    2364  
    2365  /**
    2366   * Can current user edit/delete/add an user event?
    2367   *
    2368   * @param calendar_event|stdClass $event event object
    2369   * @return bool
    2370   */
    2371  function calendar_can_manage_user_event($event): bool {
    2372      global $USER;
    2373  
    2374      if (!($event instanceof \calendar_event)) {
    2375          $event = new \calendar_event(clone($event));
    2376      }
    2377  
    2378      $canmanage = has_capability('moodle/calendar:manageentries', $event->context);
    2379      $canmanageown = has_capability('moodle/calendar:manageownentries', $event->context);
    2380      $ismyevent = $event->userid == $USER->id;
    2381      $isadminevent = is_siteadmin($event->userid);
    2382  
    2383      if ($canmanageown && $ismyevent) {
    2384          return true;
    2385      }
    2386  
    2387      // In site context, user must have login and calendar:manageentries permissions
    2388      // ... to manage other user's events except admin users.
    2389      if ($canmanage && !$isadminevent) {
    2390          return true;
    2391      }
    2392  
    2393      return false;
    2394  }
    2395  
    2396  /**
    2397   * Return the capability for deleting a calendar event.
    2398   *
    2399   * @param calendar_event $event The event object
    2400   * @return bool Whether the user has permission to delete the event or not.
    2401   */
    2402  function calendar_delete_event_allowed($event) {
    2403      // Only allow delete if you have capabilities and it is not an module or component event.
    2404      return (calendar_edit_event_allowed($event) && empty($event->modulename) && empty($event->component));
    2405  }
    2406  
    2407  /**
    2408   * Returns the default courses to display on the calendar when there isn't a specific
    2409   * course to display.
    2410   *
    2411   * @param int $courseid (optional) If passed, an additional course can be returned for admins (the current course).
    2412   * @param string $fields Comma separated list of course fields to return.
    2413   * @param bool $canmanage If true, this will return the list of courses the user can create events in, rather
    2414   *                        than the list of courses they see events from (an admin can always add events in a course
    2415   *                        calendar, even if they are not enrolled in the course).
    2416   * @param int $userid (optional) The user which this function returns the default courses for.
    2417   *                        By default the current user.
    2418   * @return array $courses Array of courses to display
    2419   */
    2420  function calendar_get_default_courses($courseid = null, $fields = '*', $canmanage = false, int $userid = null) {
    2421      global $CFG, $USER;
    2422  
    2423      if (!$userid) {
    2424          if (!isloggedin()) {
    2425              return array();
    2426          }
    2427          $userid = $USER->id;
    2428      }
    2429  
    2430      if ((!empty($CFG->calendar_adminseesall) || $canmanage) &&
    2431              has_capability('moodle/calendar:manageentries', context_system::instance(), $userid)) {
    2432  
    2433          // Add a c. prefix to every field as expected by get_courses function.
    2434          $fieldlist = explode(',', $fields);
    2435  
    2436          $prefixedfields = array_map(function($value) {
    2437              return 'c.' . trim(strtolower($value));
    2438          }, $fieldlist);
    2439  
    2440          $courses = get_courses('all', 'c.shortname', implode(',', $prefixedfields));
    2441      } else {
    2442          $courses = enrol_get_users_courses($userid, true, $fields);
    2443      }
    2444  
    2445      if ($courseid && $courseid != SITEID) {
    2446          if (empty($courses[$courseid]) && has_capability('moodle/calendar:manageentries', context_system::instance(), $userid)) {
    2447              // Allow a site admin to see calendars from courses he is not enrolled in.
    2448              // This will come from $COURSE.
    2449              $courses[$courseid] = get_course($courseid);
    2450          }
    2451      }
    2452  
    2453      return $courses;
    2454  }
    2455  
    2456  /**
    2457   * Get event format time.
    2458   *
    2459   * @param calendar_event $event event object
    2460   * @param int $now current time in gmt
    2461   * @param array $linkparams list of params for event link
    2462   * @param bool $usecommonwords the words as formatted date/time.
    2463   * @param int $showtime determine the show time GMT timestamp
    2464   * @return string $eventtime link/string for event time
    2465   */
    2466  function calendar_format_event_time($event, $now, $linkparams = null, $usecommonwords = true, $showtime = 0) {
    2467      $starttime = $event->timestart;
    2468      $endtime = $event->timestart + $event->timeduration;
    2469  
    2470      if (empty($linkparams) || !is_array($linkparams)) {
    2471          $linkparams = array();
    2472      }
    2473  
    2474      $linkparams['view'] = 'day';
    2475  
    2476      // OK, now to get a meaningful display.
    2477      // Check if there is a duration for this event.
    2478      if ($event->timeduration) {
    2479          // Get the midnight of the day the event will start.
    2480          $usermidnightstart = usergetmidnight($starttime);
    2481          // Get the midnight of the day the event will end.
    2482          $usermidnightend = usergetmidnight($endtime);
    2483          // Check if we will still be on the same day.
    2484          if ($usermidnightstart == $usermidnightend) {
    2485              // Check if we are running all day.
    2486              if ($event->timeduration == DAYSECS) {
    2487                  $time = get_string('allday', 'calendar');
    2488              } else { // Specify the time we will be running this from.
    2489                  $datestart = calendar_time_representation($starttime);
    2490                  $dateend = calendar_time_representation($endtime);
    2491                  $time = $datestart . ' <strong>&raquo;</strong> ' . $dateend;
    2492              }
    2493  
    2494              // Set printable representation.
    2495              if (!$showtime) {
    2496                  $day = calendar_day_representation($event->timestart, $now, $usecommonwords);
    2497                  $url = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $endtime);
    2498                  $eventtime = \html_writer::link($url, $day) . ', ' . $time;
    2499              } else {
    2500                  $eventtime = $time;
    2501              }
    2502          } else { // It must spans two or more days.
    2503              $daystart = calendar_day_representation($event->timestart, $now, $usecommonwords) . ', ';
    2504              if ($showtime == $usermidnightstart) {
    2505                  $daystart = '';
    2506              }
    2507              $timestart = calendar_time_representation($event->timestart);
    2508              $dayend = calendar_day_representation($event->timestart + $event->timeduration, $now, $usecommonwords) . ', ';
    2509              if ($showtime == $usermidnightend) {
    2510                  $dayend = '';
    2511              }
    2512              $timeend = calendar_time_representation($event->timestart + $event->timeduration);
    2513  
    2514              // Set printable representation.
    2515              if ($now >= $usermidnightstart && $now < strtotime('+1 day', $usermidnightstart)) {
    2516                  $url = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $endtime);
    2517                  $eventtime = $timestart . ' <strong>&raquo;</strong> ' . \html_writer::link($url, $dayend) . $timeend;
    2518              } else {
    2519                  // The event is in the future, print start and end links.
    2520                  $url = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $starttime);
    2521                  $eventtime = \html_writer::link($url, $daystart) . $timestart . ' <strong>&raquo;</strong> ';
    2522  
    2523                  $url = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', $linkparams),  0, 0, 0, $endtime);
    2524                  $eventtime .= \html_writer::link($url, $dayend) . $timeend;
    2525              }
    2526          }
    2527      } else { // There is no time duration.
    2528          $time = calendar_time_representation($event->timestart);
    2529          // Set printable representation.
    2530          if (!$showtime) {
    2531              $day = calendar_day_representation($event->timestart, $now, $usecommonwords);
    2532              $url = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', $linkparams),  0, 0, 0, $starttime);
    2533              $eventtime = \html_writer::link($url, $day) . ', ' . trim($time);
    2534          } else {
    2535              $eventtime = $time;
    2536          }
    2537      }
    2538  
    2539      // Check if It has expired.
    2540      if ($event->timestart + $event->timeduration < $now) {
    2541          $eventtime = '<span class="dimmed_text">' . str_replace(' href=', ' class="dimmed" href=', $eventtime) . '</span>';
    2542      }
    2543  
    2544      return $eventtime;
    2545  }
    2546  
    2547  /**
    2548   * Checks to see if the requested type of event should be shown for the given user.
    2549   *
    2550   * @param int $type The type to check the display for (default is to display all)
    2551   * @param stdClass|int|null $user The user to check for - by default the current user
    2552   * @return bool True if the tyep should be displayed false otherwise
    2553   */
    2554  function calendar_show_event_type($type, $user = null) {
    2555      $default = CALENDAR_EVENT_SITE + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
    2556  
    2557      if (get_user_preferences('calendar_persistflt', 0, $user) === 0) {
    2558          global $SESSION;
    2559          if (!isset($SESSION->calendarshoweventtype)) {
    2560              $SESSION->calendarshoweventtype = $default;
    2561          }
    2562          return $SESSION->calendarshoweventtype & $type;
    2563      } else {
    2564          return get_user_preferences('calendar_savedflt', $default, $user) & $type;
    2565      }
    2566  }
    2567  
    2568  /**
    2569   * Sets the display of the event type given $display.
    2570   *
    2571   * If $display = true the event type will be shown.
    2572   * If $display = false the event type will NOT be shown.
    2573   * If $display = null the current value will be toggled and saved.
    2574   *
    2575   * @param int $type object of CALENDAR_EVENT_XXX
    2576   * @param bool $display option to display event type
    2577   * @param stdClass|int $user moodle user object or id, null means current user
    2578   */
    2579  function calendar_set_event_type_display($type, $display = null, $user = null) {
    2580      $persist = get_user_preferences('calendar_persistflt', 0, $user);
    2581      $default = CALENDAR_EVENT_SITE + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP
    2582              + CALENDAR_EVENT_USER + CALENDAR_EVENT_COURSECAT;
    2583      if ($persist === 0) {
    2584          global $SESSION;
    2585          if (!isset($SESSION->calendarshoweventtype)) {
    2586              $SESSION->calendarshoweventtype = $default;
    2587          }
    2588          $preference = $SESSION->calendarshoweventtype;
    2589      } else {
    2590          $preference = get_user_preferences('calendar_savedflt', $default, $user);
    2591      }
    2592      $current = $preference & $type;
    2593      if ($display === null) {
    2594          $display = !$current;
    2595      }
    2596      if ($display && !$current) {
    2597          $preference += $type;
    2598      } else if (!$display && $current) {
    2599          $preference -= $type;
    2600      }
    2601      if ($persist === 0) {
    2602          $SESSION->calendarshoweventtype = $preference;
    2603      } else {
    2604          if ($preference == $default) {
    2605              unset_user_preference('calendar_savedflt', $user);
    2606          } else {
    2607              set_user_preference('calendar_savedflt', $preference, $user);
    2608          }
    2609      }
    2610  }
    2611  
    2612  /**
    2613   * Get calendar's allowed types.
    2614   *
    2615   * @param stdClass $allowed list of allowed edit for event  type
    2616   * @param stdClass|int $course object of a course or course id
    2617   * @param array $groups array of groups for the given course
    2618   * @param stdClass|int $category object of a category
    2619   */
    2620  function calendar_get_allowed_types(&$allowed, $course = null, $groups = null, $category = null) {
    2621      global $USER, $DB;
    2622  
    2623      $allowed = new \stdClass();
    2624      $allowed->user = has_capability('moodle/calendar:manageownentries', \context_system::instance());
    2625      $allowed->groups = false;
    2626      $allowed->courses = false;
    2627      $allowed->categories = false;
    2628      $allowed->site = has_capability('moodle/calendar:manageentries', \context_course::instance(SITEID));
    2629      $getgroupsfunc = function($course, $context, $user) use ($groups) {
    2630          if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
    2631              if (has_capability('moodle/site:accessallgroups', $context)) {
    2632                  return is_null($groups) ? groups_get_all_groups($course->id) : $groups;
    2633              } else {
    2634                  if (is_null($groups)) {
    2635                      return groups_get_all_groups($course->id, $user->id);
    2636                  } else {
    2637                      return array_filter($groups, function($group) use ($user) {
    2638                          return isset($group->members[$user->id]);
    2639                      });
    2640                  }
    2641              }
    2642          }
    2643  
    2644          return false;
    2645      };
    2646  
    2647      if (!empty($course)) {
    2648          if (!is_object($course)) {
    2649              $course = $DB->get_record('course', array('id' => $course), 'id, groupmode, groupmodeforce', MUST_EXIST);
    2650          }
    2651          if ($course->id != SITEID) {
    2652              $coursecontext = \context_course::instance($course->id);
    2653              $allowed->user = has_capability('moodle/calendar:manageownentries', $coursecontext);
    2654  
    2655              if (has_capability('moodle/calendar:manageentries', $coursecontext)) {
    2656                  $allowed->courses = array($course->id => 1);
    2657                  $allowed->groups = $getgroupsfunc($course, $coursecontext, $USER);
    2658              } else if (has_capability('moodle/calendar:managegroupentries', $coursecontext)) {
    2659                  $allowed->groups = $getgroupsfunc($course, $coursecontext, $USER);
    2660              }
    2661          }
    2662      }
    2663  
    2664      if (!empty($category)) {
    2665          $catcontext = \context_coursecat::instance($category->id);
    2666          if (has_capability('moodle/category:manage', $catcontext)) {
    2667              $allowed->categories = [$category->id => 1];
    2668          }
    2669      }
    2670  }
    2671  
    2672  /**
    2673   * See if user can add calendar entries at all used to print the "New Event" button.
    2674   *
    2675   * @param stdClass $course object of a course or course id
    2676   * @return bool has the capability to add at least one event type
    2677   */
    2678  function calendar_user_can_add_event($course) {
    2679      if (!isloggedin() || isguestuser()) {
    2680          return false;
    2681      }
    2682  
    2683      calendar_get_allowed_types($allowed, $course);
    2684  
    2685      return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->categories || $allowed->site);
    2686  }
    2687  
    2688  /**
    2689   * Check wether the current user is permitted to add events.
    2690   *
    2691   * @param stdClass $event object of event
    2692   * @return bool has the capability to add event
    2693   */
    2694  function calendar_add_event_allowed($event) {
    2695      global $USER, $DB;
    2696  
    2697      // Can not be using guest account.
    2698      if (!isloggedin() or isguestuser()) {
    2699          return false;
    2700      }
    2701  
    2702      if (calendar_can_manage_non_user_event_in_system($event)) {
    2703          return true;
    2704      }
    2705  
    2706      switch ($event->eventtype) {
    2707          case 'category':
    2708              return has_capability('moodle/category:manage', $event->context);
    2709          case 'course':
    2710              return has_capability('moodle/calendar:manageentries', $event->context);
    2711          case 'group':
    2712              // Allow users to add/edit group events if -
    2713              // 1) They have manageentries (= entries for whole course).
    2714              // 2) They have managegroupentries AND are in the group.
    2715              $group = $DB->get_record('groups', array('id' => $event->groupid));
    2716              return $group && (
    2717                      has_capability('moodle/calendar:manageentries', $event->context) ||
    2718                      (has_capability('moodle/calendar:managegroupentries', $event->context)
    2719                          && groups_is_member($event->groupid)));
    2720          case 'user':
    2721              return calendar_can_manage_user_event($event);
    2722          case 'site':
    2723              return has_capability('moodle/calendar:manageentries', $event->context);
    2724          default:
    2725              return has_capability('moodle/calendar:manageentries', $event->context);
    2726      }
    2727  }
    2728  
    2729  /**
    2730   * Returns option list for the poll interval setting.
    2731   *
    2732   * @return array An array of poll interval options. Interval => description.
    2733   */
    2734  function calendar_get_pollinterval_choices() {
    2735      return array(
    2736          '0' => new \lang_string('never', 'calendar'),
    2737          HOURSECS => new \lang_string('hourly', 'calendar'),
    2738          DAYSECS => new \lang_string('daily', 'calendar'),
    2739          WEEKSECS => new \lang_string('weekly', 'calendar'),
    2740          '2628000' => new \lang_string('monthly', 'calendar'),
    2741          YEARSECS => new \lang_string('annually', 'calendar')
    2742      );
    2743  }
    2744  
    2745  /**
    2746   * Returns option list of available options for the calendar event type, given the current user and course.
    2747   *
    2748   * @param int $courseid The id of the course
    2749   * @return array An array containing the event types the user can create.
    2750   */
    2751  function calendar_get_eventtype_choices($courseid) {
    2752      $choices = array();
    2753      $allowed = new \stdClass;
    2754      calendar_get_allowed_types($allowed, $courseid);
    2755  
    2756      if ($allowed->user) {
    2757          $choices['user'] = get_string('userevents', 'calendar');
    2758      }
    2759      if ($allowed->site) {
    2760          $choices['site'] = get_string('siteevents', 'calendar');
    2761      }
    2762      if (!empty($allowed->courses)) {
    2763          $choices['course'] = get_string('courseevents', 'calendar');
    2764      }
    2765      if (!empty($allowed->categories)) {
    2766          $choices['category'] = get_string('categoryevents', 'calendar');
    2767      }
    2768      if (!empty($allowed->groups) and is_array($allowed->groups)) {
    2769          $choices['group'] = get_string('group');
    2770      }
    2771  
    2772      return array($choices, $allowed->groups);
    2773  }
    2774  
    2775  /**
    2776   * Add an iCalendar subscription to the database.
    2777   *
    2778   * @param stdClass $sub The subscription object (e.g. from the form)
    2779   * @return int The insert ID, if any.
    2780   */
    2781  function calendar_add_subscription($sub) {
    2782      global $DB, $USER, $SITE;
    2783  
    2784      // Undo the form definition work around to allow us to have two different
    2785      // course selectors present depending on which event type the user selects.
    2786      if (!empty($sub->groupcourseid)) {
    2787          $sub->courseid = $sub->groupcourseid;
    2788          unset($sub->groupcourseid);
    2789      }
    2790  
    2791      // Default course id if none is set.
    2792      if (empty($sub->courseid)) {
    2793          if ($sub->eventtype === 'site') {
    2794              $sub->courseid = SITEID;
    2795          } else {
    2796              $sub->courseid = 0;
    2797          }
    2798      }
    2799  
    2800      if ($sub->eventtype === 'site') {
    2801          $sub->courseid = $SITE->id;
    2802      } else if ($sub->eventtype === 'group' || $sub->eventtype === 'course') {
    2803          $sub->courseid = $sub->courseid;
    2804      } else if ($sub->eventtype === 'category') {
    2805          $sub->categoryid = $sub->categoryid;
    2806      } else {
    2807          // User events.
    2808          $sub->courseid = 0;
    2809      }
    2810      $sub->userid = $USER->id;
    2811  
    2812      // File subscriptions never update.
    2813      if (empty($sub->url)) {
    2814          $sub->pollinterval = 0;
    2815      }
    2816  
    2817      if (!empty($sub->name)) {
    2818          if (empty($sub->id)) {
    2819              $id = $DB->insert_record('event_subscriptions', $sub);
    2820              // We cannot cache the data here because $sub is not complete.
    2821              $sub->id = $id;
    2822              // Trigger event, calendar subscription added.
    2823              $eventparams = array('objectid' => $sub->id,
    2824                  'context' => calendar_get_calendar_context($sub),
    2825                  'other' => array(
    2826                      'eventtype' => $sub->eventtype,
    2827                  )
    2828              );
    2829              switch ($sub->eventtype) {
    2830                  case 'category':
    2831                      $eventparams['other']['categoryid'] = $sub->categoryid;
    2832                      break;
    2833                  case 'course':
    2834                      $eventparams['other']['courseid'] = $sub->courseid;
    2835                      break;
    2836                  case 'group':
    2837                      $eventparams['other']['courseid'] = $sub->courseid;
    2838                      $eventparams['other']['groupid'] = $sub->groupid;
    2839                      break;
    2840                  default:
    2841                      $eventparams['other']['courseid'] = $sub->courseid;
    2842              }
    2843  
    2844              $event = \core\event\calendar_subscription_created::create($eventparams);
    2845              $event->trigger();
    2846              return $id;
    2847          } else {
    2848              // Why are we doing an update here?
    2849              calendar_update_subscription($sub);
    2850              return $sub->id;
    2851          }
    2852      } else {
    2853          print_error('errorbadsubscription', 'importcalendar');
    2854      }
    2855  }
    2856  
    2857  /**
    2858   * Add an iCalendar event to the Moodle calendar.
    2859   *
    2860   * @param stdClass $event The RFC-2445 iCalendar event
    2861   * @param int $unused Deprecated
    2862   * @param int $subscriptionid The iCalendar subscription ID
    2863   * @param string $timezone The X-WR-TIMEZONE iCalendar property if provided
    2864   * @throws dml_exception A DML specific exception is thrown for invalid subscriptionids.
    2865   * @return int Code: CALENDAR_IMPORT_EVENT_UPDATED = updated,  CALENDAR_IMPORT_EVENT_INSERTED = inserted, 0 = error
    2866   */
    2867  function calendar_add_icalendar_event($event, $unused, $subscriptionid, $timezone='UTC') {
    2868      global $DB;
    2869  
    2870      // Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event.
    2871      if (empty($event->properties['SUMMARY'])) {
    2872          return 0;
    2873      }
    2874  
    2875      $name = $event->properties['SUMMARY'][0]->value;
    2876      $name = str_replace('\n', '<br />', $name);
    2877      $name = str_replace('\\', '', $name);
    2878      $name = preg_replace('/\s+/u', ' ', $name);
    2879  
    2880      $eventrecord = new \stdClass;
    2881      $eventrecord->name = clean_param($name, PARAM_NOTAGS);
    2882  
    2883      if (empty($event->properties['DESCRIPTION'][0]->value)) {
    2884          $description = '';
    2885      } else {
    2886          $description = $event->properties['DESCRIPTION'][0]->value;
    2887          $description = clean_param($description, PARAM_NOTAGS);
    2888          $description = str_replace('\n', '<br />', $description);
    2889          $description = str_replace('\\', '', $description);
    2890          $description = preg_replace('/\s+/u', ' ', $description);
    2891      }
    2892      $eventrecord->description = $description;
    2893  
    2894      // Probably a repeating event with RRULE etc. TODO: skip for now.
    2895      if (empty($event->properties['DTSTART'][0]->value)) {
    2896          return 0;
    2897      }
    2898  
    2899      if (isset($event->properties['DTSTART'][0]->parameters['TZID'])) {
    2900          $tz = $event->properties['DTSTART'][0]->parameters['TZID'];
    2901      } else {
    2902          $tz = $timezone;
    2903      }
    2904      $tz = \core_date::normalise_timezone($tz);
    2905      $eventrecord->timestart = strtotime($event->properties['DTSTART'][0]->value . ' ' . $tz);
    2906      if (empty($event->properties['DTEND'])) {
    2907          $eventrecord->timeduration = 0; // No duration if no end time specified.
    2908      } else {
    2909          if (isset($event->properties['DTEND'][0]->parameters['TZID'])) {
    2910              $endtz = $event->properties['DTEND'][0]->parameters['TZID'];
    2911          } else {
    2912              $endtz = $timezone;
    2913          }
    2914          $endtz = \core_date::normalise_timezone($endtz);
    2915          $eventrecord->timeduration = strtotime($event->properties['DTEND'][0]->value . ' ' . $endtz) - $eventrecord->timestart;
    2916      }
    2917  
    2918      // Check to see if it should be treated as an all day event.
    2919      if ($eventrecord->timeduration == DAYSECS) {
    2920          // Check to see if the event started at Midnight on the imported calendar.
    2921          date_default_timezone_set($timezone);
    2922          if (date('H:i:s', $eventrecord->timestart) === "00:00:00") {
    2923              // This event should be an all day event. This is not correct, we don't do anything differently for all day events.
    2924              // See MDL-56227.
    2925              $eventrecord->timeduration = 0;
    2926          }
    2927          \core_date::set_default_server_timezone();
    2928      }
    2929  
    2930      $eventrecord->location = empty($event->properties['LOCATION'][0]->value) ? '' :
    2931              trim(str_replace('\\', '', $event->properties['LOCATION'][0]->value));
    2932      $eventrecord->uuid = $event->properties['UID'][0]->value;
    2933      $eventrecord->timemodified = time();
    2934  
    2935      // Add the iCal subscription details if required.
    2936      // We should never do anything with an event without a subscription reference.
    2937      $sub = calendar_get_subscription($subscriptionid);
    2938      $eventrecord->subscriptionid = $subscriptionid;
    2939      $eventrecord->userid = $sub->userid;
    2940      $eventrecord->groupid = $sub->groupid;
    2941      $eventrecord->courseid = $sub->courseid;
    2942      $eventrecord->categoryid = $sub->categoryid;
    2943      $eventrecord->eventtype = $sub->eventtype;
    2944  
    2945      if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid,
    2946          'subscriptionid' => $eventrecord->subscriptionid))) {
    2947  
    2948          // Compare iCal event data against the moodle event to see if something has changed.
    2949          $result = array_diff((array) $eventrecord, (array) $updaterecord);
    2950  
    2951          // Unset timemodified field because it's always going to be different.
    2952          unset($result['timemodified']);
    2953  
    2954          if (count($result)) {
    2955              $eventrecord->id = $updaterecord->id;
    2956              $return = CALENDAR_IMPORT_EVENT_UPDATED; // Update.
    2957          } else {
    2958              return CALENDAR_IMPORT_EVENT_SKIPPED;
    2959          }
    2960      } else {
    2961          $return = CALENDAR_IMPORT_EVENT_INSERTED; // Insert.
    2962      }
    2963  
    2964      if ($createdevent = \calendar_event::create($eventrecord, false)) {
    2965          if (!empty($event->properties['RRULE'])) {
    2966              // Repeating events.
    2967              date_default_timezone_set($tz); // Change time zone to parse all events.
    2968              $rrule = new \core_calendar\rrule_manager($event->properties['RRULE'][0]->value);
    2969              $rrule->parse_rrule();
    2970              $rrule->create_events($createdevent);
    2971              \core_date::set_default_server_timezone(); // Change time zone back to what it was.
    2972          }
    2973          return $return;
    2974      } else {
    2975          return 0;
    2976      }
    2977  }
    2978  
    2979  /**
    2980   * Update a subscription from the form data in one of the rows in the existing subscriptions table.
    2981   *
    2982   * @param int $subscriptionid The ID of the subscription we are acting upon.
    2983   * @param int $pollinterval The poll interval to use.
    2984   * @param int $action The action to be performed. One of update or remove.
    2985   * @throws dml_exception if invalid subscriptionid is provided
    2986   * @return string A log of the import progress, including errors
    2987   */
    2988  function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) {
    2989      // Fetch the subscription from the database making sure it exists.
    2990      $sub = calendar_get_subscription($subscriptionid);
    2991  
    2992      // Update or remove the subscription, based on action.
    2993      switch ($action) {
    2994          case CALENDAR_SUBSCRIPTION_UPDATE:
    2995              // Skip updating file subscriptions.
    2996              if (empty($sub->url)) {
    2997                  break;
    2998              }
    2999              $sub->pollinterval = $pollinterval;
    3000              calendar_update_subscription($sub);
    3001  
    3002              // Update the events.
    3003              return "<p>" . get_string('subscriptionupdated', 'calendar', $sub->name) . "</p>" .
    3004                  calendar_update_subscription_events($subscriptionid);
    3005          case CALENDAR_SUBSCRIPTION_REMOVE:
    3006              calendar_delete_subscription($subscriptionid);
    3007              return get_string('subscriptionremoved', 'calendar', $sub->name);
    3008              break;
    3009          default:
    3010              break;
    3011      }
    3012      return '';
    3013  }
    3014  
    3015  /**
    3016   * Delete subscription and all related events.
    3017   *
    3018   * @param int|stdClass $subscription subscription or it's id, which needs to be deleted.
    3019   */
    3020  function calendar_delete_subscription($subscription) {
    3021      global $DB;
    3022  
    3023      if (!is_object($subscription)) {
    3024          $subscription = $DB->get_record('event_subscriptions', array('id' => $subscription), '*', MUST_EXIST);
    3025      }
    3026  
    3027      // Delete subscription and related events.
    3028      $DB->delete_records('event', array('subscriptionid' => $subscription->id));
    3029      $DB->delete_records('event_subscriptions', array('id' => $subscription->id));
    3030      \cache_helper::invalidate_by_definition('core', 'calendar_subscriptions', array(), array($subscription->id));
    3031  
    3032      // Trigger event, calendar subscription deleted.
    3033      $eventparams = array('objectid' => $subscription->id,
    3034          'context' => calendar_get_calendar_context($subscription),
    3035          'other' => array(
    3036              'eventtype' => $subscription->eventtype,
    3037          )
    3038      );
    3039      switch ($subscription->eventtype) {
    3040          case 'category':
    3041              $eventparams['other']['categoryid'] = $subscription->categoryid;
    3042              break;
    3043          case 'course':
    3044              $eventparams['other']['courseid'] = $subscription->courseid;
    3045              break;
    3046          case 'group':
    3047              $eventparams['other']['courseid'] = $subscription->courseid;
    3048              $eventparams['other']['groupid'] = $subscription->groupid;
    3049              break;
    3050          default:
    3051              $eventparams['other']['courseid'] = $subscription->courseid;
    3052      }
    3053      $event = \core\event\calendar_subscription_deleted::create($eventparams);
    3054      $event->trigger();
    3055  }
    3056  
    3057  /**
    3058   * From a URL, fetch the calendar and return an iCalendar object.
    3059   *
    3060   * @param string $url The iCalendar URL
    3061   * @return iCalendar The iCalendar object
    3062   */
    3063  function calendar_get_icalendar($url) {
    3064      global $CFG;
    3065  
    3066      require_once($CFG->libdir . '/filelib.php');
    3067  
    3068      $curl = new \curl();
    3069      $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => 1, 'CURLOPT_MAXREDIRS' => 5));
    3070      $calendar = $curl->get($url);
    3071  
    3072      // Http code validation should actually be the job of curl class.
    3073      if (!$calendar || $curl->info['http_code'] != 200 || !empty($curl->errorno)) {
    3074          throw new \moodle_exception('errorinvalidicalurl', 'calendar');
    3075      }
    3076  
    3077      $ical = new \iCalendar();
    3078      $ical->unserialize($calendar);
    3079  
    3080      return $ical;
    3081  }
    3082  
    3083  /**
    3084   * Import events from an iCalendar object into a course calendar.
    3085   *
    3086   * @param iCalendar $ical The iCalendar object.
    3087   * @param int $unused Deprecated
    3088   * @param int $subscriptionid The subscription ID.
    3089   * @return string A log of the import progress, including errors.
    3090   */
    3091  function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid = null) {
    3092      global $DB;
    3093  
    3094      $return = '';
    3095      $eventcount = 0;
    3096      $updatecount = 0;
    3097      $skippedcount = 0;
    3098  
    3099      // Large calendars take a while...
    3100      if (!CLI_SCRIPT) {
    3101          \core_php_time_limit::raise(300);
    3102      }
    3103  
    3104      // Grab the timezone from the iCalendar file to be used later.
    3105      if (isset($ical->properties['X-WR-TIMEZONE'][0]->value)) {
    3106          $timezone = $ical->properties['X-WR-TIMEZONE'][0]->value;
    3107      } else {
    3108          $timezone = 'UTC';
    3109      }
    3110  
    3111      $icaluuids = [];
    3112      foreach ($ical->components['VEVENT'] as $event) {
    3113          $icaluuids[] = $event->properties['UID'][0]->value;
    3114          $res = calendar_add_icalendar_event($event, null, $subscriptionid, $timezone);
    3115          switch ($res) {
    3116              case CALENDAR_IMPORT_EVENT_UPDATED:
    3117                  $updatecount++;
    3118                  break;
    3119              case CALENDAR_IMPORT_EVENT_INSERTED:
    3120                  $eventcount++;
    3121                  break;
    3122              case CALENDAR_IMPORT_EVENT_SKIPPED:
    3123                  $skippedcount++;
    3124                  break;
    3125              case 0:
    3126                  $return .= '<p>' . get_string('erroraddingevent', 'calendar') . ': ';
    3127                  if (empty($event->properties['SUMMARY'])) {
    3128                      $return .= '(' . get_string('notitle', 'calendar') . ')';
    3129                  } else {
    3130                      $return .= $event->properties['SUMMARY'][0]->value;
    3131                  }
    3132                  $return .= "</p>\n";
    3133                  break;
    3134          }
    3135      }
    3136  
    3137      $existing = $DB->get_field('event_subscriptions', 'lastupdated', ['id' => $subscriptionid]);
    3138      if (!empty($existing)) {
    3139          $eventsuuids = $DB->get_records_menu('event', ['subscriptionid' => $subscriptionid], '', 'id, uuid');
    3140  
    3141          $icaleventscount = count($icaluuids);
    3142          $tobedeleted = [];
    3143          if (count($eventsuuids) > $icaleventscount) {
    3144              foreach ($eventsuuids as $eventid => $eventuuid) {
    3145                  if (!in_array($eventuuid, $icaluuids)) {
    3146                      $tobedeleted[] = $eventid;
    3147                  }
    3148              }
    3149              if (!empty($tobedeleted)) {
    3150                  $DB->delete_records_list('event', 'id', $tobedeleted);
    3151                  $return .= "<p> " . get_string('eventsdeleted', 'calendar') . ": " . count($tobedeleted) . "</p> ";
    3152              }
    3153          }
    3154      }
    3155  
    3156      $return .= "<p>" . get_string('eventsimported', 'calendar', $eventcount) . "</p> ";
    3157      $return .= "<p>" . get_string('eventsskipped', 'calendar', $skippedcount) . "</p> ";
    3158      $return .= "<p>" . get_string('eventsupdated', 'calendar', $updatecount) . "</p>";
    3159      return $return;
    3160  }
    3161  
    3162  /**
    3163   * Fetch a calendar subscription and update the events in the calendar.
    3164   *
    3165   * @param int $subscriptionid The course ID for the calendar.
    3166   * @return string A log of the import progress, including errors.
    3167   */
    3168  function calendar_update_subscription_events($subscriptionid) {
    3169      $sub = calendar_get_subscription($subscriptionid);
    3170  
    3171      // Don't update a file subscription.
    3172      if (empty($sub->url)) {
    3173          return 'File subscription not updated.';
    3174      }
    3175  
    3176      $ical = calendar_get_icalendar($sub->url);
    3177      $return = calendar_import_icalendar_events($ical, null, $subscriptionid);
    3178      $sub->lastupdated = time();
    3179  
    3180      calendar_update_subscription($sub);
    3181  
    3182      return $return;
    3183  }
    3184  
    3185  /**
    3186   * Update a calendar subscription. Also updates the associated cache.
    3187   *
    3188   * @param stdClass|array $subscription Subscription record.
    3189   * @throws coding_exception If something goes wrong
    3190   * @since Moodle 2.5
    3191   */
    3192  function calendar_update_subscription($subscription) {
    3193      global $DB;
    3194  
    3195      if (is_array($subscription)) {
    3196          $subscription = (object)$subscription;
    3197      }
    3198      if (empty($subscription->id) || !$DB->record_exists('event_subscriptions', array('id' => $subscription->id))) {
    3199          throw new \coding_exception('Cannot update a subscription without a valid id');
    3200      }
    3201  
    3202      $DB->update_record('event_subscriptions', $subscription);
    3203  
    3204      // Update cache.
    3205      $cache = \cache::make('core', 'calendar_subscriptions');
    3206      $cache->set($subscription->id, $subscription);
    3207  
    3208      // Trigger event, calendar subscription updated.
    3209      $eventparams = array('userid' => $subscription->userid,
    3210          'objectid' => $subscription->id,
    3211          'context' => calendar_get_calendar_context($subscription),
    3212          'other' => array(
    3213              'eventtype' => $subscription->eventtype,
    3214          )
    3215      );
    3216      switch ($subscription->eventtype) {
    3217          case 'category':
    3218              $eventparams['other']['categoryid'] = $subscription->categoryid;
    3219              break;
    3220          case 'course':
    3221              $eventparams['other']['courseid'] = $subscription->courseid;
    3222              break;
    3223          case 'group':
    3224              $eventparams['other']['courseid'] = $subscription->courseid;
    3225              $eventparams['other']['groupid'] = $subscription->groupid;
    3226              break;
    3227          default:
    3228              $eventparams['other']['courseid'] = $subscription->courseid;
    3229      }
    3230      $event = \core\event\calendar_subscription_updated::create($eventparams);
    3231      $event->trigger();
    3232  }
    3233  
    3234  /**
    3235   * Checks to see if the user can edit a given subscription feed.
    3236   *
    3237   * @param mixed $subscriptionorid Subscription object or id
    3238   * @return bool true if current user can edit the subscription else false
    3239   */
    3240  function calendar_can_edit_subscription($subscriptionorid) {
    3241      global $USER;
    3242      if (is_array($subscriptionorid)) {
    3243          $subscription = (object)$subscriptionorid;
    3244      } else if (is_object($subscriptionorid)) {
    3245          $subscription = $subscriptionorid;
    3246      } else {
    3247          $subscription = calendar_get_subscription($subscriptionorid);
    3248      }
    3249  
    3250      $allowed = new \stdClass;
    3251      $courseid = $subscription->courseid;
    3252      $categoryid = $subscription->categoryid;
    3253      $groupid = $subscription->groupid;
    3254      $category = null;
    3255  
    3256      if (!empty($categoryid)) {
    3257          $category = \core_course_category::get($categoryid);
    3258      }
    3259      calendar_get_allowed_types($allowed, $courseid, null, $category);
    3260      switch ($subscription->eventtype) {
    3261          case 'user':
    3262              return ($USER->id == $subscription->userid && $allowed->user);
    3263          case 'course':
    3264              if (isset($allowed->courses[$courseid])) {
    3265                  return $allowed->courses[$courseid];
    3266              } else {
    3267                  return false;
    3268              }
    3269          case 'category':
    3270              if (isset($allowed->categories[$categoryid])) {
    3271                  return $allowed->categories[$categoryid];
    3272              } else {
    3273                  return false;
    3274              }
    3275          case 'site':
    3276              return $allowed->site;
    3277          case 'group':
    3278              if (isset($allowed->groups[$groupid])) {
    3279                  return $allowed->groups[$groupid];
    3280              } else {
    3281                  return false;
    3282              }
    3283          default:
    3284              return false;
    3285      }
    3286  }
    3287  
    3288  /**
    3289   * Helper function to determine the context of a calendar subscription.
    3290   * Subscriptions can be created in two contexts COURSE, or USER.
    3291   *
    3292   * @param stdClass $subscription
    3293   * @return context instance
    3294   */
    3295  function calendar_get_calendar_context($subscription) {
    3296      // Determine context based on calendar type.
    3297      if ($subscription->eventtype === 'site') {
    3298          $context = \context_course::instance(SITEID);
    3299      } else if ($subscription->eventtype === 'group' || $subscription->eventtype === 'course') {
    3300          $context = \context_course::instance($subscription->courseid);
    3301      } else {
    3302          $context = \context_user::instance($subscription->userid);
    3303      }
    3304      return $context;
    3305  }
    3306  
    3307  /**
    3308   * Implements callback user_preferences, lists preferences that users are allowed to update directly
    3309   *
    3310   * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
    3311   *
    3312   * @return array
    3313   */
    3314  function core_calendar_user_preferences() {
    3315      $preferences = [];
    3316      $preferences['calendar_timeformat'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED, 'default' => '0',
    3317          'choices' => array('0', CALENDAR_TF_12, CALENDAR_TF_24)
    3318      );
    3319      $preferences['calendar_startwday'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
    3320          'choices' => array(0, 1, 2, 3, 4, 5, 6));
    3321      $preferences['calendar_maxevents'] = array('type' => PARAM_INT, 'choices' => range(1, 20));
    3322      $preferences['calendar_lookahead'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 365,
    3323          'choices' => array(365, 270, 180, 150, 120, 90, 60, 30, 21, 14, 7, 6, 5, 4, 3, 2, 1));
    3324      $preferences['calendar_persistflt'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
    3325          'choices' => array(0, 1));
    3326      return $preferences;
    3327  }
    3328  
    3329  /**
    3330   * Get legacy calendar events
    3331   *
    3332   * @param int $tstart Start time of time range for events
    3333   * @param int $tend End time of time range for events
    3334   * @param array|int|boolean $users array of users, user id or boolean for all/no user events
    3335   * @param array|int|boolean $groups array of groups, group id or boolean for all/no group events
    3336   * @param array|int|boolean $courses array of courses, course id or boolean for all/no course events
    3337   * @param boolean $withduration whether only events starting within time range selected
    3338   *                              or events in progress/already started selected as well
    3339   * @param boolean $ignorehidden whether to select only visible events or all events
    3340   * @param array $categories array of category ids and/or objects.
    3341   * @param int $limitnum Number of events to fetch or zero to fetch all.
    3342   *
    3343   * @return array $events of selected events or an empty array if there aren't any (or there was an error)
    3344   */
    3345  function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses,
    3346          $withduration = true, $ignorehidden = true, $categories = [], $limitnum = 0) {
    3347      // Normalise the users, groups and courses parameters so that they are compliant with \core_calendar\local\api::get_events().
    3348      // Existing functions that were using the old calendar_get_events() were passing a mixture of array, int, boolean for these
    3349      // parameters, but with the new API method, only null and arrays are accepted.
    3350      list($userparam, $groupparam, $courseparam, $categoryparam) = array_map(function($param) {
    3351          // If parameter is true, return null.
    3352          if ($param === true) {
    3353              return null;
    3354          }
    3355  
    3356          // If parameter is false, return an empty array.
    3357          if ($param === false) {
    3358              return [];
    3359          }
    3360  
    3361          // If the parameter is a scalar value, enclose it in an array.
    3362          if (!is_array($param)) {
    3363              return [$param];
    3364          }
    3365  
    3366          // No normalisation required.
    3367          return $param;
    3368      }, [$users, $groups, $courses, $categories]);
    3369  
    3370      // If a single user is provided, we can use that for capability checks.
    3371      // Otherwise current logged in user is used - See MDL-58768.
    3372      if (is_array($userparam) && count($userparam) == 1) {
    3373          \core_calendar\local\event\container::set_requesting_user($userparam[0]);
    3374      }
    3375      $mapper = \core_calendar\local\event\container::get_event_mapper();
    3376      $events = \core_calendar\local\api::get_events(
    3377          $tstart,
    3378          $tend,
    3379          null,
    3380          null,
    3381          null,
    3382          null,
    3383          $limitnum,
    3384          null,
    3385          $userparam,
    3386          $groupparam,
    3387          $courseparam,
    3388          $categoryparam,
    3389          $withduration,
    3390          $ignorehidden
    3391      );
    3392  
    3393      return array_reduce($events, function($carry, $event) use ($mapper) {
    3394          return $carry + [$event->get_id() => $mapper->from_event_to_stdclass($event)];
    3395      }, []);
    3396  }
    3397  
    3398  
    3399  /**
    3400   * Get the calendar view output.
    3401   *
    3402   * @param   \calendar_information $calendar The calendar being represented
    3403   * @param   string  $view The type of calendar to have displayed
    3404   * @param   bool    $includenavigation Whether to include navigation
    3405   * @param   bool    $skipevents Whether to load the events or not
    3406   * @param   int     $lookahead Overwrites site and users's lookahead setting.
    3407   * @return  array[array, string]
    3408   */
    3409  function calendar_get_view(\calendar_information $calendar, $view, $includenavigation = true, bool $skipevents = false,
    3410          ?int $lookahead = null) {
    3411      global $PAGE, $CFG;
    3412  
    3413      $renderer = $PAGE->get_renderer('core_calendar');
    3414      $type = \core_calendar\type_factory::get_calendar_instance();
    3415  
    3416      // Calculate the bounds of the month.
    3417      $calendardate = $type->timestamp_to_date_array($calendar->time);
    3418  
    3419      $date = new \DateTime('now', core_date::get_user_timezone_object(99));
    3420      $eventlimit = 0;
    3421  
    3422      if ($view === 'day') {
    3423          $tstart = $type->convert_to_timestamp($calendardate['year'], $calendardate['mon'], $calendardate['mday']);
    3424          $date->setTimestamp($tstart);
    3425          $date->modify('+1 day');
    3426      } else if ($view === 'upcoming' || $view === 'upcoming_mini') {
    3427          // Number of days in the future that will be used to fetch events.
    3428          if (!$lookahead) {
    3429              if (isset($CFG->calendar_lookahead)) {
    3430                  $defaultlookahead = intval($CFG->calendar_lookahead);
    3431              } else {
    3432                  $defaultlookahead = CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD;
    3433              }
    3434              $lookahead = get_user_preferences('calendar_lookahead', $defaultlookahead);
    3435          }
    3436  
    3437          // Maximum number of events to be displayed on upcoming view.
    3438          $defaultmaxevents = CALENDAR_DEFAULT_UPCOMING_MAXEVENTS;
    3439          if (isset($CFG->calendar_maxevents)) {
    3440              $defaultmaxevents = intval($CFG->calendar_maxevents);
    3441          }
    3442          $eventlimit = get_user_preferences('calendar_maxevents', $defaultmaxevents);
    3443  
    3444          $tstart = $type->convert_to_timestamp($calendardate['year'], $calendardate['mon'], $calendardate['mday'],
    3445                  $calendardate['hours']);
    3446          $date->setTimestamp($tstart);
    3447          $date->modify('+' . $lookahead . ' days');
    3448      } else {
    3449          $tstart = $type->convert_to_timestamp($calendardate['year'], $calendardate['mon'], 1);
    3450          $monthdays = $type->get_num_days_in_month($calendardate['year'], $calendardate['mon']);
    3451          $date->setTimestamp($tstart);
    3452          $date->modify('+' . $monthdays . ' days');
    3453  
    3454          if ($view === 'mini' || $view === 'minithree') {
    3455              $template = 'core_calendar/calendar_mini';
    3456          } else {
    3457              $template = 'core_calendar/calendar_month';
    3458          }
    3459      }
    3460  
    3461      // We need to extract 1 second to ensure that we don't get into the next day.
    3462      $date->modify('-1 second');
    3463      $tend = $date->getTimestamp();
    3464  
    3465      list($userparam, $groupparam, $courseparam, $categoryparam) = array_map(function($param) {
    3466          // If parameter is true, return null.
    3467          if ($param === true) {
    3468              return null;
    3469          }
    3470  
    3471          // If parameter is false, return an empty array.
    3472          if ($param === false) {
    3473              return [];
    3474          }
    3475  
    3476          // If the parameter is a scalar value, enclose it in an array.
    3477          if (!is_array($param)) {
    3478              return [$param];
    3479          }
    3480  
    3481          // No normalisation required.
    3482          return $param;
    3483      }, [$calendar->users, $calendar->groups, $calendar->courses, $calendar->categories]);
    3484  
    3485      if ($skipevents) {
    3486          $events = [];
    3487      } else {
    3488          $events = \core_calendar\local\api::get_events(
    3489              $tstart,
    3490              $tend,
    3491              null,
    3492              null,
    3493              null,
    3494              null,
    3495              $eventlimit,
    3496              null,
    3497              $userparam,
    3498              $groupparam,
    3499              $courseparam,
    3500              $categoryparam,
    3501              true,
    3502              true,
    3503              function ($event) {
    3504                  if ($proxy = $event->get_course_module()) {
    3505                      $cminfo = $proxy->get_proxied_instance();
    3506                      return $cminfo->uservisible;
    3507                  }
    3508  
    3509                  if ($proxy = $event->get_category()) {
    3510                      $category = $proxy->get_proxied_instance();
    3511  
    3512                      return $category->is_uservisible();
    3513                  }
    3514  
    3515                  return true;
    3516              }
    3517          );
    3518      }
    3519  
    3520      $related = [
    3521          'events' => $events,
    3522          'cache' => new \core_calendar\external\events_related_objects_cache($events),
    3523          'type' => $type,
    3524      ];
    3525  
    3526      $data = [];
    3527      if ($view == "month" || $view == "mini" || $view == "minithree") {
    3528          $month = new \core_calendar\external\month_exporter($calendar, $type, $related);
    3529          $month->set_includenavigation($includenavigation);
    3530          $month->set_initialeventsloaded(!$skipevents);
    3531          $month->set_showcoursefilter($view == "month");
    3532          $data = $month->export($renderer);
    3533          $data->viewingmonth = true;
    3534      } else if ($view == "day") {
    3535          $day = new \core_calendar\external\calendar_day_exporter($calendar, $related);
    3536          $data = $day->export($renderer);
    3537          $data->viewingday = true;
    3538          $template = 'core_calendar/calendar_day';
    3539      } else if ($view == "upcoming" || $view == "upcoming_mini") {
    3540          $upcoming = new \core_calendar\external\calendar_upcoming_exporter($calendar, $related);
    3541          $data = $upcoming->export($renderer);
    3542  
    3543          if ($view == "upcoming") {
    3544              $template = 'core_calendar/calendar_upcoming';
    3545              $data->viewingupcoming = true;
    3546          } else if ($view == "upcoming_mini") {
    3547              $template = 'core_calendar/calendar_upcoming_mini';
    3548          }
    3549      }
    3550  
    3551      return [$data, $template];
    3552  }
    3553  
    3554  /**
    3555   * Request and render event form fragment.
    3556   *
    3557   * @param array $args The fragment arguments.
    3558   * @return string The rendered mform fragment.
    3559   */
    3560  function calendar_output_fragment_event_form($args) {
    3561      global $CFG, $OUTPUT, $USER;
    3562      require_once($CFG->libdir . '/grouplib.php');
    3563      $html = '';
    3564      $data = [];
    3565      $eventid = isset($args['eventid']) ? clean_param($args['eventid'], PARAM_INT) : null;
    3566      $starttime = isset($args['starttime']) ? clean_param($args['starttime'], PARAM_INT) : null;
    3567      $courseid = (isset($args['courseid']) && $args['courseid'] != SITEID) ? clean_param($args['courseid'], PARAM_INT) : null;
    3568      $categoryid = isset($args['categoryid']) ? clean_param($args['categoryid'], PARAM_INT) : null;
    3569      $event = null;
    3570      $hasformdata = isset($args['formdata']) && !empty($args['formdata']);
    3571      $context = \context_user::instance($USER->id);
    3572      $editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
    3573      $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
    3574      $draftitemid = 0;
    3575  
    3576      if ($hasformdata) {
    3577          parse_str(clean_param($args['formdata'], PARAM_TEXT), $data);
    3578          if (isset($data['description']['itemid'])) {
    3579              $draftitemid = $data['description']['itemid'];
    3580          }
    3581      }
    3582  
    3583      if ($starttime) {
    3584          $formoptions['starttime'] = $starttime;
    3585      }
    3586      // Let's check first which event types user can add.
    3587      $eventtypes = calendar_get_allowed_event_types($courseid);
    3588      $formoptions['eventtypes'] = $eventtypes;
    3589  
    3590      if (is_null($eventid)) {
    3591          if (!empty($courseid)) {
    3592              $groupcoursedata = groups_get_course_data($courseid);
    3593              $formoptions['groups'] = [];
    3594              foreach ($groupcoursedata->groups as $groupid => $groupdata) {
    3595                  $formoptions['groups'][$groupid] = $groupdata->name;
    3596              }
    3597          }
    3598  
    3599          $mform = new \core_calendar\local\event\forms\create(
    3600              null,
    3601              $formoptions,
    3602              'post',
    3603              '',
    3604              null,
    3605              true,
    3606              $data
    3607          );
    3608  
    3609          // If the user is on course context and is allowed to add course events set the event type default to course.
    3610          if (!empty($courseid) && !empty($eventtypes['course'])) {
    3611              $data['eventtype'] = 'course';
    3612              $data['courseid'] = $courseid;
    3613              $data['groupcourseid'] = $courseid;
    3614          } else if (!empty($categoryid) && !empty($eventtypes['category'])) {
    3615              $data['eventtype'] = 'category';
    3616              $data['categoryid'] = $categoryid;
    3617          } else if (!empty($groupcoursedata) && !empty($eventtypes['group'])) {
    3618              $data['groupcourseid'] = $courseid;
    3619              $data['groups'] = $groupcoursedata->groups;
    3620          }
    3621          $mform->set_data($data);
    3622      } else {
    3623          $event = calendar_event::load($eventid);
    3624  
    3625          if (!calendar_edit_event_allowed($event)) {
    3626              print_error('nopermissiontoupdatecalendar');
    3627          }
    3628  
    3629          $mapper = new \core_calendar\local\event\mappers\create_update_form_mapper();
    3630          $eventdata = $mapper->from_legacy_event_to_data($event);
    3631          $data = array_merge((array) $eventdata, $data);
    3632          $event->count_repeats();
    3633          $formoptions['event'] = $event;
    3634  
    3635          if (!empty($event->courseid)) {
    3636              $groupcoursedata = groups_get_course_data($event->courseid);
    3637              $formoptions['groups'] = [];
    3638              foreach ($groupcoursedata->groups as $groupid => $groupdata) {
    3639                  $formoptions['groups'][$groupid] = $groupdata->name;
    3640              }
    3641          }
    3642  
    3643          $data['description']['text'] = file_prepare_draft_area(
    3644              $draftitemid,
    3645              $event->context->id,
    3646              'calendar',
    3647              'event_description',
    3648              $event->id,
    3649              null,
    3650              $data['description']['text']
    3651          );
    3652          $data['description']['itemid'] = $draftitemid;
    3653  
    3654          $mform = new \core_calendar\local\event\forms\update(
    3655              null,
    3656              $formoptions,
    3657              'post',
    3658              '',
    3659              null,
    3660              true,
    3661              $data
    3662          );
    3663          $mform->set_data($data);
    3664  
    3665          // Check to see if this event is part of a subscription or import.
    3666          // If so display a warning on edit.
    3667          if (isset($event->subscriptionid) && ($event->subscriptionid != null)) {
    3668              $renderable = new \core\output\notification(
    3669                  get_string('eventsubscriptioneditwarning', 'calendar'),
    3670                  \core\output\notification::NOTIFY_INFO
    3671              );
    3672  
    3673              $html .= $OUTPUT->render($renderable);
    3674          }
    3675      }
    3676  
    3677      if ($hasformdata) {
    3678          $mform->is_validated();
    3679      }
    3680  
    3681      $html .= $mform->render();
    3682      return $html;
    3683  }
    3684  
    3685  /**
    3686   * Calculate the timestamp from the supplied Gregorian Year, Month, and Day.
    3687   *
    3688   * @param   int     $d The day
    3689   * @param   int     $m The month
    3690   * @param   int     $y The year
    3691   * @param   int     $time The timestamp to use instead of a separate y/m/d.
    3692   * @return  int     The timestamp
    3693   */
    3694  function calendar_get_timestamp($d, $m, $y, $time = 0) {
    3695      // If a day, month and year were passed then convert it to a timestamp. If these were passed
    3696      // then we can assume the day, month and year are passed as Gregorian, as no where in core
    3697      // should we be passing these values rather than the time.
    3698      if (!empty($d) && !empty($m) && !empty($y)) {
    3699          if (checkdate($m, $d, $y)) {
    3700              $time = make_timestamp($y, $m, $d);
    3701          } else {
    3702              $time = time();
    3703          }
    3704      } else if (empty($time)) {
    3705          $time = time();
    3706      }
    3707  
    3708      return $time;
    3709  }
    3710  
    3711  /**
    3712   * Get the calendar footer options.
    3713   *
    3714   * @param calendar_information $calendar The calendar information object.
    3715   * @return array The data for template and template name.
    3716   */
    3717  function calendar_get_footer_options($calendar) {
    3718      global $CFG, $USER, $PAGE;
    3719  
    3720      // Generate hash for iCal link.
    3721      $authtoken = calendar_get_export_token($USER);
    3722  
    3723      $renderer = $PAGE->get_renderer('core_calendar');
    3724      $footer = new \core_calendar\external\footer_options_exporter($calendar, $USER->id, $authtoken);
    3725      $data = $footer->export($renderer);
    3726      $template = 'core_calendar/footer_options';
    3727  
    3728      return [$data, $template];
    3729  }
    3730  
    3731  /**
    3732   * Get the list of potential calendar filter types as a type => name
    3733   * combination.
    3734   *
    3735   * @return array
    3736   */
    3737  function calendar_get_filter_types() {
    3738      $types = [
    3739          'site',
    3740          'category',
    3741          'course',
    3742          'group',
    3743          'user',
    3744          'other'
    3745      ];
    3746  
    3747      return array_map(function($type) {
    3748          return [
    3749              'eventtype' => $type,
    3750              'name' => get_string("eventtype{$type}", "calendar"),
    3751              'icon' => true,
    3752              'key' => 'i/' . $type . 'event',
    3753              'component' => 'core'
    3754          ];
    3755      }, $types);
    3756  }
    3757  
    3758  /**
    3759   * Check whether the specified event type is valid.
    3760   *
    3761   * @param string $type
    3762   * @return bool
    3763   */
    3764  function calendar_is_valid_eventtype($type) {
    3765      $validtypes = [
    3766          'user',
    3767          'group',
    3768          'course',
    3769          'category',
    3770          'site',
    3771      ];
    3772      return in_array($type, $validtypes);
    3773  }
    3774  
    3775  /**
    3776   * Get event types the user can create event based on categories, courses and groups
    3777   * the logged in user belongs to.
    3778   *
    3779   * @param int|null $courseid The course id.
    3780   * @return array The array of allowed types.
    3781   */
    3782  function calendar_get_allowed_event_types(int $courseid = null) {
    3783      global $DB,