Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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 the month view.
  19   *
  20   * @package   core_calendar
  21   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
  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\external\exporter;
  30  use renderer_base;
  31  use moodle_url;
  32  
  33  /**
  34   * Class for displaying the month view.
  35   *
  36   * @package   core_calendar
  37   * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
  38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class month_exporter extends exporter {
  41  
  42      /** @var int Number of calendar instances displayed. */
  43      protected static $calendarinstances = 0;
  44  
  45      /** @var int This calendar instance's ID. */
  46      protected $calendarinstanceid = 0;
  47  
  48      /**
  49       * @var \calendar_information $calendar The calendar to be rendered.
  50       */
  51      protected $calendar;
  52  
  53      /**
  54       * @var int $firstdayofweek The first day of the week.
  55       */
  56      protected $firstdayofweek;
  57  
  58      /**
  59       * @var moodle_url $url The URL for the events page.
  60       */
  61      protected $url;
  62  
  63      /**
  64       * @var bool $includenavigation Whether navigation should be included on the output.
  65       */
  66      protected $includenavigation = true;
  67  
  68      /**
  69       * @var bool $initialeventsloaded Whether the events have been loaded for this month.
  70       */
  71      protected $initialeventsloaded = true;
  72  
  73      /**
  74       * @var bool $showcoursefilter Whether to render the course filter selector as well.
  75       */
  76      protected $showcoursefilter = false;
  77  
  78      /**
  79       * Constructor for month_exporter.
  80       *
  81       * @param \calendar_information $calendar The calendar being represented
  82       * @param \core_calendar\type_base $type The calendar type (e.g. Gregorian)
  83       * @param array $related The related information
  84       */
  85      public function __construct(\calendar_information $calendar, \core_calendar\type_base $type, $related) {
  86          // Increment the calendar instances count on initialisation.
  87          self::$calendarinstances++;
  88          // Assign this instance an ID based on the latest calendar instances count.
  89          $this->calendarinstanceid = self::$calendarinstances;
  90          $this->calendar = $calendar;
  91          $this->firstdayofweek = $type->get_starting_weekday();
  92  
  93          $this->url = new moodle_url('/calendar/view.php', [
  94                  'view' => 'month',
  95                  'time' => $calendar->time,
  96              ]);
  97  
  98          if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
  99              $this->url->param('course', $this->calendar->course->id);
 100          } else if ($this->calendar->categoryid) {
 101              $this->url->param('category', $this->calendar->categoryid);
 102          }
 103  
 104          $related['type'] = $type;
 105  
 106          $data = [
 107              'url' => $this->url->out(false),
 108          ];
 109  
 110          parent::__construct($data, $related);
 111      }
 112  
 113      protected static function define_properties() {
 114          return [
 115              'url' => [
 116                  'type' => PARAM_URL,
 117              ],
 118          ];
 119      }
 120  
 121      /**
 122       * Return the list of additional properties.
 123       *
 124       * @return array
 125       */
 126      protected static function define_other_properties() {
 127          return [
 128              'courseid' => [
 129                  'type' => PARAM_INT,
 130              ],
 131              'categoryid' => [
 132                  'type' => PARAM_INT,
 133                  'optional' => true,
 134                  'default' => 0,
 135              ],
 136              'filter_selector' => [
 137                  'type' => PARAM_RAW,
 138                  'optional' => true,
 139              ],
 140              'weeks' => [
 141                  'type' => week_exporter::read_properties_definition(),
 142                  'multiple' => true,
 143              ],
 144              'daynames' => [
 145                  'type' => day_name_exporter::read_properties_definition(),
 146                  'multiple' => true,
 147              ],
 148              'view' => [
 149                  'type' => PARAM_ALPHA,
 150              ],
 151              'date' => [
 152                  'type' => date_exporter::read_properties_definition(),
 153              ],
 154              'periodname' => [
 155                  // Note: We must use RAW here because the calendar type returns the formatted month name based on a
 156                  // calendar format.
 157                  'type' => PARAM_RAW,
 158              ],
 159              'includenavigation' => [
 160                  'type' => PARAM_BOOL,
 161                  'default' => true,
 162              ],
 163              // Tracks whether the first set of events have been loaded and provided
 164              // to the exporter.
 165              'initialeventsloaded' => [
 166                  'type' => PARAM_BOOL,
 167                  'default' => true,
 168              ],
 169              'previousperiod' => [
 170                  'type' => date_exporter::read_properties_definition(),
 171              ],
 172              'previousperiodlink' => [
 173                  'type' => PARAM_URL,
 174              ],
 175              'previousperiodname' => [
 176                  // Note: We must use RAW here because the calendar type returns the formatted month name based on a
 177                  // calendar format.
 178                  'type' => PARAM_RAW,
 179              ],
 180              'nextperiod' => [
 181                  'type' => date_exporter::read_properties_definition(),
 182              ],
 183              'nextperiodname' => [
 184                  // Note: We must use RAW here because the calendar type returns the formatted month name based on a
 185                  // calendar format.
 186                  'type' => PARAM_RAW,
 187              ],
 188              'nextperiodlink' => [
 189                  'type' => PARAM_URL,
 190              ],
 191              'larrow' => [
 192                  // The left arrow defined by the theme.
 193                  'type' => PARAM_RAW,
 194              ],
 195              'rarrow' => [
 196                  // The right arrow defined by the theme.
 197                  'type' => PARAM_RAW,
 198              ],
 199              'defaulteventcontext' => [
 200                  'type' => PARAM_INT,
 201                  'default' => 0,
 202              ],
 203              'calendarinstanceid' => [
 204                  'type' => PARAM_INT,
 205                  'default' => 0,
 206              ],
 207              'viewingmonth' => [
 208                  'type' => PARAM_BOOL,
 209                  'default' => true,
 210              ],
 211              'showviewselector' => [
 212                  'type' => PARAM_BOOL,
 213                  'default' => true,
 214              ],
 215              'viewinginblock' => [
 216                  'type' => PARAM_BOOL,
 217                  'default' => false,
 218              ],
 219          ];
 220      }
 221  
 222      /**
 223       * Get the additional values to inject while exporting.
 224       *
 225       * @param renderer_base $output The renderer.
 226       * @return array Keys are the property names, values are their values.
 227       */
 228      protected function get_other_values(renderer_base $output) {
 229          $previousperiod = $this->get_previous_month_data();
 230          $nextperiod = $this->get_next_month_data();
 231          $date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
 232  
 233          $nextperiodlink = new moodle_url($this->url);
 234          $nextperiodlink->param('time', $nextperiod[0]);
 235  
 236          $previousperiodlink = new moodle_url($this->url);
 237          $previousperiodlink->param('time', $previousperiod[0]);
 238  
 239          $viewmode = $this->calendar->get_viewmode() ?? 'month';
 240  
 241          $return = [
 242              'courseid' => $this->calendar->courseid,
 243              'weeks' => $this->get_weeks($output),
 244              'daynames' => $this->get_day_names($output),
 245              'view' => $viewmode,
 246              'date' => (new date_exporter($date))->export($output),
 247              'periodname' => userdate($this->calendar->time, get_string('strftimemonthyear')),
 248              'previousperiod' => (new date_exporter($previousperiod))->export($output),
 249              'previousperiodname' => userdate($previousperiod[0], get_string('strftimemonth')),
 250              'previousperiodlink' => $previousperiodlink->out(false),
 251              'nextperiod' => (new date_exporter($nextperiod))->export($output),
 252              'nextperiodname' => userdate($nextperiod[0], get_string('strftimemonth')),
 253              'nextperiodlink' => $nextperiodlink->out(false),
 254              'larrow' => $output->larrow(),
 255              'rarrow' => $output->rarrow(),
 256              'includenavigation' => $this->includenavigation,
 257              'initialeventsloaded' => $this->initialeventsloaded,
 258              'calendarinstanceid' => $this->calendarinstanceid,
 259              'showviewselector' => $viewmode === 'month',
 260              'viewinginblock' => $viewmode === 'monthblock',
 261          ];
 262  
 263          if ($this->showcoursefilter) {
 264              $return['filter_selector'] = $this->get_course_filter_selector($output);
 265          }
 266  
 267          if ($context = $this->get_default_add_context()) {
 268              $return['defaulteventcontext'] = $context->id;
 269          }
 270  
 271          if ($this->calendar->categoryid) {
 272              $return['categoryid'] = $this->calendar->categoryid;
 273          }
 274  
 275          return $return;
 276      }
 277  
 278      /**
 279       * Get the course filter selector.
 280       *
 281       * @param renderer_base $output
 282       * @return string The html code for the course filter selector.
 283       */
 284      protected function get_course_filter_selector(renderer_base $output) {
 285          $content = '';
 286          $content .= $output->course_filter_selector($this->url, '', $this->calendar->course->id, $this->calendarinstanceid);
 287  
 288          return $content;
 289      }
 290  
 291      /**
 292       * Get the list of day names for display, re-ordered from the first day
 293       * of the week.
 294       *
 295       * @param   renderer_base $output
 296       * @return  day_name_exporter[]
 297       */
 298      protected function get_day_names(renderer_base $output) {
 299          $weekdays = $this->related['type']->get_weekdays();
 300          $daysinweek = count($weekdays);
 301  
 302          $daynames = [];
 303          for ($i = 0; $i < $daysinweek; $i++) {
 304              // Bump the currentdayno and ensure it loops.
 305              $dayno = ($i + $this->firstdayofweek + $daysinweek) % $daysinweek;
 306              $dayname = new day_name_exporter($dayno, $weekdays[$dayno]);
 307              $daynames[] = $dayname->export($output);
 308          }
 309  
 310          return $daynames;
 311      }
 312  
 313      /**
 314       * Get the list of week days, ordered into weeks and padded according
 315       * to the value of the first day of the week.
 316       *
 317       * @param renderer_base $output
 318       * @return array The list of weeks.
 319       */
 320      protected function get_weeks(renderer_base $output) {
 321          $weeks = [];
 322          $alldays = $this->get_days();
 323  
 324          $daysinweek = count($this->related['type']->get_weekdays());
 325  
 326          // Calculate which day number is the first, and last day of the week.
 327          $firstdayofweek = $this->firstdayofweek;
 328  
 329          // The first week is special as it may have padding at the beginning.
 330          $day = reset($alldays);
 331          $firstdayno = $day['wday'];
 332  
 333          $prepadding = ($firstdayno + $daysinweek - $firstdayofweek) % $daysinweek;
 334          $daysinfirstweek = $daysinweek - $prepadding;
 335          $days = array_slice($alldays, 0, $daysinfirstweek);
 336          $week = new week_exporter($this->calendar, $days, $prepadding, ($daysinweek - count($days) - $prepadding), $this->related);
 337          $weeks[] = $week->export($output);
 338  
 339          // Now chunk up the remaining day. and turn them into weeks.
 340          $daychunks = array_chunk(array_slice($alldays, $daysinfirstweek), $daysinweek);
 341          foreach ($daychunks as $days) {
 342              $week = new week_exporter($this->calendar, $days, 0, ($daysinweek - count($days)), $this->related);
 343              $weeks[] = $week->export($output);
 344          }
 345  
 346          return $weeks;
 347      }
 348  
 349      /**
 350       * Get the list of days with the matching date array.
 351       *
 352       * @return array
 353       */
 354      protected function get_days() {
 355          $date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
 356          $monthdays = $this->related['type']->get_num_days_in_month($date['year'], $date['mon']);
 357  
 358          $days = [];
 359          for ($dayno = 1; $dayno <= $monthdays; $dayno++) {
 360              // Get the gregorian representation of the day.
 361              $timestamp = $this->related['type']->convert_to_timestamp($date['year'], $date['mon'], $dayno);
 362  
 363              $days[] = $this->related['type']->timestamp_to_date_array($timestamp);
 364          }
 365  
 366          return $days;
 367      }
 368  
 369      /**
 370       * Returns a list of objects that are related.
 371       *
 372       * @return array
 373       */
 374      protected static function define_related() {
 375          return [
 376              'events' => '\core_calendar\local\event\entities\event_interface[]',
 377              'cache' => '\core_calendar\external\events_related_objects_cache',
 378              'type' => '\core_calendar\type_base',
 379          ];
 380      }
 381  
 382      /**
 383       * Get the current month timestamp.
 384       *
 385       * @return int The month timestamp.
 386       */
 387      protected function get_month_data() {
 388          $date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
 389          $monthtime = $this->related['type']->convert_to_gregorian($date['year'], $date['month'], 1);
 390  
 391          return make_timestamp($monthtime['year'], $monthtime['month']);
 392      }
 393  
 394      /**
 395       * Get the previous month timestamp.
 396       *
 397       * @return int The previous month timestamp.
 398       */
 399      protected function get_previous_month_data() {
 400          $type = $this->related['type'];
 401          $date = $type->timestamp_to_date_array($this->calendar->time);
 402          list($date['mon'], $date['year']) = $type->get_prev_month($date['year'], $date['mon']);
 403          $time = $type->convert_to_timestamp($date['year'], $date['mon'], 1);
 404  
 405          return $type->timestamp_to_date_array($time);
 406      }
 407  
 408      /**
 409       * Get the next month timestamp.
 410       *
 411       * @return int The next month timestamp.
 412       */
 413      protected function get_next_month_data() {
 414          $type = $this->related['type'];
 415          $date = $type->timestamp_to_date_array($this->calendar->time);
 416          list($date['mon'], $date['year']) = $type->get_next_month($date['year'], $date['mon']);
 417          $time = $type->convert_to_timestamp($date['year'], $date['mon'], 1);
 418  
 419          return $type->timestamp_to_date_array($time);
 420      }
 421  
 422      /**
 423       * Set whether the navigation should be shown.
 424       *
 425       * @param   bool    $include
 426       * @return  $this
 427       */
 428      public function set_includenavigation($include) {
 429          $this->includenavigation = $include;
 430  
 431          return $this;
 432      }
 433  
 434      /**
 435       * Set whether the initial events have already been loaded and
 436       * provided to the exporter.
 437       *
 438       * @param   bool    $loaded
 439       * @return  $this
 440       */
 441      public function set_initialeventsloaded(bool $loaded) {
 442          $this->initialeventsloaded = $loaded;
 443  
 444          return $this;
 445      }
 446  
 447      /**
 448       * Set whether the course filter selector should be shown.
 449       *
 450       * @param   bool    $show
 451       * @return  $this
 452       */
 453      public function set_showcoursefilter(bool $show) {
 454          $this->showcoursefilter = $show;
 455  
 456          return $this;
 457      }
 458  
 459      /**
 460       * Get the default context for use when adding a new event.
 461       *
 462       * @return null|\context
 463       */
 464      protected function get_default_add_context() {
 465          if (calendar_user_can_add_event($this->calendar->course)) {
 466              return \context_course::instance($this->calendar->course->id);
 467          }
 468  
 469          return null;
 470      }
 471  }