MDL-59750 core_calendar: better handling of event subscription
[moodle.git] / calendar / renderer.php
CommitLineData
36dc3b71
SH
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * This file contains the renderers for the calendar within Moodle
20 *
21 * @copyright 2010 Sam Hemelryk
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @package calendar
24 */
25
e30390a0
SH
26if (!defined('MOODLE_INTERNAL')) {
27 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
28}
29
36dc3b71
SH
30/**
31 * The primary renderer for the calendar.
32 */
33class core_calendar_renderer extends plugin_renderer_base {
34
36dc3b71
SH
35 /**
36 * Starts the standard layout for the page
6be8c6b7 37 *
36dc3b71
SH
38 * @return string
39 */
40 public function start_layout() {
0d38888e 41 return html_writer::start_tag('div', ['data-region' => 'calendar', 'class' => 'maincalendar']);
36dc3b71
SH
42 }
43
44 /**
45 * Creates the remainder of the layout
46 *
47 * @return string
48 */
49 public function complete_layout() {
50 return html_writer::end_tag('div');
51 }
52
36dc3b71
SH
53 /**
54 * Produces the content for the three months block (pretend block)
55 *
56 * This includes the previous month, the current month, and the next month
57 *
58 * @param calendar_information $calendar
59 * @return string
60 */
61 public function fake_block_threemonths(calendar_information $calendar) {
da304137
MN
62 // Get the calendar type we are using.
63 $calendartype = \core_calendar\type_factory::get_calendar_instance();
6038d626 64 $time = $calendartype->timestamp_to_date_array($calendar->time);
36dc3b71 65
6038d626
AN
66 $current = $calendar->time;
67 $prev = $calendartype->convert_to_timestamp(
68 $time['year'],
69 $time['mon'] - 1,
70 $time['mday']
71 );
72 $next = $calendartype->convert_to_timestamp(
73 $time['year'],
74 $time['mon'] + 1,
75 $time['mday']
76 );
77
78 $content = '';
79
80 // Previous.
81 $calendar->set_time($prev);
82 list($previousmonth, ) = calendar_get_view($calendar, 'minithree', false);
83
84 // Current month.
85 $calendar->set_time($current);
86 list($currentmonth, ) = calendar_get_view($calendar, 'minithree', false);
87
88 // Next month.
89 $calendar->set_time($next);
90 list($nextmonth, ) = calendar_get_view($calendar, 'minithree', false);
91
92 // Reset the time back.
93 $calendar->set_time($current);
94
95 $data = (object) [
96 'previousmonth' => $previousmonth,
97 'currentmonth' => $currentmonth,
98 'nextmonth' => $nextmonth,
99 ];
100
101 $template = 'core_calendar/calendar_threemonth';
102 $content .= $this->render_from_template($template, $data);
36dc3b71
SH
103 return $content;
104 }
105
106 /**
107 * Adds a pretent calendar block
108 *
109 * @param block_contents $bc
110 * @param mixed $pos BLOCK_POS_RIGHT | BLOCK_POS_LEFT
111 */
112 public function add_pretend_calendar_block(block_contents $bc, $pos=BLOCK_POS_RIGHT) {
d9c26e21 113 $this->page->blocks->add_fake_block($bc, $pos);
36dc3b71
SH
114 }
115
116 /**
117 * Creates a button to add a new event
118 *
119 * @param int $courseid
120 * @param int $day
121 * @param int $month
122 * @param int $year
da304137
MN
123 * @param int $time the unixtime, used for multiple calendar support. The values $day,
124 * $month and $year are kept for backwards compatibility.
36dc3b71
SH
125 * @return string
126 */
64ff737a 127 public function add_event_button($courseid, $day = null, $month = null, $year = null, $time = null) {
da304137
MN
128 // If a day, month and year were passed then convert it to a timestamp. If these were passed
129 // then we can assume the day, month and year are passed as Gregorian, as no where in core
130 // should we be passing these values rather than the time. This is done for BC.
131 if (!empty($day) && !empty($month) && !empty($year)) {
132 if (checkdate($month, $day, $year)) {
133 $time = make_timestamp($year, $month, $day);
134 } else {
135 $time = time();
136 }
137 } else if (empty($time)) {
138 $time = time();
139 }
140
aa091225
RW
141 $coursecontext = \context_course::instance($courseid);
142 $attributes = [
143 'class' => 'btn btn-secondary pull-xs-right pull-right',
144 'data-context-id' => $coursecontext->id,
145 'data-action' => 'new-event-button'
146 ];
147 return html_writer::tag('button', get_string('newevent', 'calendar'), $attributes);
36dc3b71
SH
148 }
149
150 /**
151 * Displays the calendar for a single day
152 *
153 * @param calendar_information $calendar
154 * @return string
155 */
797cedc7
SH
156 public function show_day(calendar_information $calendar, moodle_url $returnurl = null) {
157
158 if ($returnurl === null) {
159 $returnurl = $this->page->url;
160 }
161
23a29de7 162 $events = calendar_get_upcoming($calendar->courses, $calendar->groups, $calendar->users,
12cbce0a 163 1, 100, $calendar->timestamp_today());
36dc3b71
SH
164
165 $output = html_writer::start_tag('div', array('class'=>'header'));
712e5f22 166 $output .= $this->course_filter_selector($returnurl, get_string('dayviewfor', 'calendar'));
23a29de7 167 if (calendar_user_can_add_event($calendar->course)) {
da304137 168 $output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
36dc3b71 169 }
36dc3b71
SH
170 $output .= html_writer::end_tag('div');
171 // Controls
23a29de7 172 $output .= html_writer::tag('div', calendar_top_controls('day', array('id' => $calendar->courseid,
12cbce0a 173 'time' => $calendar->time)), array('class' => 'controls'));
36dc3b71
SH
174
175 if (empty($events)) {
176 // There is nothing to display today.
136f8f09 177 $output .= html_writer::span(get_string('daywithnoevents', 'calendar'), 'calendar-information calendar-no-results');
36dc3b71 178 } else {
11edf09c 179 $output .= html_writer::start_tag('div', array('class' => 'eventlist'));
36dc3b71
SH
180 $underway = array();
181 // First, print details about events that start today
182 foreach ($events as $event) {
e1cd93ce 183 $event = new calendar_event($event);
36dc3b71
SH
184 $event->calendarcourseid = $calendar->courseid;
185 if ($event->timestart >= $calendar->timestamp_today() && $event->timestart <= $calendar->timestamp_tomorrow()-1) { // Print it now
23a29de7 186 $event->time = calendar_format_event_time($event, time(), null, false,
12cbce0a 187 $calendar->timestamp_today());
36dc3b71
SH
188 $output .= $this->event($event);
189 } else { // Save this for later
190 $underway[] = $event;
191 }
192 }
193
194 // Then, show a list of all events that just span this day
195 if (!empty($underway)) {
136f8f09
BB
196 $output .= html_writer::span(get_string('spanningevents', 'calendar'),
197 'calendar-information calendar-span-multiple-days');
36dc3b71 198 foreach ($underway as $event) {
23a29de7 199 $event->time = calendar_format_event_time($event, time(), null, false,
12cbce0a 200 $calendar->timestamp_today());
36dc3b71
SH
201 $output .= $this->event($event);
202 }
203 }
204
205 $output .= html_writer::end_tag('div');
206 }
207
208 return $output;
209 }
210
211 /**
212 * Displays an event
213 *
e1cd93ce 214 * @param calendar_event $event
36dc3b71
SH
215 * @param bool $showactions
216 * @return string
217 */
e1cd93ce 218 public function event(calendar_event $event, $showactions=true) {
e73b527c
AA
219 global $CFG;
220
23a29de7 221 $event = calendar_add_event_metadata($event);
9984132c 222 $context = $event->context;
11edf09c 223 $output = '';
36dc3b71 224
7ee4a287 225 $output .= $this->output->box_start('card-header clearfix');
23a29de7 226 if (calendar_edit_event_allowed($event) && $showactions) {
436d39ba 227 if (calendar_delete_event_allowed($event)) {
48489fb9
DW
228 $editlink = new moodle_url(CALENDAR_URL.'event.php', array('action' => 'edit', 'id' => $event->id));
229 $deletelink = new moodle_url(CALENDAR_URL.'delete.php', array('id' => $event->id));
7ee4a287
DW
230 if (!empty($event->calendarcourseid)) {
231 $editlink->param('course', $event->calendarcourseid);
232 $deletelink->param('course', $event->calendarcourseid);
233 }
234 } else {
48489fb9
DW
235 $params = array('update' => $event->cmid, 'return' => true, 'sesskey' => sesskey());
236 $editlink = new moodle_url('/course/mod.php', $params);
7ee4a287
DW
237 $deletelink = null;
238 }
239
48489fb9
DW
240 $commands = html_writer::start_tag('div', array('class' => 'commands pull-xs-right'));
241 $commands .= html_writer::start_tag('a', array('href' => $editlink));
7ee4a287 242 $str = get_string('tt_editevent', 'calendar');
663640f5 243 $commands .= $this->output->pix_icon('t/edit', $str);
7ee4a287
DW
244 $commands .= html_writer::end_tag('a');
245 if ($deletelink != null) {
48489fb9 246 $commands .= html_writer::start_tag('a', array('href' => $deletelink));
7ee4a287 247 $str = get_string('tt_deleteevent', 'calendar');
663640f5 248 $commands .= $this->output->pix_icon('t/delete', $str);
7ee4a287
DW
249 $commands .= html_writer::end_tag('a');
250 }
251 $commands .= html_writer::end_tag('div');
252 $output .= $commands;
253 }
36dc3b71 254 if (!empty($event->icon)) {
11edf09c 255 $output .= $event->icon;
36dc3b71 256 } else {
11edf09c 257 $output .= $this->output->spacer(array('height' => 16, 'width' => 16));
36dc3b71 258 }
36dc3b71 259
36dc3b71 260 if (!empty($event->referer)) {
11edf09c 261 $output .= $this->output->heading($event->referer, 3, array('class' => 'referer'));
36dc3b71 262 } else {
11edf09c
BB
263 $output .= $this->output->heading(
264 format_string($event->name, false, array('context' => $context)),
265 3,
7ee4a287 266 array('class' => 'name d-inline-block')
11edf09c 267 );
36dc3b71 268 }
e73b527c
AA
269 // Show subscription source if needed.
270 if (!empty($event->subscription) && $CFG->calendar_showicalsource) {
271 if (!empty($event->subscription->url)) {
8afe9f8a
SL
272 $source = html_writer::link($event->subscription->url,
273 get_string('subscriptionsource', 'calendar', $event->subscription->name));
e73b527c
AA
274 } else {
275 // File based ical.
8afe9f8a 276 $source = get_string('subscriptionsource', 'calendar', $event->subscription->name);
e73b527c 277 }
11edf09c 278 $output .= html_writer::tag('div', $source, array('class' => 'subscription'));
8a7326ed 279 }
a05e4f25 280 if (!empty($event->courselink)) {
0b8e7216 281 $output .= html_writer::tag('div', $event->courselink);
a05e4f25 282 }
36dc3b71 283 if (!empty($event->time)) {
7ee4a287 284 $output .= html_writer::tag('span', $event->time, array('class' => 'date pull-xs-right m-r-1'));
36dc3b71 285 } else {
48489fb9 286 $attrs = array('class' => 'date pull-xs-right m-r-1');
23a29de7 287 $output .= html_writer::tag('span', calendar_time_representation($event->timestart), $attrs);
7ee4a287 288 }
a05e4f25
CB
289
290 if (!empty($event->actionurl)) {
0b8e7216
SL
291 $actionlink = html_writer::link(new moodle_url($event->actionurl), $event->actionname);
292 $output .= html_writer::tag('div', $actionlink, ['class' => 'action']);
36dc3b71
SH
293 }
294
7ee4a287 295 $output .= $this->output->box_end();
11edf09c
BB
296 $eventdetailshtml = '';
297 $eventdetailsclasses = '';
36dc3b71 298
11edf09c 299 $eventdetailshtml .= format_text($event->description, $event->format, array('context' => $context));
7ee4a287 300 $eventdetailsclasses .= 'description card-block';
36dc3b71 301 if (isset($event->cssclass)) {
11edf09c 302 $eventdetailsclasses .= ' '.$event->cssclass;
36dc3b71
SH
303 }
304
7ee4a287
DW
305 if (!empty($eventdetailshtml)) {
306 $output .= html_writer::tag('div', $eventdetailshtml, array('class' => $eventdetailsclasses));
36dc3b71 307 }
7ee4a287
DW
308
309 $eventhtml = html_writer::tag('div', $output, array('class' => 'card'));
310 return html_writer::tag('div', $eventhtml, array('class' => 'event', 'id' => 'event_' . $event->id));
36dc3b71
SH
311 }
312
36dc3b71
SH
313 /**
314 * Displays upcoming events
315 *
316 * @param calendar_information $calendar
317 * @param int $futuredays
318 * @param int $maxevents
319 * @return string
320 */
797cedc7
SH
321 public function show_upcoming_events(calendar_information $calendar, $futuredays, $maxevents, moodle_url $returnurl = null) {
322
323 if ($returnurl === null) {
324 $returnurl = $this->page->url;
325 }
326
23a29de7 327 $events = calendar_get_upcoming($calendar->courses, $calendar->groups, $calendar->users,
12cbce0a 328 $futuredays, $maxevents);
36dc3b71
SH
329
330 $output = html_writer::start_tag('div', array('class'=>'header'));
712e5f22 331 $output .= $this->course_filter_selector($returnurl, get_string('upcomingeventsfor', 'calendar'));
23a29de7 332 if (calendar_user_can_add_event($calendar->course)) {
797cedc7 333 $output .= $this->add_event_button($calendar->course->id);
36dc3b71 334 }
36dc3b71
SH
335 $output .= html_writer::end_tag('div');
336
337 if ($events) {
11edf09c 338 $output .= html_writer::start_tag('div', array('class' => 'eventlist'));
36dc3b71 339 foreach ($events as $event) {
e1cd93ce
MN
340 // Convert to calendar_event object so that we transform description accordingly.
341 $event = new calendar_event($event);
484617d2 342 $event->calendarcourseid = $calendar->courseid;
36dc3b71
SH
343 $output .= $this->event($event);
344 }
345 $output .= html_writer::end_tag('div');
346 } else {
136f8f09 347 $output .= html_writer::span(get_string('noupcomingevents', 'calendar'), 'calendar-information calendar-no-results');
36dc3b71
SH
348 }
349
350 return $output;
351 }
352
353 /**
354 * Displays a course filter selector
355 *
ba1ce6e9
SH
356 * @param moodle_url $returnurl The URL that the user should be taken too upon selecting a course.
357 * @param string $label The label to use for the course select.
36dc3b71
SH
358 * @return string
359 */
64ff737a 360 public function course_filter_selector(moodle_url $returnurl, $label=null) {
6be8c6b7 361 global $USER, $SESSION, $CFG;
36dc3b71
SH
362
363 if (!isloggedin() or isguestuser()) {
364 return '';
365 }
366
6ca657a7 367 if (has_capability('moodle/calendar:manageentries', context_system::instance()) && !empty($CFG->calendar_adminseesall)) {
36dc3b71
SH
368 $courses = get_courses('all', 'c.shortname','c.id,c.shortname');
369 } else {
370 $courses = enrol_get_my_courses();
371 }
372
373 unset($courses[SITEID]);
374
375 $courseoptions = array();
376 $courseoptions[SITEID] = get_string('fulllistofcourses');
377 foreach ($courses as $course) {
6ca657a7 378 $coursecontext = context_course::instance($course->id);
8ebbb06a 379 $courseoptions[$course->id] = format_string($course->shortname, true, array('context' => $coursecontext));
36dc3b71
SH
380 }
381
797cedc7
SH
382 if ($this->page->course->id !== SITEID) {
383 $selected = $this->page->course->id;
36dc3b71
SH
384 } else {
385 $selected = '';
386 }
ba1ce6e9
SH
387 $courseurl = new moodle_url($returnurl);
388 $courseurl->remove_params('course');
ce679f6c
SL
389
390 if ($label === null) {
391 $label = get_string('listofcourses');
91a774e2 392 }
ce679f6c
SL
393
394 $select = html_writer::label($label, 'course', false, ['class' => 'm-r-1']);
395 $select .= html_writer::select($courseoptions, 'course', $selected, false, ['class' => 'cal_courses_flt']);
396
397 return $select;
36dc3b71 398 }
e30390a0
SH
399
400 /**
401 * Renders a table containing information about calendar subscriptions.
402 *
403 * @param int $courseid
404 * @param array $subscriptions
405 * @param string $importresults
406 * @return string
407 */
408 public function subscription_details($courseid, $subscriptions, $importresults = '') {
409 $table = new html_table();
410 $table->head = array(
411 get_string('colcalendar', 'calendar'),
412 get_string('collastupdated', 'calendar'),
8c84f374 413 get_string('eventkind', 'calendar'),
e30390a0
SH
414 get_string('colpoll', 'calendar'),
415 get_string('colactions', 'calendar')
416 );
417 $table->align = array('left', 'left', 'left', 'center');
418 $table->width = '100%';
419 $table->data = array();
420
421 if (empty($subscriptions)) {
422 $cell = new html_table_cell(get_string('nocalendarsubscriptions', 'calendar'));
f936c2a9 423 $cell->colspan = 5;
e30390a0
SH
424 $table->data[] = new html_table_row(array($cell));
425 }
426 $strnever = new lang_string('never', 'calendar');
427 foreach ($subscriptions as $sub) {
428 $label = $sub->name;
429 if (!empty($sub->url)) {
430 $label = html_writer::link($sub->url, $label);
431 }
432 if (empty($sub->lastupdated)) {
433 $lastupdated = $strnever->out();
434 } else {
435 $lastupdated = userdate($sub->lastupdated, get_string('strftimedatetimeshort', 'langconfig'));
436 }
437
438 $cell = new html_table_cell($this->subscription_action_form($sub, $courseid));
439 $cell->colspan = 2;
8c84f374 440 $type = $sub->eventtype . 'events';
e30390a0
SH
441
442 $table->data[] = new html_table_row(array(
443 new html_table_cell($label),
444 new html_table_cell($lastupdated),
8c84f374 445 new html_table_cell(get_string($type, 'calendar')),
e30390a0
SH
446 $cell
447 ));
448 }
449
450 $out = $this->output->box_start('generalbox calendarsubs');
451
452 $out .= $importresults;
453 $out .= html_writer::table($table);
454 $out .= $this->output->box_end();
455 return $out;
456 }
457
458 /**
459 * Creates a form to perform actions on a given subscription.
460 *
461 * @param stdClass $subscription
462 * @param int $courseid
463 * @return string
464 */
465 protected function subscription_action_form($subscription, $courseid) {
466 // Assemble form for the subscription row.
467 $html = html_writer::start_tag('form', array('action' => new moodle_url('/calendar/managesubscriptions.php'), 'method' => 'post'));
468 if (empty($subscription->url)) {
469 // Don't update an iCal file, which has no URL.
470 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'pollinterval', 'value' => '0'));
471 } else {
472 // Assemble pollinterval control.
473 $html .= html_writer::start_tag('div', array('style' => 'float:left;'));
d1ee1e5f 474 $html .= html_writer::start_tag('select', array('name' => 'pollinterval', 'class' => 'custom-select'));
23a29de7 475 foreach (calendar_get_pollinterval_choices() as $k => $v) {
e30390a0
SH
476 $attributes = array();
477 if ($k == $subscription->pollinterval) {
478 $attributes['selected'] = 'selected';
479 }
4e3ce346 480 $attributes['value'] = $k;
e30390a0
SH
481 $html .= html_writer::tag('option', $v, $attributes);
482 }
483 $html .= html_writer::end_tag('select');
484 $html .= html_writer::end_tag('div');
485 }
e30390a0
SH
486 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
487 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'course', 'value' => $courseid));
488 $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 'value' => $subscription->id));
5fd78ba4 489 $html .= html_writer::start_tag('div', array('class' => 'btn-group pull-right'));
e30390a0 490 if (!empty($subscription->url)) {
ee74a2a1 491 $html .= html_writer::tag('button', get_string('update'), array('type' => 'submit', 'name' => 'action',
d1ee1e5f 492 'class' => 'btn btn-secondary',
ee74a2a1 493 'value' => CALENDAR_SUBSCRIPTION_UPDATE));
e30390a0 494 }
ee74a2a1 495 $html .= html_writer::tag('button', get_string('remove'), array('type' => 'submit', 'name' => 'action',
d1ee1e5f 496 'class' => 'btn btn-secondary',
ee74a2a1 497 'value' => CALENDAR_SUBSCRIPTION_REMOVE));
e30390a0
SH
498 $html .= html_writer::end_tag('div');
499 $html .= html_writer::end_tag('form');
500 return $html;
501 }
c8b6e9ab
AN
502
503 /**
504 * Render the event filter region.
505 *
506 * @return string
507 */
508 public function event_filter() {
509 $data = [
510 'eventtypes' => calendar_get_filter_types(),
511 ];
512 return $this->render_from_template('core_calendar/event_filter', $data);
513 }
2ad8f0f6 514}