Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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