Search moodle.org's
Developer Documentation

See Release Notes

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

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Contains event class for displaying a calendar event.
  19   *
  20   * @package   core_calendar
  21   * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_calendar\external;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use \core_calendar\local\event\container;
  30  use \renderer_base;
  31  require_once($CFG->dirroot . '/course/lib.php');
  32  /**
  33   * Class for displaying a calendar event.
  34   *
  35   * @package   core_calendar
  36   * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class calendar_event_exporter extends event_exporter_base {
  40  
  41      /**
  42       * Return the list of additional properties.
  43       *
  44       * @return array
  45       */
  46      protected static function define_other_properties() {
  47  
  48          $values = parent::define_other_properties();
  49          $values['url'] = ['type' => PARAM_URL];
  50          $values['islastday'] = [
  51              'type' => PARAM_BOOL,
  52              'default' => false,
  53          ];
  54          $values['popupname'] = [
  55              'type' => PARAM_RAW,
  56          ];
  57          $values['mindaytimestamp'] = [
  58              'type' => PARAM_INT,
  59              'optional' => true
  60          ];
  61          $values['mindayerror'] = [
  62              'type' => PARAM_TEXT,
  63              'optional' => true
  64          ];
  65          $values['maxdaytimestamp'] = [
  66              'type' => PARAM_INT,
  67              'optional' => true
  68          ];
  69          $values['maxdayerror'] = [
  70              'type' => PARAM_TEXT,
  71              'optional' => true
  72          ];
  73          $values['draggable'] = [
  74              'type' => PARAM_BOOL,
  75              'default' => false
  76          ];
  77  
  78          return $values;
  79      }
  80  
  81      /**
  82       * Get the additional values to inject while exporting.
  83       *
  84       * @param renderer_base $output The renderer.
  85       * @return array Keys are the property names, values are their values.
  86       */
  87      protected function get_other_values(renderer_base $output) {
  88          global $CFG;
  89  
  90          $values = parent::get_other_values($output);
  91          $event = $this->event;
  92          $course = $this->related['course'];
  93          $hascourse = !empty($course);
  94  
  95          // By default all events that can be edited are
  96          // draggable.
  97          $values['draggable'] = $values['canedit'];
  98  
  99          if ($moduleproxy = $event->get_course_module()) {
 100              $modulename = $moduleproxy->get('modname');
 101              $moduleid = $moduleproxy->get('id');
 102              $url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]);
 103  
 104              // Build edit event url for action events.
 105              $params = array('update' => $moduleid, 'return' => true, 'sesskey' => sesskey());
 106              $editurl = new \moodle_url('/course/mod.php', $params);
 107              $values['editurl'] = $editurl->out(false);
 108          } else if ($event->get_type() == 'category') {
 109              $url = $event->get_category()->get_proxied_instance()->get_view_link();
 110          } else {
 111              $url = course_get_url($hascourse ? $course : SITEID);
 112          }
 113  
 114          $values['url'] = $url->out(false);
 115          $values['islastday'] = false;
 116          $today = $this->related['type']->timestamp_to_date_array($this->related['today']);
 117  
 118          if ($hascourse) {
 119              $values['popupname'] = \core_external\util::format_string(
 120                  $this->event->get_name(),
 121                  \context_course::instance($course->id),
 122                  true
 123              );
 124          } else {
 125              $values['popupname'] = \core_external\util::format_string($this->event->get_name(), \context_system::instance(), true);
 126          }
 127  
 128          $times = $this->event->get_times();
 129          if ($duration = $times->get_duration()) {
 130              $enddate = $this->related['type']->timestamp_to_date_array($times->get_end_time()->getTimestamp());
 131              $values['islastday'] = true;
 132              $values['islastday'] = $values['islastday'] && $enddate['year'] == $today['year'];
 133              $values['islastday'] = $values['islastday'] && $enddate['mon'] == $today['mon'];
 134              $values['islastday'] = $values['islastday'] && $enddate['mday'] == $today['mday'];
 135          }
 136  
 137          $subscription = $this->event->get_subscription();
 138          if ($subscription && !empty($subscription->get('id')) && $CFG->calendar_showicalsource) {
 139              $a = (object) [
 140                  'name' => $values['popupname'],
 141                  'source' => $subscription->get('name'),
 142              ];
 143              $values['popupname'] = get_string('namewithsource', 'calendar', $a);
 144          } else {
 145              if ($values['islastday']) {
 146                  $startdate = $this->related['type']->timestamp_to_date_array($times->get_start_time()->getTimestamp());
 147                  $samedate = true;
 148                  $samedate = $samedate && $startdate['mon'] == $enddate['mon'];
 149                  $samedate = $samedate && $startdate['year'] == $enddate['year'];
 150                  $samedate = $samedate && $startdate['mday'] == $enddate['mday'];
 151  
 152                  if (!$samedate) {
 153                      $values['popupname'] = get_string('eventendtimewrapped', 'calendar', $values['popupname']);
 154                  }
 155              }
 156          }
 157  
 158          // Include category name into the event name, if applicable.
 159          $proxy = $this->event->get_category();
 160          if ($proxy && $proxy->get('id')) {
 161              $category = $proxy->get_proxied_instance();
 162              $eventnameparams = (object) [
 163                  'name' => $values['popupname'],
 164                  'category' => $category->get_formatted_name(),
 165              ];
 166              $values['popupname'] = get_string('eventnameandcategory', 'calendar', $eventnameparams);
 167          }
 168  
 169          // Include course's shortname into the event name, if applicable.
 170          if ($hascourse && $course->id !== SITEID) {
 171              $eventnameparams = (object) [
 172                  'name' => $values['popupname'],
 173                  'course' => $values['course']->shortname,
 174              ];
 175              $values['popupname'] = get_string('eventnameandcourse', 'calendar', $eventnameparams);
 176          }
 177  
 178          if ($event->get_course_module()) {
 179              $values = array_merge($values, $this->get_module_timestamp_limits($event));
 180          } else if ($hascourse && $course->id != SITEID && empty($event->get_group())) {
 181              // This is a course event.
 182              $values = array_merge($values, $this->get_course_timestamp_limits($event));
 183          }
 184  
 185          return $values;
 186      }
 187  
 188      /**
 189       * Returns a list of objects that are related.
 190       *
 191       * @return array
 192       */
 193      protected static function define_related() {
 194          $related = parent::define_related();
 195          $related['daylink'] = \moodle_url::class;
 196          $related['type'] = '\core_calendar\type_base';
 197          $related['today'] = 'int';
 198          $related['moduleinstance'] = 'stdClass?';
 199  
 200          return $related;
 201      }
 202  
 203      /**
 204       * Return the normalised event type.
 205       * Activity events are normalised to be course events.
 206       *
 207       * @return string
 208       */
 209      public function get_calendar_event_type() {
 210          if ($this->event->get_course_module()) {
 211              return 'course';
 212          }
 213  
 214          return $this->event->get_type();
 215      }
 216  
 217      /**
 218       * Return the set of minimum and maximum date timestamp values
 219       * for the given event.
 220       *
 221       * @param \core_calendar\local\event\entities\event_interface $event
 222       * @return array
 223       */
 224      protected function get_course_timestamp_limits($event) {
 225          $values = [];
 226          $mapper = container::get_event_mapper();
 227          $starttime = $event->get_times()->get_start_time();
 228  
 229          list($min, $max) = component_callback(
 230              'core_course',
 231              'core_calendar_get_valid_event_timestart_range',
 232              [$mapper->from_event_to_legacy_event($event), $event->get_course()->get_proxied_instance()],
 233              [false, false]
 234          );
 235  
 236          // The callback will return false for either of the
 237          // min or max cutoffs to indicate that there are no
 238          // valid timestart values. In which case the event is
 239          // not draggable.
 240          if ($min === false || $max === false) {
 241              return ['draggable' => false];
 242          }
 243  
 244          if ($min) {
 245              $values = array_merge($values, $this->get_timestamp_min_limit($starttime, $min));
 246          }
 247  
 248          if ($max) {
 249              $values = array_merge($values, $this->get_timestamp_max_limit($starttime, $max));
 250          }
 251  
 252          return $values;
 253      }
 254  
 255      /**
 256       * Return the set of minimum and maximum date timestamp values
 257       * for the given event.
 258       *
 259       * @param \core_calendar\local\event\entities\event_interface $event
 260       * @return array
 261       */
 262      protected function get_module_timestamp_limits($event) {
 263          $values = [];
 264          $mapper = container::get_event_mapper();
 265          $starttime = $event->get_times()->get_start_time();
 266          $modname = $event->get_course_module()->get('modname');
 267          $moduleinstance = $this->related['moduleinstance'];
 268  
 269          list($min, $max) = component_callback(
 270              'mod_' . $modname,
 271              'core_calendar_get_valid_event_timestart_range',
 272              [$mapper->from_event_to_legacy_event($event), $moduleinstance],
 273              [false, false]
 274          );
 275  
 276          // The callback will return false for either of the
 277          // min or max cutoffs to indicate that there are no
 278          // valid timestart values. In which case the event is
 279          // not draggable.
 280          if ($min === false || $max === false) {
 281              return ['draggable' => false];
 282          }
 283  
 284          if ($min) {
 285              $values = array_merge($values, $this->get_timestamp_min_limit($starttime, $min));
 286          }
 287  
 288          if ($max) {
 289              $values = array_merge($values, $this->get_timestamp_max_limit($starttime, $max));
 290          }
 291  
 292          return $values;
 293      }
 294  
 295      /**
 296       * Get the correct minimum midnight day limit based on the event start time
 297       * and the minimum timestamp limit of what the event belongs to.
 298       *
 299       * @param DateTimeInterface $starttime The event start time
 300       * @param array $min The module's minimum limit for the event
 301       * @return array Returns an array with mindaytimestamp and mindayerror keys.
 302       */
 303      protected function get_timestamp_min_limit(\DateTimeInterface $starttime, $min) {
 304          // We need to check that the minimum valid time is earlier in the
 305          // day than the current event time so that if the user drags and drops
 306          // the event to this day (which changes the date but not the time) it
 307          // will result in a valid time start for the event.
 308          //
 309          // For example:
 310          // An event that starts on 2017-01-10 08:00 with a minimum cutoff
 311          // of 2017-01-05 09:00 means that 2017-01-05 is not a valid start day
 312          // for the drag and drop because it would result in the event start time
 313          // being set to 2017-01-05 08:00, which is invalid. Instead the minimum
 314          // valid start day would be 2017-01-06.
 315          $values = [];
 316          $timestamp = $min[0];
 317          $errorstring = $min[1];
 318          $mindate = (new \DateTimeImmutable())->setTimestamp($timestamp);
 319          $minstart = $mindate->setTime(
 320              $starttime->format('H'),
 321              $starttime->format('i'),
 322              $starttime->format('s')
 323          );
 324          $midnight = usergetmidnight($timestamp);
 325  
 326          if ($mindate <= $minstart) {
 327              $values['mindaytimestamp'] = $midnight;
 328          } else {
 329              $tomorrow = (new \DateTime())->setTimestamp($midnight)->modify('+1 day');
 330              $values['mindaytimestamp'] = $tomorrow->getTimestamp();
 331          }
 332  
 333          // Get the human readable error message to display if the min day
 334          // timestamp is violated.
 335          $values['mindayerror'] = $errorstring;
 336          return $values;
 337      }
 338  
 339      /**
 340       * Get the correct maximum midnight day limit based on the event start time
 341       * and the maximum timestamp limit of what the event belongs to.
 342       *
 343       * @param DateTimeInterface $starttime The event start time
 344       * @param array $max The module's maximum limit for the event
 345       * @return array Returns an array with maxdaytimestamp and maxdayerror keys.
 346       */
 347      protected function get_timestamp_max_limit(\DateTimeInterface $starttime, $max) {
 348          // We're doing a similar calculation here as we are for the minimum
 349          // day timestamp. See the explanation above.
 350          $values = [];
 351          $timestamp = $max[0];
 352          $errorstring = $max[1];
 353          $maxdate = (new \DateTimeImmutable())->setTimestamp($timestamp);
 354          $maxstart = $maxdate->setTime(
 355              $starttime->format('H'),
 356              $starttime->format('i'),
 357              $starttime->format('s')
 358          );
 359          $midnight = usergetmidnight($timestamp);
 360  
 361          if ($maxdate >= $maxstart) {
 362              $values['maxdaytimestamp'] = $midnight;
 363          } else {
 364              $yesterday = (new \DateTime())->setTimestamp($midnight)->modify('-1 day');
 365              $values['maxdaytimestamp'] = $yesterday->getTimestamp();
 366          }
 367  
 368          // Get the human readable error message to display if the max day
 369          // timestamp is violated.
 370          $values['maxdayerror'] = $errorstring;
 371          return $values;
 372      }
 373  
 374      /**
 375       * Get the correct minimum midnight day limit based on the event start time
 376       * and the module's minimum timestamp limit.
 377       *
 378       * @deprecated since Moodle 3.6. Please use get_timestamp_min_limit().
 379       * @todo final deprecation. To be removed in Moodle 3.10
 380       * @param \DateTimeInterface $starttime The event start time
 381       * @param array $min The module's minimum limit for the event
 382       * @return array Returns an array with mindaytimestamp and mindayerror keys.
 383       */
 384      protected function get_module_timestamp_min_limit(\DateTimeInterface $starttime, $min) {
 385          debugging('get_module_timestamp_min_limit() has been deprecated. Please call get_timestamp_min_limit() instead.',
 386                  DEBUG_DEVELOPER);
 387          return $this->get_timestamp_min_limit($starttime, $min);
 388      }
 389  
 390      /**
 391       * Get the correct maximum midnight day limit based on the event start time
 392       * and the module's maximum timestamp limit.
 393       *
 394       * @deprecated since Moodle 3.6. Please use get_timestamp_max_limit().
 395       * @todo final deprecation. To be removed in Moodle 3.10
 396       * @param \DateTimeInterface $starttime The event start time
 397       * @param array $max The module's maximum limit for the event
 398       * @return array Returns an array with maxdaytimestamp and maxdayerror keys.
 399       */
 400      protected function get_module_timestamp_max_limit(\DateTimeInterface $starttime, $max) {
 401          debugging('get_module_timestamp_max_limit() has been deprecated. Please call get_timestamp_max_limit() instead.',
 402                  DEBUG_DEVELOPER);
 403          return $this->get_timestamp_max_limit($starttime, $max);
 404      }
 405  }