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