MDL-35826 - Calendar - Creating a caption containing the month name for mini-calendars
[moodle.git] / calendar / lib.php
CommitLineData
93c91ee4 1<?php
08b4a4e1
RW
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/>.
7423f116 16
08b4a4e1
RW
17/**
18 * Calendar extension
19 *
20 * @package core_calendar
21 * @copyright 2004 Greek School Network (http://www.sch.gr), Jon Papaioannou,
22 * Avgoustos Tsinakos
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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}
b5a52acd 29
08b4a4e1
RW
30/**
31 * These are read by the administration component to provide default values
32 */
33
34/**
35 * CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD - default value of upcoming event preference
36 */
bb4a2e85 37define('CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD', 21);
08b4a4e1
RW
38
39/**
40 * CALENDAR_DEFAULT_UPCOMING_MAXEVENTS - default value to display the maximum number of upcoming event
41 */
bb4a2e85 42define('CALENDAR_DEFAULT_UPCOMING_MAXEVENTS', 10);
08b4a4e1
RW
43
44/**
45 * CALENDAR_DEFAULT_STARTING_WEEKDAY - default value to display the starting weekday
46 */
47define('CALENDAR_DEFAULT_STARTING_WEEKDAY', 1);
48
bb4a2e85 49// This is a packed bitfield: day X is "weekend" if $field & (1 << X) is true
50// Default value = 65 = 64 + 1 = 2^6 + 2^0 = Saturday & Sunday
08b4a4e1
RW
51
52/**
53 * CALENDAR_DEFAULT_WEEKEND - default value for weekend (Saturday & Sunday)
54 */
55define('CALENDAR_DEFAULT_WEEKEND', 65);
56
57/**
58 * CALENDAR_URL - path to calendar's folder
59 */
76d9df3f 60define('CALENDAR_URL', $CFG->wwwroot.'/calendar/');
08b4a4e1
RW
61
62/**
63 * CALENDAR_TF_24 - Calendar time in 24 hours format
64 */
76d9df3f 65define('CALENDAR_TF_24', '%H:%M');
08b4a4e1
RW
66
67/**
68 * CALENDAR_TF_12 - Calendar time in 12 hours format
69 */
76d9df3f 70define('CALENDAR_TF_12', '%I:%M %p');
bb4a2e85 71
08b4a4e1
RW
72/**
73 * CALENDAR_EVENT_GLOBAL - Global calendar event types
74 */
797cedc7 75define('CALENDAR_EVENT_GLOBAL', 1);
08b4a4e1
RW
76
77/**
78 * CALENDAR_EVENT_COURSE - Course calendar event types
79 */
797cedc7 80define('CALENDAR_EVENT_COURSE', 2);
08b4a4e1
RW
81
82/**
83 * CALENDAR_EVENT_GROUP - group calendar event types
84 */
797cedc7 85define('CALENDAR_EVENT_GROUP', 4);
08b4a4e1
RW
86
87/**
88 * CALENDAR_EVENT_USER - user calendar event types
89 */
797cedc7
SH
90define('CALENDAR_EVENT_USER', 8);
91
7423f116 92
b5a52acd
JH
93/**
94 * CALENDAR_IMPORT_FROM_FILE - import the calendar from a file
95 */
96define('CALENDAR_IMPORT_FROM_FILE', 0);
97
98/**
99 * CALENDAR_IMPORT_FROM_URL - import the calendar from a URL
100 */
101define('CALENDAR_IMPORT_FROM_URL', 1);
102
103/**
104 * CALENDAR_IMPORT_EVENT_UPDATED - imported event was updated
105 */
106define('CALENDAR_IMPORT_EVENT_UPDATED', 1);
107
108/**
109 * CALENDAR_IMPORT_EVENT_INSERTED - imported event was added by insert
110 */
111define('CALENDAR_IMPORT_EVENT_INSERTED', 2);
112
797cedc7
SH
113/**
114 * Return the days of the week
115 *
08b4a4e1 116 * @return array array of days
797cedc7
SH
117 */
118function calendar_get_days() {
119 return array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
120}
f2bffd9e 121
76d9df3f
SH
122/**
123 * Gets the first day of the week
124 *
125 * Used to be define('CALENDAR_STARTING_WEEKDAY', blah);
126 *
127 * @return int
128 */
129function calendar_get_starting_weekday() {
130 global $CFG;
37d87d11 131
76d9df3f
SH
132 if (isset($CFG->calendar_startwday)) {
133 $firstday = $CFG->calendar_startwday;
134 } else {
0a933ae6 135 $firstday = get_string('firstdayofweek', 'langconfig');
76d9df3f
SH
136 }
137
138 if(!is_numeric($firstday)) {
139 return CALENDAR_DEFAULT_STARTING_WEEKDAY;
140 } else {
141 return intval($firstday) % 7;
142 }
143}
bd119567 144
0f927f1e 145/**
14236cbd 146 * Generates the HTML for a miniature calendar
0f927f1e 147 *
512edd65 148 * @param array $courses list of course to list events from
08b4a4e1
RW
149 * @param array $groups list of group
150 * @param array $users user's info
151 * @param int $cal_month calendar month in numeric, default is set to false
152 * @param int $cal_year calendar month in numeric, default is set to false
512edd65
JF
153 * @param string $placement the place/page the calendar is set to appear - passed on the the controls function
154 * @param int $courseid id of the course the calendar is displayed on - passed on the the controls function
08b4a4e1 155 * @return string $content return html table for mini calendar
0f927f1e 156 */
512edd65 157function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_year = false, $placement, $courseid = false ) {
6b608f8f 158 global $CFG, $USER, $OUTPUT;
7423f116 159
dd97c328 160 $display = new stdClass;
76d9df3f 161 $display->minwday = get_user_preferences('calendar_startwday', calendar_get_starting_weekday());
7423f116 162 $display->maxwday = $display->minwday + 6;
163
164 $content = '';
165
166 if(!empty($cal_month) && !empty($cal_year)) {
167 $thisdate = usergetdate(time()); // Date and time the user sees at his location
168 if($cal_month == $thisdate['mon'] && $cal_year == $thisdate['year']) {
169 // Navigated to this month
170 $date = $thisdate;
171 $display->thismonth = true;
36dc3b71 172 } else {
7423f116 173 // Navigated to other month, let's do a nice trick and save us a lot of work...
174 if(!checkdate($cal_month, 1, $cal_year)) {
175 $date = array('mday' => 1, 'mon' => $thisdate['mon'], 'year' => $thisdate['year']);
176 $display->thismonth = true;
36dc3b71 177 } else {
7423f116 178 $date = array('mday' => 1, 'mon' => $cal_month, 'year' => $cal_year);
179 $display->thismonth = false;
180 }
181 }
36dc3b71 182 } else {
7423f116 183 $date = usergetdate(time()); // Date and time the user sees at his location
184 $display->thismonth = true;
185 }
186
187 // Fill in the variables we 're going to use, nice and tidy
188 list($d, $m, $y) = array($date['mday'], $date['mon'], $date['year']); // This is what we want to display
189 $display->maxdays = calendar_days_in_month($m, $y);
190
d6198903 191 if (get_user_timezone_offset() < 99) {
192 // We 'll keep these values as GMT here, and offset them when the time comes to query the db
61240489 193 $display->tstart = gmmktime(0, 0, 0, $m, 1, $y); // This is GMT
194 $display->tend = gmmktime(23, 59, 59, $m, $display->maxdays, $y); // GMT
d6198903 195 } else {
196 // no timezone info specified
61240489 197 $display->tstart = mktime(0, 0, 0, $m, 1, $y);
198 $display->tend = mktime(23, 59, 59, $m, $display->maxdays, $y);
d6198903 199 }
7423f116 200
69244b91 201 $startwday = dayofweek(1, $m, $y);
7423f116 202
203 // Align the starting weekday to fall in our display range
204 // This is simple, not foolproof.
205 if($startwday < $display->minwday) {
206 $startwday += 7;
207 }
208
c0b507d1 209 // TODO: THIS IS TEMPORARY CODE!
210 // [pj] I was just reading through this and realized that I when writing this code I was probably
211 // asking for trouble, as all these time manipulations seem to be unnecessary and a simple
212 // make_timestamp would accomplish the same thing. So here goes a test:
1747ee05 213 //$test_start = make_timestamp($y, $m, 1);
214 //$test_end = make_timestamp($y, $m, $display->maxdays, 23, 59, 59);
215 //if($test_start != usertime($display->tstart) - dst_offset_on($display->tstart)) {
216 //notify('Failed assertion in calendar/lib.php line 126; display->tstart = '.$display->tstart.', dst_offset = '.dst_offset_on($display->tstart).', usertime = '.usertime($display->tstart).', make_t = '.$test_start);
217 //}
218 //if($test_end != usertime($display->tend) - dst_offset_on($display->tend)) {
219 //notify('Failed assertion in calendar/lib.php line 130; display->tend = '.$display->tend.', dst_offset = '.dst_offset_on($display->tend).', usertime = '.usertime($display->tend).', make_t = '.$test_end);
220 //}
c0b507d1 221
222
7423f116 223 // Get the events matching our criteria. Don't forget to offset the timestamps for the user's TZ!
8263f802 224 $events = calendar_get_events(
d3555a2f 225 usertime($display->tstart) - dst_offset_on($display->tstart),
226 usertime($display->tend) - dst_offset_on($display->tend),
227 $users, $groups, $courses);
7423f116 228
b4892fa2 229 // Set event course class for course events
230 if (!empty($events)) {
13534ef7 231 foreach ($events as $eventid => $event) {
13534ef7
ML
232 if (!empty($event->modulename)) {
233 $cm = get_coursemodule_from_instance($event->modulename, $event->instance);
234 if (!groups_course_module_visible($cm)) {
235 unset($events[$eventid]);
236 }
237 }
b4892fa2 238 }
239 }
6619eba4 240
c635dcda 241 // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after
9064751b 242 // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month
c635dcda 243 // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra
244 // arguments to this function.
245
0f927f1e 246 $hrefparams = array();
7bd1677c 247 if(!empty($courses)) {
e749554e 248 $courses = array_diff($courses, array(SITEID));
7bd1677c 249 if(count($courses) == 1) {
0f927f1e 250 $hrefparams['course'] = reset($courses);
7bd1677c 251 }
c635dcda 252 }
253
7423f116 254 // We want to have easy access by day, since the display is on a per-day basis.
255 // Arguments passed by reference.
7b38bfa6 256 //calendar_events_by_day($events, $display->tstart, $eventsbyday, $durationbyday, $typesbyday);
7c50db30 257 calendar_events_by_day($events, $m, $y, $eventsbyday, $durationbyday, $typesbyday, $courses);
7423f116 258
f20c4d02 259 //Accessibility: added summary and <abbr> elements.
797cedc7 260 $days_title = calendar_get_days();
f20c4d02 261
2a06efcc 262 $summary = get_string('calendarheading', 'calendar', userdate(make_timestamp($y, $m), get_string('strftimemonthyear')));
90723839 263 $content .= '<table class="minicalendar calendartable" summary="'.$summary.'">'; // Begin table
512edd65
JF
264 if (($placement !== false) && ($courseid !== false)) {
265 $content .= '<caption>'. calendar_top_controls($placement, array('id' => $courseid, 'm' => $m, 'y' => $y)) .'</caption>';
266 }
f136e4c5 267 $content .= '<tr class="weekdays">'; // Header row: day names
7423f116 268
269 // Print out the names of the weekdays
270 $days = array('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat');
271 for($i = $display->minwday; $i <= $display->maxwday; ++$i) {
272 // This uses the % operator to get the correct weekday no matter what shift we have
273 // applied to the $display->minwday : $display->maxwday range from the default 0 : 6
f20c4d02 274 $content .= '<th scope="col"><abbr title="'. get_string($days_title[$i % 7], 'calendar') .'">'.
275 get_string($days[$i % 7], 'calendar') ."</abbr></th>\n";
7423f116 276 }
277
f136e4c5 278 $content .= '</tr><tr>'; // End of day names; prepare for day numbers
7423f116 279
280 // For the table display. $week is the row; $dayweek is the column.
7423f116 281 $dayweek = $startwday;
282
283 // Paddding (the first week may have blank days in the beginning)
284 for($i = $display->minwday; $i < $startwday; ++$i) {
3bfd8bc8 285 $content .= '<td class="dayblank">&nbsp;</td>'."\n";
7423f116 286 }
287
797cedc7
SH
288 $weekend = CALENDAR_DEFAULT_WEEKEND;
289 if (isset($CFG->calendar_weekend)) {
290 $weekend = intval($CFG->calendar_weekend);
291 }
292
7423f116 293 // Now display all the calendar
294 for($day = 1; $day <= $display->maxdays; ++$day, ++$dayweek) {
295 if($dayweek > $display->maxwday) {
296 // We need to change week (table row)
d56d4e23 297 $content .= '</tr><tr>';
7423f116 298 $dayweek = $display->minwday;
7423f116 299 }
92668ad2 300
7423f116 301 // Reset vars
7423f116 302 $cell = '';
797cedc7 303 if ($weekend & (1 << ($dayweek % 7))) {
7423f116 304 // Weekend. This is true no matter what the exact range is.
e2aa618b 305 $class = 'weekend day';
90723839 306 } else {
7423f116 307 // Normal working day.
e2aa618b 308 $class = 'day';
7423f116 309 }
310
311 // Special visual fx if an event is defined
312 if(isset($eventsbyday[$day])) {
90723839 313 $class .= ' hasevent';
0f927f1e
SH
314 $hrefparams['view'] = 'day';
315 $dayhref = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $hrefparams), $day, $m, $y);
f434283f 316
7423f116 317 $popupcontent = '';
318 foreach($eventsbyday[$day] as $eventid) {
41d30a8e 319 if (!isset($events[$eventid])) {
320 continue;
4e17c6f3 321 }
41d30a8e 322 $event = $events[$eventid];
0f927f1e
SH
323 $popupalt = '';
324 $component = 'moodle';
41d30a8e 325 if(!empty($event->modulename)) {
0f927f1e 326 $popupicon = 'icon';
41d30a8e 327 $popupalt = $event->modulename;
0f927f1e 328 $component = $event->modulename;
9064751b 329 } else if ($event->courseid == SITEID) { // Site event
5326675e 330 $popupicon = 'i/siteevent';
c3d3b6d4 331 } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event
5326675e 332 $popupicon = 'i/courseevent';
41d30a8e 333 } else if ($event->groupid) { // Group event
5326675e 334 $popupicon = 'i/groupevent';
41d30a8e 335 } else if ($event->userid) { // User event
5326675e 336 $popupicon = 'i/userevent';
4e17c6f3 337 }
1d5bd3d2 338
0f927f1e 339 $dayhref->set_anchor('event_'.$event->id);
1d5bd3d2 340
0f927f1e 341 $popupcontent .= html_writer::start_tag('div');
adc3cfc7 342 $popupcontent .= $OUTPUT->pix_icon($popupicon, $popupalt, $component);
0f927f1e
SH
343 $popupcontent .= html_writer::link($dayhref, format_string($event->name, true));
344 $popupcontent .= html_writer::end_tag('div');
7423f116 345 }
e295df44 346
b5c42e70 347 //Accessibility: functionality moved to calendar_get_popup.
348 if($display->thismonth && $day == $d) {
7df3ea15 349 $popupid = calendar_get_popup(true, $events[$eventid]->timestart, $popupcontent);
b5c42e70 350 } else {
7df3ea15 351 $popupid = calendar_get_popup(false, $events[$eventid]->timestart, $popupcontent);
e295df44 352 }
f20c4d02 353
7423f116 354 // Class and cell content
355 if(isset($typesbyday[$day]['startglobal'])) {
90723839
SH
356 $class .= ' calendar_event_global';
357 } else if(isset($typesbyday[$day]['startcourse'])) {
358 $class .= ' calendar_event_course';
359 } else if(isset($typesbyday[$day]['startgroup'])) {
360 $class .= ' calendar_event_group';
361 } else if(isset($typesbyday[$day]['startuser'])) {
362 $class .= ' calendar_event_user';
7423f116 363 }
cfa4a78b 364 $cell = html_writer::link($dayhref, $day, array('aria-controls' => $popupid.'_panel', 'id' => $popupid));
90723839 365 } else {
05fcc5fd 366 $cell = $day;
7423f116 367 }
368
90723839
SH
369 $durationclass = false;
370 if (isset($typesbyday[$day]['durationglobal'])) {
371 $durationclass = ' duration_global';
372 } else if(isset($typesbyday[$day]['durationcourse'])) {
373 $durationclass = ' duration_course';
374 } else if(isset($typesbyday[$day]['durationgroup'])) {
375 $durationclass = ' duration_group';
376 } else if(isset($typesbyday[$day]['durationuser'])) {
377 $durationclass = ' duration_user';
7423f116 378 }
90723839
SH
379 if ($durationclass) {
380 $class .= ' duration '.$durationclass;
7423f116 381 }
382
b4892fa2 383 // If event has a class set then add it to the table day <td> tag
384 // Note: only one colour for minicalendar
385 if(isset($eventsbyday[$day])) {
386 foreach($eventsbyday[$day] as $eventid) {
387 if (!isset($events[$eventid])) {
388 continue;
389 }
390 $event = $events[$eventid];
391 if (!empty($event->class)) {
392 $class .= ' '.$event->class;
393 }
394 break;
395 }
396 }
397
7423f116 398 // Special visual fx for today
450a0a7d 399 //Accessibility: hidden text for today, and popup.
7423f116 400 if($display->thismonth && $day == $d) {
edbe6c1b 401 $class .= ' today';
b5c42e70 402 $today = get_string('today', 'calendar').' '.userdate(time(), get_string('strftimedayshort'));
e295df44 403
b5c42e70 404 if(! isset($eventsbyday[$day])) {
450a0a7d 405 $class .= ' eventnone';
7df3ea15 406 $popupid = calendar_get_popup(true, false);
cfa4a78b 407 $cell = html_writer::link('#', $day, array('aria-controls' => $popupid.'_panel', 'id' => $popupid));
b5c42e70 408 }
f79f2494 409 $cell = get_accesshide($today.' ').$cell;
7423f116 410 }
411
412 // Just display it
92668ad2 413 if(!empty($class)) {
d56d4e23 414 $class = ' class="'.$class.'"';
92668ad2 415 }
416 $content .= '<td'.$class.'>'.$cell."</td>\n";
7423f116 417 }
418
419 // Paddding (the last week may have blank days at the end)
420 for($i = $dayweek; $i <= $display->maxwday; ++$i) {
3bfd8bc8 421 $content .= '<td class="dayblank">&nbsp;</td>';
7423f116 422 }
423 $content .= '</tr>'; // Last row ends
424
f136e4c5 425 $content .= '</table>'; // Tabular display of days ends
7423f116 426
427 return $content;
428}
429
b5c42e70 430/**
08b4a4e1
RW
431 * Gets the calendar popup
432 *
433 * It called at multiple points in from calendar_get_mini.
434 * Copied and modified from calendar_get_mini.
435 *
436 * @param bool $is_today false except when called on the current day.
437 * @param mixed $event_timestart $events[$eventid]->timestart, OR false if there are no events.
7df3ea15
AA
438 * @param string $popupcontent content for the popup window/layout.
439 * @return string eventid for the calendar_tooltip popup window/layout.
b5c42e70 440 */
441function calendar_get_popup($is_today, $event_timestart, $popupcontent='') {
458eb0d1 442 global $PAGE;
443 static $popupcount;
444 if ($popupcount === null) {
445 $popupcount = 1;
446 }
b5c42e70 447 $popupcaption = '';
448 if($is_today) {
449 $popupcaption = get_string('today', 'calendar').' ';
450 }
451 if (false === $event_timestart) {
452 $popupcaption .= userdate(time(), get_string('strftimedayshort'));
453 $popupcontent = get_string('eventnone', 'calendar');
450a0a7d 454
b5c42e70 455 } else {
456 $popupcaption .= get_string('eventsfor', 'calendar', userdate($event_timestart, get_string('strftimedayshort')));
457 }
458eb0d1 458 $id = 'calendar_tooltip_'.$popupcount;
33e48a1a 459 $PAGE->requires->yui_module('moodle-calendar-eventmanager', 'M.core_calendar.add_event', array(array('eventId'=>$id,'title'=>$popupcaption, 'content'=>$popupcontent)));
bdbae764 460
458eb0d1 461 $popupcount++;
7df3ea15 462 return $id;
b5c42e70 463}
464
08b4a4e1
RW
465/**
466 * Gets the calendar upcoming event
467 *
468 * @param array $courses array of courses
469 * @param array|int|bool $groups array of groups, group id or boolean for all/no group events
470 * @param array|int|bool $users array of users, user id or boolean for all/no user events
471 * @param int $daysinfuture number of days in the future we 'll look
472 * @param int $maxevents maximum number of events
473 * @param int $fromtime start time
474 * @return array $output array of upcoming events
475 */
9958a08c 476function calendar_get_upcoming($courses, $groups, $users, $daysinfuture, $maxevents, $fromtime=0) {
62d11d77 477 global $CFG, $COURSE, $DB;
7423f116 478
dd97c328 479 $display = new stdClass;
7423f116 480 $display->range = $daysinfuture; // How many days in the future we 'll look
481 $display->maxevents = $maxevents;
482
483 $output = array();
484
485 // Prepare "course caching", since it may save us a lot of queries
486 $coursecache = array();
487
488 $processed = 0;
489 $now = time(); // We 'll need this later
9d567178 490 $usermidnighttoday = usergetmidnight($now);
7423f116 491
9958a08c 492 if ($fromtime) {
493 $display->tstart = $fromtime;
494 } else {
9d567178 495 $display->tstart = $usermidnighttoday;
9958a08c 496 }
7423f116 497
1f473774 498 // This works correctly with respect to the user's DST, but it is accurate
499 // only because $fromtime is always the exact midnight of some day!
500 $display->tend = usergetmidnight($display->tstart + DAYSECS * $display->range + 3 * HOURSECS) - 1;
7423f116 501
502 // Get the events matching our criteria
8263f802 503 $events = calendar_get_events($display->tstart, $display->tend, $users, $groups, $courses);
7423f116 504
c635dcda 505 // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after
9064751b 506 // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month
c635dcda 507 // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra
508 // arguments to this function.
509
0f927f1e 510 $hrefparams = array();
7bd1677c 511 if(!empty($courses)) {
e749554e 512 $courses = array_diff($courses, array(SITEID));
7bd1677c 513 if(count($courses) == 1) {
0f927f1e 514 $hrefparams['course'] = reset($courses);
7bd1677c 515 }
c635dcda 516 }
517
dd97c328 518 if ($events !== false) {
519
f20edd52 520 $modinfo = get_fast_modinfo($COURSE);
fa22fd5f 521
7423f116 522 foreach($events as $event) {
dc6cb74e 523
dd97c328 524
525 if (!empty($event->modulename)) {
526 if ($event->courseid == $COURSE->id) {
527 if (isset($modinfo->instances[$event->modulename][$event->instance])) {
528 $cm = $modinfo->instances[$event->modulename][$event->instance];
529 if (!$cm->uservisible) {
530 continue;
531 }
532 }
533 } else {
534 if (!$cm = get_coursemodule_from_instance($event->modulename, $event->instance)) {
dc6cb74e 535 continue;
536 }
dd97c328 537 if (!coursemodule_visible_for_user($cm)) {
dc6cb74e 538 continue;
539 }
dd97c328 540 }
541 if ($event->modulename == 'assignment'){
f4700b91
RW
542 // create calendar_event to test edit_event capability
543 // this new event will also prevent double creation of calendar_event object
544 $checkevent = new calendar_event($event);
dd97c328 545 // TODO: rewrite this hack somehow
f4700b91 546 if (!calendar_edit_event_allowed($checkevent)){ // cannot manage entries, eg. student
62d11d77 547 if (!$assignment = $DB->get_record('assignment', array('id'=>$event->instance))) {
f581f8d6 548 // print_error("invalidid", 'assignment');
dd97c328 549 continue;
550 }
551 // assign assignment to assignment object to use hidden_is_hidden method
552 require_once($CFG->dirroot.'/mod/assignment/lib.php');
553
554 if (!file_exists($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php')) {
555 continue;
556 }
557 require_once ($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
558
559 $assignmentclass = 'assignment_'.$assignment->assignmenttype;
560 $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm);
561
562 if ($assignmentinstance->description_is_hidden()){//force not to show description before availability
563 $event->description = get_string('notavailableyet', 'assignment');
564 }
dc6cb74e 565 }
566 }
567 }
9d567178 568
dd97c328 569 if ($processed >= $display->maxevents) {
9d567178 570 break;
571 }
7423f116 572
0f927f1e 573 $event->time = calendar_format_event_time($event, $now, $hrefparams);
3c134875 574 $output[] = $event;
575 ++$processed;
576 }
577 }
578 return $output;
579}
7423f116 580
08b4a4e1
RW
581/**
582 * Add calendar event metadata
583 *
584 * @param stdClass $event event info
585 * @return stdClass $event metadata
586 */
9df8ff44 587function calendar_add_event_metadata($event) {
6b608f8f 588 global $CFG, $OUTPUT;
fb73f3b3 589
e295df44 590 //Support multilang in event->name
fb73f3b3 591 $event->name = format_string($event->name,true);
e295df44 592
3c134875 593 if(!empty($event->modulename)) { // Activity event
594 // The module name is set. I will assume that it has to be displayed, and
595 // also that it is an automatically-generated event. And of course that the
596 // fields for get_coursemodule_from_instance are set correctly.
597 $module = calendar_get_module_cached($coursecache, $event->modulename, $event->instance);
7423f116 598
3c134875 599 if ($module === false) {
600 return;
601 }
9958a08c 602
3c134875 603 $modulename = get_string('modulename', $event->modulename);
a48bf079
DM
604 if (get_string_manager()->string_exists($event->eventtype, $event->modulename)) {
605 // will be used as alt text if the event icon
606 $eventtype = get_string($event->eventtype, $event->modulename);
607 } else {
608 $eventtype = '';
609 }
b5d0cafc 610 $icon = $OUTPUT->pix_url('icon', $event->modulename) . '';
9958a08c 611
6ca657a7 612 $context = context_course::instance($module->course);
91d284c1
SH
613 $fullname = format_string($coursecache[$module->course]->fullname, true, array('context' => $context));
614
857c5f90 615 $event->icon = '<img src="'.$icon.'" alt="'.$eventtype.'" title="'.$modulename.'" class="icon" />';
fb73f3b3 616 $event->referer = '<a href="'.$CFG->wwwroot.'/mod/'.$event->modulename.'/view.php?id='.$module->id.'">'.$event->name.'</a>';
91d284c1 617 $event->courselink = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$module->course.'">'.$fullname.'</a>';
3c134875 618 $event->cmid = $module->id;
9958a08c 619
9958a08c 620
3c134875 621 } else if($event->courseid == SITEID) { // Site event
857c5f90 622 $event->icon = '<img src="'.$OUTPUT->pix_url('i/siteevent') . '" alt="'.get_string('globalevent', 'calendar').'" class="icon" />';
90723839 623 $event->cssclass = 'calendar_event_global';
3c134875 624 } else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event
625 calendar_get_course_cached($coursecache, $event->courseid);
91d284c1 626
6ca657a7 627 $context = context_course::instance($event->courseid);
91d284c1
SH
628 $fullname = format_string($coursecache[$event->courseid]->fullname, true, array('context' => $context));
629
857c5f90 630 $event->icon = '<img src="'.$OUTPUT->pix_url('i/courseevent') . '" alt="'.get_string('courseevent', 'calendar').'" class="icon" />';
91d284c1 631 $event->courselink = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$event->courseid.'">'.$fullname.'</a>';
90723839 632 $event->cssclass = 'calendar_event_course';
3c134875 633 } else if ($event->groupid) { // Group event
857c5f90 634 $event->icon = '<img src="'.$OUTPUT->pix_url('i/groupevent') . '" alt="'.get_string('groupevent', 'calendar').'" class="icon" />';
90723839 635 $event->cssclass = 'calendar_event_group';
3c134875 636 } else if($event->userid) { // User event
857c5f90 637 $event->icon = '<img src="'.$OUTPUT->pix_url('i/userevent') . '" alt="'.get_string('userevent', 'calendar').'" class="icon" />';
90723839 638 $event->cssclass = 'calendar_event_user';
3c134875 639 }
9df8ff44 640 return $event;
e295df44 641}
9df8ff44 642
8263f802 643/**
644 * Get calendar events
08b4a4e1 645 *
8263f802 646 * @param int $tstart Start time of time range for events
08b4a4e1
RW
647 * @param int $tend End time of time range for events
648 * @param array|int|boolean $users array of users, user id or boolean for all/no user events
649 * @param array|int|boolean $groups array of groups, group id or boolean for all/no group events
650 * @param array|int|boolean $courses array of courses, course id or boolean for all/no course events
8263f802 651 * @param boolean $withduration whether only events starting within time range selected
652 * or events in progress/already started selected as well
653 * @param boolean $ignorehidden whether to select only visible events or all events
08b4a4e1 654 * @return array $events of selected events or an empty array if there aren't any (or there was an error)
8263f802 655 */
656function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withduration=true, $ignorehidden=true) {
62d11d77 657 global $DB;
658
7423f116 659 $whereclause = '';
660 // Quick test
661 if(is_bool($users) && is_bool($groups) && is_bool($courses)) {
8263f802 662 return array();
7423f116 663 }
482dbe0c 664
7423f116 665 if(is_array($users) && !empty($users)) {
666 // Events from a number of users
667 if(!empty($whereclause)) $whereclause .= ' OR';
6e957c41 668 $whereclause .= ' (userid IN ('.implode(',', $users).') AND courseid = 0 AND groupid = 0)';
36dc3b71 669 } else if(is_numeric($users)) {
7423f116 670 // Events from one user
671 if(!empty($whereclause)) $whereclause .= ' OR';
6e957c41 672 $whereclause .= ' (userid = '.$users.' AND courseid = 0 AND groupid = 0)';
36dc3b71 673 } else if($users === true) {
7423f116 674 // Events from ALL users
675 if(!empty($whereclause)) $whereclause .= ' OR';
6e957c41 676 $whereclause .= ' (userid != 0 AND courseid = 0 AND groupid = 0)';
36dc3b71 677 } else if($users === false) {
6e957c41 678 // No user at all, do nothing
f52f7413 679 }
482dbe0c 680
7423f116 681 if(is_array($groups) && !empty($groups)) {
682 // Events from a number of groups
683 if(!empty($whereclause)) $whereclause .= ' OR';
684 $whereclause .= ' groupid IN ('.implode(',', $groups).')';
36dc3b71 685 } else if(is_numeric($groups)) {
7423f116 686 // Events from one group
687 if(!empty($whereclause)) $whereclause .= ' OR ';
688 $whereclause .= ' groupid = '.$groups;
36dc3b71 689 } else if($groups === true) {
7423f116 690 // Events from ALL groups
691 if(!empty($whereclause)) $whereclause .= ' OR ';
692 $whereclause .= ' groupid != 0';
693 }
482dbe0c 694 // boolean false (no groups at all): we don't need to do anything
695
36dc3b71
SH
696 if(is_array($courses) && !empty($courses)) {
697 if(!empty($whereclause)) {
698 $whereclause .= ' OR';
f52f7413 699 }
36dc3b71
SH
700 $whereclause .= ' (groupid = 0 AND courseid IN ('.implode(',', $courses).'))';
701 } else if(is_numeric($courses)) {
7423f116 702 // One course
703 if(!empty($whereclause)) $whereclause .= ' OR';
6e957c41 704 $whereclause .= ' (groupid = 0 AND courseid = '.$courses.')';
36dc3b71 705 } else if ($courses === true) {
7423f116 706 // Events from ALL courses
707 if(!empty($whereclause)) $whereclause .= ' OR';
6e957c41 708 $whereclause .= ' (groupid = 0 AND courseid != 0)';
7423f116 709 }
8c165fe9 710
482dbe0c 711 // Security check: if, by now, we have NOTHING in $whereclause, then it means
712 // that NO event-selecting clauses were defined. Thus, we won't be returning ANY
713 // events no matter what. Allowing the code to proceed might return a completely
714 // valid query with only time constraints, thus selecting ALL events in that time frame!
715 if(empty($whereclause)) {
8263f802 716 return array();
482dbe0c 717 }
718
7423f116 719 if($withduration) {
b4892fa2 720 $timeclause = '(timestart >= '.$tstart.' OR timestart + timeduration > '.$tstart.') AND timestart <= '.$tend;
7423f116 721 }
722 else {
723 $timeclause = 'timestart >= '.$tstart.' AND timestart <= '.$tend;
724 }
725 if(!empty($whereclause)) {
726 // We have additional constraints
727 $whereclause = $timeclause.' AND ('.$whereclause.')';
728 }
729 else {
730 // Just basic time filtering
731 $whereclause = $timeclause;
732 }
f52f7413 733
0ad072de 734 if ($ignorehidden) {
735 $whereclause .= ' AND visible = 1';
736 }
737
62d11d77 738 $events = $DB->get_records_select('event', $whereclause, null, 'timestart');
8263f802 739 if ($events === false) {
740 $events = array();
741 }
742 return $events;
7423f116 743}
744
08b4a4e1
RW
745/**
746 * Get control options for Calendar
747 *
748 * @param string $type of calendar
749 * @param array $data calendar information
750 * @return string $content return available control for the calender in html
751 */
7423f116 752function calendar_top_controls($type, $data) {
797cedc7 753 global $CFG;
7423f116 754 $content = '';
755 if(!isset($data['d'])) {
756 $data['d'] = 1;
757 }
5147ad48 758
f21ed0f3 759 // Ensure course id passed if relevant
760 // Required due to changes in view/lib.php mainly (calendar_session_vars())
761 $courseid = '';
762 if (!empty($data['id'])) {
763 $courseid = '&amp;course='.$data['id'];
764 }
765
5147ad48 766 if(!checkdate($data['m'], $data['d'], $data['y'])) {
767 $time = time();
768 }
769 else {
770 $time = make_timestamp($data['y'], $data['m'], $data['d']);
771 }
772 $date = usergetdate($time);
e295df44 773
7423f116 774 $data['m'] = $date['mon'];
775 $data['y'] = $date['year'];
7423f116 776
2a06efcc 777 //Accessibility: calendar block controls, replaced <table> with <div>.
a84dea2c 778 //$nexttext = link_arrow_right(get_string('monthnext', 'access'), $url='', $accesshide=true);
779 //$prevtext = link_arrow_left(get_string('monthprev', 'access'), $url='', $accesshide=true);
2a06efcc 780
7423f116 781 switch($type) {
782 case 'frontpage':
783 list($prevmonth, $prevyear) = calendar_sub_month($data['m'], $data['y']);
784 list($nextmonth, $nextyear) = calendar_add_month($data['m'], $data['y']);
a84dea2c 785 $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), 'index.php?', 0, $nextmonth, $nextyear, $accesshide=true);
786 $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), 'index.php?', 0, $prevmonth, $prevyear, true);
ae53bd1d 787
0f927f1e
SH
788 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'month')), 1, $data['m'], $data['y']);
789 if (!empty($data['id'])) {
790 $calendarlink->param('course', $data['id']);
791 }
792
43c90b9b 793 if (right_to_left()) {
0f927f1e
SH
794 $left = $nextlink;
795 $right = $prevlink;
43c90b9b 796 } else {
0f927f1e
SH
797 $left = $prevlink;
798 $right = $nextlink;
43c90b9b 799 }
ae53bd1d 800
0f927f1e
SH
801 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
802 $content .= $left.'<span class="hide"> | </span>';
e71e340d 803 $content .= html_writer::tag('span', html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')), array('title'=>get_string('monththis','calendar'))), array('class'=>'current'));
0f927f1e 804 $content .= '<span class="hide"> | </span>'. $right;
6694ff84 805 $content .= "<span class=\"clearer\"><!-- --></span>\n";
0f927f1e
SH
806 $content .= html_writer::end_tag('div');
807
808 break;
7423f116 809 case 'course':
810 list($prevmonth, $prevyear) = calendar_sub_month($data['m'], $data['y']);
811 list($nextmonth, $nextyear) = calendar_add_month($data['m'], $data['y']);
a84dea2c 812 $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), 'view.php?id='.$data['id'].'&amp;', 0, $nextmonth, $nextyear, $accesshide=true);
813 $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), 'view.php?id='.$data['id'].'&amp;', 0, $prevmonth, $prevyear, true);
0f927f1e
SH
814
815 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'month')), 1, $data['m'], $data['y']);
816 if (!empty($data['id'])) {
817 $calendarlink->param('course', $data['id']);
818 }
819
820 if (right_to_left()) {
821 $left = $nextlink;
822 $right = $prevlink;
823 } else {
824 $left = $prevlink;
825 $right = $nextlink;
826 }
827
828 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
829 $content .= $left.'<span class="hide"> | </span>';
e71e340d 830 $content .= html_writer::tag('span', html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')), array('title'=>get_string('monththis','calendar'))), array('class'=>'current'));
0f927f1e
SH
831 $content .= '<span class="hide"> | </span>'. $right;
832 $content .= "<span class=\"clearer\"><!-- --></span>";
833 $content .= html_writer::end_tag('div');
834 break;
7423f116 835 case 'upcoming':
0f927f1e
SH
836 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'upcoming')), 1, $data['m'], $data['y']);
837 if (!empty($data['id'])) {
838 $calendarlink->param('course', $data['id']);
839 }
840 $calendarlink = html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')));
841 $content .= html_writer::tag('div', $calendarlink, array('class'=>'centered'));
842 break;
7423f116 843 case 'display':
0f927f1e
SH
844 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'month')), 1, $data['m'], $data['y']);
845 if (!empty($data['id'])) {
846 $calendarlink->param('course', $data['id']);
847 }
848 $calendarlink = html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')));
849 $content .= html_writer::tag('h3', $calendarlink);
850 break;
7423f116 851 case 'month':
852 list($prevmonth, $prevyear) = calendar_sub_month($data['m'], $data['y']);
853 list($nextmonth, $nextyear) = calendar_add_month($data['m'], $data['y']);
5147ad48 854 $prevdate = make_timestamp($prevyear, $prevmonth, 1);
855 $nextdate = make_timestamp($nextyear, $nextmonth, 1);
0f927f1e
SH
856 $prevlink = calendar_get_link_previous(userdate($prevdate, get_string('strftimemonthyear')), 'view.php?view=month'.$courseid.'&amp;', 1, $prevmonth, $prevyear);
857 $nextlink = calendar_get_link_next(userdate($nextdate, get_string('strftimemonthyear')), 'view.php?view=month'.$courseid.'&amp;', 1, $nextmonth, $nextyear);
858
859 if (right_to_left()) {
860 $left = $nextlink;
861 $right = $prevlink;
862 } else {
863 $left = $prevlink;
864 $right = $nextlink;
865 }
866
867 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
868 $content .= $left . '<span class="hide"> | </span><h1 class="current">'.userdate($time, get_string('strftimemonthyear'))."</h1>";
869 $content .= '<span class="hide"> | </span>' . $right;
870 $content .= '<span class="clearer"><!-- --></span>';
871 $content .= html_writer::end_tag('div')."\n";
872 break;
7423f116 873 case 'day':
797cedc7 874 $days = calendar_get_days();
7423f116 875 $data['d'] = $date['mday']; // Just for convenience
5147ad48 876 $prevdate = usergetdate(make_timestamp($data['y'], $data['m'], $data['d'] - 1));
877 $nextdate = usergetdate(make_timestamp($data['y'], $data['m'], $data['d'] + 1));
797cedc7
SH
878 $prevname = calendar_wday_name($days[$prevdate['wday']]);
879 $nextname = calendar_wday_name($days[$nextdate['wday']]);
0f927f1e
SH
880 $prevlink = calendar_get_link_previous($prevname, 'view.php?view=day'.$courseid.'&amp;', $prevdate['mday'], $prevdate['mon'], $prevdate['year']);
881 $nextlink = calendar_get_link_next($nextname, 'view.php?view=day'.$courseid.'&amp;', $nextdate['mday'], $nextdate['mon'], $nextdate['year']);
882
883 if (right_to_left()) {
884 $left = $nextlink;
885 $right = $prevlink;
886 } else {
887 $left = $prevlink;
888 $right = $nextlink;
889 }
890
891 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
892 $content .= $left;
893 $content .= '<span class="hide"> | </span><span class="current">'.userdate($time, get_string('strftimedaydate')).'</span>';
894 $content .= '<span class="hide"> | </span>'. $right;
895 $content .= "<span class=\"clearer\"><!-- --></span>";
896 $content .= html_writer::end_tag('div')."\n";
897
898 break;
7423f116 899 }
900 return $content;
901}
902
ef21ad21
FM
903/**
904 * Formats a filter control element.
905 *
906 * @param moodle_url $url of the filter
907 * @param int $type constant defining the type filter
908 * @return string html content of the element
909 */
910function calendar_filter_controls_element(moodle_url $url, $type) {
911 global $OUTPUT;
912 switch ($type) {
913 case CALENDAR_EVENT_GLOBAL:
914 $typeforhumans = 'global';
915 $class = 'calendar_event_global';
916 break;
917 case CALENDAR_EVENT_COURSE:
918 $typeforhumans = 'course';
919 $class = 'calendar_event_course';
920 break;
921 case CALENDAR_EVENT_GROUP:
922 $typeforhumans = 'groups';
923 $class = 'calendar_event_group';
924 break;
925 case CALENDAR_EVENT_USER:
926 $typeforhumans = 'user';
927 $class = 'calendar_event_user';
928 break;
929 }
930 if (calendar_show_event_type($type)) {
931 $icon = $OUTPUT->pix_icon('t/hide', get_string('hide'));
932 $str = get_string('hide'.$typeforhumans.'events', 'calendar');
933 } else {
934 $icon = $OUTPUT->pix_icon('t/show', get_string('show'));
935 $str = get_string('show'.$typeforhumans.'events', 'calendar');
936 }
937 $content = html_writer::start_tag('li', array('class' => 'calendar_event'));
938 $content .= html_writer::start_tag('a', array('href' => $url));
939 $content .= html_writer::tag('span', $icon, array('class' => $class));
940 $content .= html_writer::tag('span', $str, array('class' => 'eventname'));
941 $content .= html_writer::end_tag('a');
942 $content .= html_writer::end_tag('li');
943 return $content;
944}
945
08b4a4e1
RW
946/**
947 * Get the controls filter for calendar.
948 *
949 * Filter is used to hide calendar info from the display page
950 *
951 * @param moodle_url $returnurl return-url for filter controls
952 * @return string $content return filter controls in html
953 */
797cedc7
SH
954function calendar_filter_controls(moodle_url $returnurl) {
955 global $CFG, $USER, $OUTPUT;
7423f116 956
48f508ab 957 $groupevents = true;
7f4d18fc 958 $id = optional_param( 'id',0,PARAM_INT );
eb59a448 959 $seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out(false)), 'sesskey'=>sesskey()));
ef21ad21 960 $content = html_writer::start_tag('ul');
797cedc7
SH
961
962 $seturl->param('var', 'showglobal');
ef21ad21 963 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_GLOBAL);
aa6c1ced 964
797cedc7 965 $seturl->param('var', 'showcourses');
ef21ad21 966 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_COURSE);
48f508ab 967
4f0c2d00 968 if (isloggedin() && !isguestuser()) {
797cedc7 969 if ($groupevents) {
43c3ffbe 970 // This course MIGHT have group events defined, so show the filter
797cedc7 971 $seturl->param('var', 'showgroups');
ef21ad21 972 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_GROUP);
e3bb6401 973 } else {
43c3ffbe 974 // This course CANNOT have group events, so lose the filter
fdbffa54 975 }
797cedc7 976 $seturl->param('var', 'showuser');
ef21ad21 977 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_USER);
48f508ab 978 }
ef21ad21 979 $content .= html_writer::end_tag('ul');
48f508ab 980
7423f116 981 return $content;
982}
983
08b4a4e1
RW
984/**
985 * Return the representation day
986 *
987 * @param int $tstamp Timestamp in GMT
988 * @param int $now current Unix timestamp
989 * @param bool $usecommonwords
990 * @return string the formatted date/time
991 */
7423f116 992function calendar_day_representation($tstamp, $now = false, $usecommonwords = true) {
993
0ef7c973 994 static $shortformat;
995 if(empty($shortformat)) {
e70fdac0 996 $shortformat = get_string('strftimedayshort');
0ef7c973 997 }
998
7423f116 999 if($now === false) {
1000 $now = time();
1001 }
1002
1003 // To have it in one place, if a change is needed
e70fdac0 1004 $formal = userdate($tstamp, $shortformat);
7423f116 1005
7b38bfa6 1006 $datestamp = usergetdate($tstamp);
1007 $datenow = usergetdate($now);
7423f116 1008
1009 if($usecommonwords == false) {
1010 // We don't want words, just a date
1011 return $formal;
1012 }
7b38bfa6 1013 else if($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday']) {
7423f116 1014 // Today
1015 return get_string('today', 'calendar');
1016 }
7b38bfa6 1017 else if(
1018 ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] - 1 ) ||
1019 ($datestamp['year'] == $datenow['year'] - 1 && $datestamp['mday'] == 31 && $datestamp['mon'] == 12 && $datenow['yday'] == 1)
1020 ) {
7423f116 1021 // Yesterday
1022 return get_string('yesterday', 'calendar');
1023 }
7b38bfa6 1024 else if(
1025 ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] + 1 ) ||
1026 ($datestamp['year'] == $datenow['year'] + 1 && $datenow['mday'] == 31 && $datenow['mon'] == 12 && $datestamp['yday'] == 1)
1027 ) {
7423f116 1028 // Tomorrow
1029 return get_string('tomorrow', 'calendar');
1030 }
1031 else {
1032 return $formal;
1033 }
1034}
1035
08b4a4e1
RW
1036/**
1037 * return the formatted representation time
1038 *
1039 * @param int $time the timestamp in UTC, as obtained from the database
1040 * @return string the formatted date/time
1041 */
7423f116 1042function calendar_time_representation($time) {
1b0ebe79 1043 static $langtimeformat = NULL;
1044 if($langtimeformat === NULL) {
1045 $langtimeformat = get_string('strftimetime');
1046 }
1047 $timeformat = get_user_preferences('calendar_timeformat');
c7dd2550 1048 if(empty($timeformat)){
1049 $timeformat = get_config(NULL,'calendar_site_timeformat');
1050 }
1b0ebe79 1051 // The ? is needed because the preference might be present, but empty
1052 return userdate($time, empty($timeformat) ? $langtimeformat : $timeformat);
7423f116 1053}
1054
e295df44 1055/**
14236cbd
SH
1056 * Adds day, month, year arguments to a URL and returns a moodle_url object.
1057 *
1058 * @param string|moodle_url $linkbase
08b4a4e1
RW
1059 * @param int $d The number of the day.
1060 * @param int $m The number of the month.
1061 * @param int $y The number of the year.
1062 * @return moodle_url|null $linkbase
e295df44 1063 */
7423f116 1064function calendar_get_link_href($linkbase, $d, $m, $y) {
0f927f1e
SH
1065 if (empty($linkbase)) {
1066 return '';
1067 }
1068 if (!($linkbase instanceof moodle_url)) {
e30390a0 1069 $linkbase = new moodle_url($linkbase);
0f927f1e
SH
1070 }
1071 if (!empty($d)) {
1072 $linkbase->param('cal_d', $d);
1073 }
1074 if (!empty($m)) {
1075 $linkbase->param('cal_m', $m);
1076 }
1077 if (!empty($y)) {
1078 $linkbase->param('cal_y', $y);
1079 }
1080 return $linkbase;
7423f116 1081}
1082
a84dea2c 1083/**
1084 * Build and return a previous month HTML link, with an arrow.
14236cbd 1085 *
a84dea2c 1086 * @param string $text The text label.
14236cbd 1087 * @param string|moodle_url $linkbase The URL stub.
08b4a4e1
RW
1088 * @param int $d The number of the date.
1089 * @param int $m The number of the month.
1090 * @param int $y year The number of the year.
a84dea2c 1091 * @param bool $accesshide Default visible, or hide from all except screenreaders.
1092 * @return string HTML string.
1093 */
1094function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide=false) {
0f927f1e
SH
1095 $href = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y);
1096 if (empty($href)) {
1097 return $text;
1098 }
1099 return link_arrow_left($text, (string)$href, $accesshide, 'previous');
a84dea2c 1100}
1101
1102/**
1103 * Build and return a next month HTML link, with an arrow.
1d5bd3d2 1104 *
a84dea2c 1105 * @param string $text The text label.
14236cbd 1106 * @param string|moodle_url $linkbase The URL stub.
08b4a4e1
RW
1107 * @param int $d the number of the Day
1108 * @param int $m The number of the month.
1109 * @param int $y The number of the year.
a84dea2c 1110 * @param bool $accesshide Default visible, or hide from all except screenreaders.
1111 * @return string HTML string.
1112 */
1113function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide=false) {
0f927f1e
SH
1114 $href = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y);
1115 if (empty($href)) {
1116 return $text;
1117 }
1118 return link_arrow_right($text, (string)$href, $accesshide, 'next');
a84dea2c 1119}
1120
08b4a4e1
RW
1121/**
1122 * Return the name of the weekday
1123 *
1124 * @param string $englishname
1125 * @return string of the weekeday
1126 */
7423f116 1127function calendar_wday_name($englishname) {
1128 return get_string(strtolower($englishname), 'calendar');
1129}
1130
08b4a4e1
RW
1131/**
1132 * Return the number of days in month
1133 *
1134 * @param int $month the number of the month.
1135 * @param int $year the number of the year
1136 * @return int
1137 */
7423f116 1138function calendar_days_in_month($month, $year) {
7b38bfa6 1139 return intval(date('t', mktime(0, 0, 0, $month, 1, $year)));
7423f116 1140}
1141
08b4a4e1
RW
1142/**
1143 * Get the upcoming event block
1144 *
1145 * @param array $events list of events
1146 * @param moodle_url|string $linkhref link to event referer
1147 * @return string|null $content html block content
1148 */
6605ff8c 1149function calendar_get_block_upcoming($events, $linkhref = NULL) {
7423f116 1150 $content = '';
1151 $lines = count($events);
396b61f0 1152 if (!$lines) {
1153 return $content;
1154 }
7423f116 1155
396b61f0 1156 for ($i = 0; $i < $lines; ++$i) {
b0ac9180 1157 if (!isset($events[$i]->time)) { // Just for robustness
1158 continue;
1159 }
9df8ff44 1160 $events[$i] = calendar_add_event_metadata($events[$i]);
857c5f90 1161 $content .= '<div class="event"><span class="icon c0">'.$events[$i]->icon.'</span>';
43c3ffbe 1162 if (!empty($events[$i]->referer)) {
7423f116 1163 // That's an activity event, so let's provide the hyperlink
396b61f0 1164 $content .= $events[$i]->referer;
1165 } else {
e749554e 1166 if(!empty($linkhref)) {
1167 $ed = usergetdate($events[$i]->timestart);
0f927f1e
SH
1168 $href = calendar_get_link_href(new moodle_url(CALENDAR_URL.$linkhref), $ed['mday'], $ed['mon'], $ed['year']);
1169 $href->set_anchor('event_'.$events[$i]->id);
1170 $content .= html_writer::link($href, $events[$i]->name);
e749554e 1171 }
1172 else {
1173 $content .= $events[$i]->name;
1174 }
7423f116 1175 }
9ecf051d 1176 $events[$i]->time = str_replace('&raquo;', '<br />&raquo;', $events[$i]->time);
1177 $content .= '<div class="date">'.$events[$i]->time.'</div></div>';
396b61f0 1178 if ($i < $lines - 1) $content .= '<hr />';
7423f116 1179 }
1180
1181 return $content;
7423f116 1182}
1183
08b4a4e1
RW
1184/**
1185 * Get the next following month
1186 *
1187 * If the current month is December, it will get the first month of the following year.
1188 *
1189 *
1190 * @param int $month the number of the month.
1191 * @param int $year the number of the year.
1192 * @return array the following month
1193 */
7423f116 1194function calendar_add_month($month, $year) {
1195 if($month == 12) {
1196 return array(1, $year + 1);
1197 }
1198 else {
1199 return array($month + 1, $year);
1200 }
1201}
1202
08b4a4e1
RW
1203/**
1204 * Get the previous month
1205 *
1206 * If the current month is January, it will get the last month of the previous year.
1207 *
1208 * @param int $month the number of the month.
1209 * @param int $year the number of the year.
1210 * @return array previous month
1211 */
7423f116 1212function calendar_sub_month($month, $year) {
1213 if($month == 1) {
1214 return array(12, $year - 1);
1215 }
1216 else {
1217 return array($month - 1, $year);
1218 }
1219}
1220
08b4a4e1
RW
1221/**
1222 * Get per-day basis events
1223 *
1224 * @param array $events list of events
1225 * @param int $month the number of the month
1226 * @param int $year the number of the year
1227 * @param array $eventsbyday event on specific day
1228 * @param array $durationbyday duration of the event in days
1229 * @param array $typesbyday event type (eg: global, course, user, or group)
1230 * @param array $courses list of courses
1231 * @return void
1232 */
7c50db30 1233function calendar_events_by_day($events, $month, $year, &$eventsbyday, &$durationbyday, &$typesbyday, &$courses) {
7423f116 1234 $eventsbyday = array();
1235 $typesbyday = array();
1236 $durationbyday = array();
1237
1238 if($events === false) {
1239 return;
1240 }
1241
7423f116 1242 foreach($events as $event) {
7423f116 1243
7b38bfa6 1244 $startdate = usergetdate($event->timestart);
b4892fa2 1245 // Set end date = start date if no duration
1246 if ($event->timeduration) {
1247 $enddate = usergetdate($event->timestart + $event->timeduration - 1);
1248 } else {
1249 $enddate = $startdate;
1250 }
7423f116 1251
7b38bfa6 1252 // Simple arithmetic: $year * 13 + $month is a distinct integer for each distinct ($year, $month) pair
ef618501 1253 if(!($startdate['year'] * 13 + $startdate['mon'] <= $year * 13 + $month) && ($enddate['year'] * 13 + $enddate['mon'] >= $year * 13 + $month)) {
7b38bfa6 1254 // Out of bounds
1255 continue;
7423f116 1256 }
1257
7b38bfa6 1258 $eventdaystart = intval($startdate['mday']);
7423f116 1259
7b38bfa6 1260 if($startdate['mon'] == $month && $startdate['year'] == $year) {
1261 // Give the event to its day
1262 $eventsbyday[$eventdaystart][] = $event->id;
7423f116 1263
7b38bfa6 1264 // Mark the day as having such an event
9064751b 1265 if($event->courseid == SITEID && $event->groupid == 0) {
7b38bfa6 1266 $typesbyday[$eventdaystart]['startglobal'] = true;
b4892fa2 1267 // Set event class for global event
90723839 1268 $events[$event->id]->class = 'calendar_event_global';
7b38bfa6 1269 }
c3d3b6d4 1270 else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {
7b38bfa6 1271 $typesbyday[$eventdaystart]['startcourse'] = true;
b4892fa2 1272 // Set event class for course event
90723839 1273 $events[$event->id]->class = 'calendar_event_course';
7b38bfa6 1274 }
1275 else if($event->groupid) {
1276 $typesbyday[$eventdaystart]['startgroup'] = true;
b4892fa2 1277 // Set event class for group event
90723839 1278 $events[$event->id]->class = 'calendar_event_group';
7b38bfa6 1279 }
1280 else if($event->userid) {
1281 $typesbyday[$eventdaystart]['startuser'] = true;
b4892fa2 1282 // Set event class for user event
90723839 1283 $events[$event->id]->class = 'calendar_event_user';
7b38bfa6 1284 }
1285 }
7423f116 1286
7b38bfa6 1287 if($event->timeduration == 0) {
1288 // Proceed with the next
1289 continue;
1290 }
7423f116 1291
7b38bfa6 1292 // The event starts on $month $year or before. So...
ef618501 1293 $lowerbound = $startdate['mon'] == $month && $startdate['year'] == $year ? intval($startdate['mday']) : 0;
7b38bfa6 1294
1295 // Also, it ends on $month $year or later...
1296 $upperbound = $enddate['mon'] == $month && $enddate['year'] == $year ? intval($enddate['mday']) : calendar_days_in_month($month, $year);
1297
1298 // Mark all days between $lowerbound and $upperbound (inclusive) as duration
1299 for($i = $lowerbound + 1; $i <= $upperbound; ++$i) {
1300 $durationbyday[$i][] = $event->id;
9064751b 1301 if($event->courseid == SITEID && $event->groupid == 0) {
7b38bfa6 1302 $typesbyday[$i]['durationglobal'] = true;
1303 }
c3d3b6d4 1304 else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {
7b38bfa6 1305 $typesbyday[$i]['durationcourse'] = true;
1306 }
1307 else if($event->groupid) {
1308 $typesbyday[$i]['durationgroup'] = true;
1309 }
1310 else if($event->userid) {
1311 $typesbyday[$i]['durationuser'] = true;
7423f116 1312 }
1313 }
7b38bfa6 1314
7423f116 1315 }
1316 return;
1317}
1318
08b4a4e1
RW
1319/**
1320 * Get current module cache
1321 *
1322 * @param array $coursecache list of course cache
1323 * @param string $modulename name of the module
1324 * @param int $instance module instance number
1325 * @return stdClass|bool $module information
1326 */
b63c0ee5 1327function calendar_get_module_cached(&$coursecache, $modulename, $instance) {
1328 $module = get_coursemodule_from_instance($modulename, $instance);
7423f116 1329
1330 if($module === false) return false;
b63c0ee5 1331 if(!calendar_get_course_cached($coursecache, $module->course)) {
7423f116 1332 return false;
1333 }
1334 return $module;
1335}
1336
08b4a4e1
RW
1337/**
1338 * Get current course cache
1339 *
1340 * @param array $coursecache list of course cache
1341 * @param int $courseid id of the course
1342 * @return stdClass $coursecache[$courseid] return the specific course cache
1343 */
7423f116 1344function calendar_get_course_cached(&$coursecache, $courseid) {
62d11d77 1345 global $COURSE, $DB;
1346
b571c6b3 1347 if (!isset($coursecache[$courseid])) {
1348 if ($courseid == $COURSE->id) {
1349 $coursecache[$courseid] = $COURSE;
1350 } else {
62d11d77 1351 $coursecache[$courseid] = $DB->get_record('course', array('id'=>$courseid));
b571c6b3 1352 }
7423f116 1353 }
1354 return $coursecache[$courseid];
1355}
1356
797cedc7
SH
1357/**
1358 * Returns the courses to load events for, the
1359 *
797cedc7 1360 * @param array $courseeventsfrom An array of courses to load calendar events for
08b4a4e1 1361 * @param bool $ignorefilters specify the use of filters, false is set as default
797cedc7
SH
1362 * @return array An array of courses, groups, and user to load calendar events for based upon filters
1363 */
1364function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
1365 global $USER, $CFG, $DB;
7423f116 1366
797cedc7
SH
1367 // For backwards compatability we have to check whether the courses array contains
1368 // just id's in which case we need to load course objects.
1369 $coursestoload = array();
1370 foreach ($courseeventsfrom as $id => $something) {
1371 if (!is_object($something)) {
1372 $coursestoload[] = $id;
1373 unset($courseeventsfrom[$id]);
753b799d 1374 }
dd97c328 1375 }
797cedc7
SH
1376 if (!empty($coursestoload)) {
1377 // TODO remove this in 2.2
1378 debugging('calendar_set_filters now preferes an array of course objects with preloaded contexts', DEBUG_DEVELOPER);
1379 $courseeventsfrom = array_merge($courseeventsfrom, $DB->get_records_list('course', 'id', $coursestoload));
89adb174 1380 }
7423f116 1381
797cedc7
SH
1382 $courses = array();
1383 $user = false;
1384 $group = false;
7423f116 1385
7fd5786e
HB
1386 // capabilities that allow seeing group events from all groups
1387 // TODO: rewrite so that moodle/calendar:manageentries is not necessary here
1388 $allgroupscaps = array('moodle/site:accessallgroups', 'moodle/calendar:manageentries');
1389
797cedc7 1390 $isloggedin = isloggedin();
d12e3ff2 1391
797cedc7
SH
1392 if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_COURSE)) {
1393 $courses = array_keys($courseeventsfrom);
7423f116 1394 }
797cedc7
SH
1395 if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_GLOBAL)) {
1396 $courses[] = SITEID;
7423f116 1397 }
797cedc7
SH
1398 $courses = array_unique($courses);
1399 sort($courses);
7c50db30 1400
797cedc7 1401 if (!empty($courses) && in_array(SITEID, $courses)) {
7c50db30 1402 // Sort courses for consistent colour highlighting
1403 // Effectively ignoring SITEID as setting as last course id
1404 $key = array_search(SITEID, $courses);
797cedc7
SH
1405 unset($courses[$key]);
1406 $courses[] = SITEID;
0e6a8f4b 1407 }
7423f116 1408
797cedc7
SH
1409 if ($ignorefilters || ($isloggedin && calendar_show_event_type(CALENDAR_EVENT_USER))) {
1410 $user = $USER->id;
7423f116 1411 }
257e3f4c 1412
797cedc7
SH
1413 if (!empty($courseeventsfrom) && (calendar_show_event_type(CALENDAR_EVENT_GROUP) || $ignorefilters)) {
1414
7fd5786e
HB
1415 if (count($courseeventsfrom)==1) {
1416 $course = reset($courseeventsfrom);
6ca657a7 1417 if (has_any_capability($allgroupscaps, context_course::instance($course->id))) {
7fd5786e
HB
1418 $coursegroups = groups_get_all_groups($course->id, 0, 0, 'g.id');
1419 $group = array_keys($coursegroups);
7423f116 1420 }
7fd5786e
HB
1421 }
1422 if ($group === false) {
1423 if (!empty($CFG->calendar_adminseesall) && has_any_capability($allgroupscaps, get_system_context())) {
1424 $group = true;
1425 } else if ($isloggedin) {
1426 $groupids = array();
c46da759 1427
7fd5786e
HB
1428 // We already have the courses to examine in $courses
1429 // For each course...
1430 foreach ($courseeventsfrom as $courseid => $course) {
1431 // If the user is an editing teacher in there,
1432 if (!empty($USER->groupmember[$course->id])) {
1433 // We've already cached the users groups for this course so we can just use that
1434 $groupids = array_merge($groupids, $USER->groupmember[$course->id]);
1435 } else if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
1436 // If this course has groups, show events from all of those related to the current user
1437 $coursegroups = groups_get_user_groups($course->id, $USER->id);
1438 $groupids = array_merge($groupids, $coursegroups['0']);
1439 }
1440 }
1441 if (!empty($groupids)) {
1442 $group = $groupids;
1443 }
6c9584d1 1444 }
7423f116 1445 }
7423f116 1446 }
797cedc7
SH
1447 if (empty($courses)) {
1448 $courses = false;
7423f116 1449 }
797cedc7
SH
1450
1451 return array($courses, $group, $user);
7423f116 1452}
1453
08b4a4e1
RW
1454/**
1455 * Return the capability for editing calendar event
1456 *
1457 * @param calendar_event $event event object
1458 * @return bool capability to edit event
1459 */
7423f116 1460function calendar_edit_event_allowed($event) {
62d11d77 1461 global $USER, $DB;
7423f116 1462
2ac8da76 1463 // Must be logged in
1464 if (!isloggedin()) {
1465 return false;
1466 }
1467
89491dbd 1468 // can not be using guest account
2396a414 1469 if (isguestuser()) {
e295df44 1470 return false;
89491dbd 1471 }
e295df44 1472
e6059873
SH
1473 // You cannot edit calendar subscription events presently.
1474 if (!empty($event->subscriptionid)) {
1475 return false;
1476 }
1477
6ca657a7 1478 $sitecontext = context_system::instance();
89491dbd 1479 // if user has manageentries at site level, return true
28ee98c5 1480 if (has_capability('moodle/calendar:manageentries', $sitecontext)) {
89491dbd 1481 return true;
f52f7413 1482 }
e295df44 1483
c0a2c361 1484 // if groupid is set, it's definitely a group event
438e4add 1485 if (!empty($event->groupid)) {
f63d2922 1486 // Allow users to add/edit group events if:
1487 // 1) They have manageentries (= entries for whole course)
1488 // 2) They have managegroupentries AND are in the group
62d11d77 1489 $group = $DB->get_record('groups', array('id'=>$event->groupid));
f63d2922 1490 return $group && (
f4700b91
RW
1491 has_capability('moodle/calendar:manageentries', $event->context) ||
1492 (has_capability('moodle/calendar:managegroupentries', $event->context)
f63d2922 1493 && groups_is_member($event->groupid)));
438e4add 1494 } else if (!empty($event->courseid)) {
c0a2c361 1495 // if groupid is not set, but course is set,
1496 // it's definiely a course event
f4700b91 1497 return has_capability('moodle/calendar:manageentries', $event->context);
438e4add 1498 } else if (!empty($event->userid) && $event->userid == $USER->id) {
c0a2c361 1499 // if course is not set, but userid id set, it's a user event
f4700b91
RW
1500 return (has_capability('moodle/calendar:manageownentries', $event->context));
1501 } else if (!empty($event->userid)) {
1502 return (has_capability('moodle/calendar:manageentries', $event->context));
e295df44 1503 }
7423f116 1504 return false;
1505}
1506
797cedc7
SH
1507/**
1508 * Returns the default courses to display on the calendar when there isn't a specific
1509 * course to display.
1510 *
08b4a4e1 1511 * @return array $courses Array of courses to display
797cedc7
SH
1512 */
1513function calendar_get_default_courses() {
1514 global $CFG, $DB;
7423f116 1515
4f0c2d00 1516 if (!isloggedin()) {
2ef75eee 1517 return array();
1518 }
1519
7423f116 1520 $courses = array();
d083084a 1521 if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', context_system::instance())) {
797cedc7 1522 list ($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
d083084a 1523 $sql = "SELECT c.* $select
797cedc7 1524 FROM {course} c
d083084a
ARN
1525 $join
1526 WHERE EXISTS (SELECT 1 FROM {event} e WHERE e.courseid = c.id)
1527 ";
797cedc7
SH
1528 $courses = $DB->get_records_sql($sql, null, 0, 20);
1529 foreach ($courses as $course) {
d083084a 1530 context_helper::preload_from_record($course);
86f092d2 1531 }
797cedc7 1532 return $courses;
95a89225 1533 }
e295df44 1534
df997f84 1535 $courses = enrol_get_my_courses();
37d87d11 1536
1537 return $courses;
7423f116 1538}
1539
08b4a4e1
RW
1540/**
1541 * Display calendar preference button
1542 *
1543 * @param stdClass $course course object
1544 * @return string return preference button in html
1545 */
797cedc7
SH
1546function calendar_preferences_button(stdClass $course) {
1547 global $OUTPUT;
7423f116 1548
1549 // Guests have no preferences
4f0c2d00 1550 if (!isloggedin() || isguestuser()) {
7423f116 1551 return '';
1552 }
1553
797cedc7 1554 return $OUTPUT->single_button(new moodle_url('/calendar/preferences.php', array('course' => $course->id)), get_string("preferences", "calendar"));
7423f116 1555}
1556
08b4a4e1
RW
1557/**
1558 * Get event format time
1559 *
1560 * @param calendar_event $event event object
1561 * @param int $now current time in gmt
1562 * @param array $linkparams list of params for event link
1563 * @param bool $usecommonwords the words as formatted date/time.
1564 * @param int $showtime determine the show time GMT timestamp
1565 * @return string $eventtime link/string for event time
1566 */
0f927f1e 1567function calendar_format_event_time($event, $now, $linkparams = null, $usecommonwords = true, $showtime=0) {
8f896582 1568 $startdate = usergetdate($event->timestart);
1569 $enddate = usergetdate($event->timestart + $event->timeduration);
1570 $usermidnightstart = usergetmidnight($event->timestart);
1571
1572 if($event->timeduration) {
1573 // To avoid doing the math if one IF is enough :)
1574 $usermidnightend = usergetmidnight($event->timestart + $event->timeduration);
1575 }
1576 else {
1577 $usermidnightend = $usermidnightstart;
1578 }
1579
0f927f1e
SH
1580 if (empty($linkparams) || !is_array($linkparams)) {
1581 $linkparams = array();
1582 }
1583 $linkparams['view'] = 'day';
1584
8f896582 1585 // OK, now to get a meaningful display...
1586 // First of all we have to construct a human-readable date/time representation
1587
b4892fa2 1588 if($event->timeduration) {
8f896582 1589 // It has a duration
b4892fa2 1590 if($usermidnightstart == $usermidnightend ||
1591 ($event->timestart == $usermidnightstart) && ($event->timeduration == 86400 || $event->timeduration == 86399) ||
1592 ($event->timestart + $event->timeduration <= $usermidnightstart + 86400)) {
8f896582 1593 // But it's all on the same day
8f896582 1594 $timestart = calendar_time_representation($event->timestart);
1595 $timeend = calendar_time_representation($event->timestart + $event->timeduration);
b4892fa2 1596 $time = $timestart.' <strong>&raquo;</strong> '.$timeend;
1597
1598 if ($event->timestart == $usermidnightstart && ($event->timeduration == 86400 || $event->timeduration == 86399)) {
1599 $time = get_string('allday', 'calendar');
1600 }
8f896582 1601
1602 // Set printable representation
b4892fa2 1603 if (!$showtime) {
1604 $day = calendar_day_representation($event->timestart, $now, $usecommonwords);
0f927f1e
SH
1605 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $enddate['mday'], $enddate['mon'], $enddate['year']);
1606 $eventtime = html_writer::link($url, $day).', '.$time;
b4892fa2 1607 } else {
1608 $eventtime = $time;
1609 }
1610 } else {
8f896582 1611 // It spans two or more days
b4892fa2 1612 $daystart = calendar_day_representation($event->timestart, $now, $usecommonwords).', ';
1613 if ($showtime == $usermidnightstart) {
1614 $daystart = '';
1615 }
8f896582 1616 $timestart = calendar_time_representation($event->timestart);
b4892fa2 1617 $dayend = calendar_day_representation($event->timestart + $event->timeduration, $now, $usecommonwords).', ';
1618 if ($showtime == $usermidnightend) {
1619 $dayend = '';
1620 }
8f896582 1621 $timeend = calendar_time_representation($event->timestart + $event->timeduration);
1622
1623 // Set printable representation
b4892fa2 1624 if ($now >= $usermidnightstart && $now < ($usermidnightstart + 86400)) {
0f927f1e
SH
1625 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $enddate['mday'], $enddate['mon'], $enddate['year']);
1626 $eventtime = $timestart.' <strong>&raquo;</strong> '.html_writer::link($url, $dayend).$timeend;
b4892fa2 1627 } else {
0f927f1e
SH
1628 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $enddate['mday'], $enddate['mon'], $enddate['year']);
1629 $eventtime = html_writer::link($url, $daystart).$timestart.' <strong>&raquo;</strong> ';
1d5bd3d2 1630
0f927f1e
SH
1631 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $startdate['mday'], $startdate['mon'], $startdate['year']);
1632 $eventtime .= html_writer::link($url, $dayend).$timeend;
b4892fa2 1633 }
8f896582 1634 }
b4892fa2 1635 } else {
2af268a1 1636 $time = calendar_time_representation($event->timestart);
8f896582 1637
1638 // Set printable representation
b4892fa2 1639 if (!$showtime) {
1640 $day = calendar_day_representation($event->timestart, $now, $usecommonwords);
0f927f1e 1641 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $startdate['mday'], $startdate['mon'], $startdate['year']);
2af268a1 1642 $eventtime = html_writer::link($url, $day).', '.trim($time);
b4892fa2 1643 } else {
1644 $eventtime = $time;
1645 }
1646 }
1647
1648 if($event->timestart + $event->timeduration < $now) {
1649 // It has expired
1650 $eventtime = '<span class="dimmed_text">'.str_replace(' href=', ' class="dimmed" href=', $eventtime).'</span>';
8f896582 1651 }
09d36284 1652
8f896582 1653 return $eventtime;
1654}
054193be 1655
08b4a4e1
RW
1656/**
1657 * Display month selector options
1658 *
1659 * @param string $name for the select element
1660 * @param string|array $selected options for select elements
1661 */
86f092d2 1662function calendar_print_month_selector($name, $selected) {
86f092d2 1663 $months = array();
86f092d2 1664 for ($i=1; $i<=12; $i++) {
76ab1c33 1665 $months[$i] = userdate(gmmktime(12, 0, 0, $i, 15, 2000), '%B');
86f092d2 1666 }
5c576552 1667 echo html_writer::label(get_string('months'), 'menu'. $name, false, array('class' => 'accesshide'));
d776d59e 1668 echo html_writer::select($months, $name, $selected, false);
86f092d2 1669}
1670
797cedc7
SH
1671/**
1672 * Checks to see if the requested type of event should be shown for the given user.
1673 *
1674 * @param CALENDAR_EVENT_GLOBAL|CALENDAR_EVENT_COURSE|CALENDAR_EVENT_GROUP|CALENDAR_EVENT_USER $type
1675 * The type to check the display for (default is to display all)
1676 * @param stdClass|int|null $user The user to check for - by default the current user
1677 * @return bool True if the tyep should be displayed false otherwise
1678 */
1679function calendar_show_event_type($type, $user = null) {
1680 $default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
1681 if (get_user_preferences('calendar_persistflt', 0, $user) === 0) {
1682 global $SESSION;
1683 if (!isset($SESSION->calendarshoweventtype)) {
1684 $SESSION->calendarshoweventtype = $default;
1685 }
1686 return $SESSION->calendarshoweventtype & $type;
1687 } else {
1688 return get_user_preferences('calendar_savedflt', $default, $user) & $type;
054193be 1689 }
797cedc7
SH
1690}
1691
1692/**
1693 * Sets the display of the event type given $display.
08b4a4e1 1694 *
797cedc7
SH
1695 * If $display = true the event type will be shown.
1696 * If $display = false the event type will NOT be shown.
1697 * If $display = null the current value will be toggled and saved.
1698 *
08b4a4e1
RW
1699 * @param CALENDAR_EVENT_GLOBAL|CALENDAR_EVENT_COURSE|CALENDAR_EVENT_GROUP|CALENDAR_EVENT_USER $type object of CALENDAR_EVENT_XXX
1700 * @param bool $display option to display event type
1701 * @param stdClass|int $user moodle user object or id, null means current user
797cedc7
SH
1702 */
1703function calendar_set_event_type_display($type, $display = null, $user = null) {
1704 $persist = get_user_preferences('calendar_persistflt', 0, $user);
1705 $default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
1706 if ($persist === 0) {
1707 global $SESSION;
1708 if (!isset($SESSION->calendarshoweventtype)) {
1709 $SESSION->calendarshoweventtype = $default;
1710 }
1711 $preference = $SESSION->calendarshoweventtype;
1712 } else {
1713 $preference = get_user_preferences('calendar_savedflt', $default, $user);
054193be 1714 }
797cedc7
SH
1715 $current = $preference & $type;
1716 if ($display === null) {
1717 $display = !$current;
054193be 1718 }
797cedc7
SH
1719 if ($display && !$current) {
1720 $preference += $type;
1721 } else if (!$display && $current) {
1722 $preference -= $type;
054193be 1723 }
797cedc7
SH
1724 if ($persist === 0) {
1725 $SESSION->calendarshoweventtype = $preference;
1726 } else {
1727 if ($preference == $default) {
1728 unset_user_preference('calendar_savedflt', $user);
1729 } else {
1730 set_user_preference('calendar_savedflt', $preference, $user);
1731 }
054193be 1732 }
054193be 1733}
1734
08b4a4e1
RW
1735/**
1736 * Get calendar's allowed types
1737 *
1738 * @param stdClass $allowed list of allowed edit for event type
1739 * @param stdClass|int $course object of a course or course id
1740 */
797cedc7
SH
1741function calendar_get_allowed_types(&$allowed, $course = null) {
1742 global $USER, $CFG, $DB;
b85b25eb 1743 $allowed = new stdClass();
797cedc7 1744 $allowed->user = has_capability('moodle/calendar:manageownentries', get_system_context());
86ac8b24 1745 $allowed->groups = false; // This may change just below
1746 $allowed->courses = false; // This may change just below
6ca657a7 1747 $allowed->site = has_capability('moodle/calendar:manageentries', context_course::instance(SITEID));
86ac8b24 1748
797cedc7
SH
1749 if (!empty($course)) {
1750 if (!is_object($course)) {
1751 $course = $DB->get_record('course', array('id' => $course), '*', MUST_EXIST);
1752 }
1753 if ($course->id != SITEID) {
6ca657a7 1754 $coursecontext = context_course::instance($course->id);
3c0ea9b2 1755 $allowed->user = has_capability('moodle/calendar:manageownentries', $coursecontext);
86ac8b24 1756
797cedc7
SH
1757 if (has_capability('moodle/calendar:manageentries', $coursecontext)) {
1758 $allowed->courses = array($course->id => 1);
aa6c1ced 1759
797cedc7 1760 if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
462e42cb
JP
1761 if (has_capability('moodle/site:accessallgroups', $coursecontext)) {
1762 $allowed->groups = groups_get_all_groups($course->id);
1763 } else {
1764 $allowed->groups = groups_get_all_groups($course->id, $USER->id);
1765 }
797cedc7
SH
1766 }
1767 } else if (has_capability('moodle/calendar:managegroupentries', $coursecontext)) {
1768 if($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
462e42cb
JP
1769 if (has_capability('moodle/site:accessallgroups', $coursecontext)) {
1770 $allowed->groups = groups_get_all_groups($course->id);
1771 } else {
1772 $allowed->groups = groups_get_all_groups($course->id, $USER->id);
1773 }
797cedc7 1774 }
f63d2922 1775 }
86ac8b24 1776 }
1777 }
1778}
1779
1780/**
08b4a4e1 1781 * See if user can add calendar entries at all
86ac8b24 1782 * used to print the "New Event" button
08b4a4e1
RW
1783 *
1784 * @param stdClass $course object of a course or course id
1785 * @return bool has the capability to add at least one event type
86ac8b24 1786 */
797cedc7
SH
1787function calendar_user_can_add_event($course) {
1788 if (!isloggedin() || isguestuser()) {
1789 return false;
1790 }
1791 calendar_get_allowed_types($allowed, $course);
e295df44 1792 return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->site);
86ac8b24 1793}
76d9df3f
SH
1794
1795/**
1796 * Check wether the current user is permitted to add events
1797 *
08b4a4e1
RW
1798 * @param stdClass $event object of event
1799 * @return bool has the capability to add event
76d9df3f
SH
1800 */
1801function calendar_add_event_allowed($event) {
1802 global $USER, $DB;
1803
1804 // can not be using guest account
4f0c2d00 1805 if (!isloggedin() or isguestuser()) {
76d9df3f
SH
1806 return false;
1807 }
1808
6ca657a7 1809 $sitecontext = context_system::instance();
76d9df3f
SH
1810 // if user has manageentries at site level, always return true
1811 if (has_capability('moodle/calendar:manageentries', $sitecontext)) {
1812 return true;
1813 }
1814
1815 switch ($event->eventtype) {
1816 case 'course':
f4700b91 1817 return has_capability('moodle/calendar:manageentries', $event->context);
76d9df3f
SH
1818
1819 case 'group':
1820 // Allow users to add/edit group events if:
1821 // 1) They have manageentries (= entries for whole course)
1822 // 2) They have managegroupentries AND are in the group
1823 $group = $DB->get_record('groups', array('id'=>$event->groupid));
1824 return $group && (
f4700b91
RW
1825 has_capability('moodle/calendar:manageentries', $event->context) ||
1826 (has_capability('moodle/calendar:managegroupentries', $event->context)
76d9df3f
SH
1827 && groups_is_member($event->groupid)));
1828
1829 case 'user':
1830 if ($event->userid == $USER->id) {
f4700b91 1831 return (has_capability('moodle/calendar:manageownentries', $event->context));
76d9df3f
SH
1832 }
1833 //there is no 'break;' intentionally
1834
1835 case 'site':
f4700b91 1836 return has_capability('moodle/calendar:manageentries', $event->context);
76d9df3f
SH
1837
1838 default:
f4700b91 1839 return has_capability('moodle/calendar:manageentries', $event->context);
76d9df3f
SH
1840 }
1841}
1842
1843/**
08b4a4e1 1844 * Manage calendar events
76d9df3f
SH
1845 *
1846 * This class provides the required functionality in order to manage calendar events.
1847 * It was introduced as part of Moodle 2.0 and was created in order to provide a
1848 * better framework for dealing with calendar events in particular regard to file
1849 * handling through the new file API
1850 *
08b4a4e1
RW
1851 * @package core_calendar
1852 * @category calendar
1853 * @copyright 2009 Sam Hemelryk
1854 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1855 *
76d9df3f
SH
1856 * @property int $id The id within the event table
1857 * @property string $name The name of the event
1858 * @property string $description The description of the event
1859 * @property int $format The format of the description FORMAT_?
1860 * @property int $courseid The course the event is associated with (0 if none)
1861 * @property int $groupid The group the event is associated with (0 if none)
1862 * @property int $userid The user the event is associated with (0 if none)
1863 * @property int $repeatid If this is a repeated event this will be set to the
1864 * id of the original
1865 * @property string $modulename If added by a module this will be the module name
1866 * @property int $instance If added by a module this will be the module instance
1867 * @property string $eventtype The event type
1868 * @property int $timestart The start time as a timestamp
1869 * @property int $timeduration The duration of the event in seconds
1870 * @property int $visible 1 if the event is visible
1871 * @property int $uuid ?
1872 * @property int $sequence ?
1873 * @property int $timemodified The time last modified as a timestamp
1874 */
1875class calendar_event {
1876
08b4a4e1 1877 /** @var array An object containing the event properties can be accessed via the magic __get/set methods */
76d9df3f 1878 protected $properties = null;
08b4a4e1 1879
76d9df3f 1880 /**
08b4a4e1 1881 * @var string The converted event discription with file paths resolved. This gets populated when someone requests description for the first time */
76d9df3f 1882 protected $_description = null;
08b4a4e1
RW
1883
1884 /** @var array The options to use with this description editor */
76d9df3f
SH
1885 protected $editoroptions = array(
1886 'subdirs'=>false,
1887 'forcehttps'=>false,
2b35ed1a 1888 'maxfiles'=>-1,
76d9df3f
SH
1889 'maxbytes'=>null,
1890 'trusttext'=>false);
08b4a4e1
RW
1891
1892 /** @var object The context to use with the description editor */
76d9df3f
SH
1893 protected $editorcontext = null;
1894
1895 /**
1896 * Instantiates a new event and optionally populates its properties with the
1897 * data provided
1898 *
1899 * @param stdClass $data Optional. An object containing the properties to for
1900 * an event
1901 */
1902 public function __construct($data=null) {
c203a277 1903 global $CFG, $USER;
76d9df3f
SH
1904
1905 // First convert to object if it is not already (should either be object or assoc array)
1906 if (!is_object($data)) {
1907 $data = (object)$data;
1908 }
1909
1910 $this->editoroptions['maxbytes'] = $CFG->maxbytes;
1911
1912 $data->eventrepeats = 0;
1913
1914 if (empty($data->id)) {
1915 $data->id = null;
1916 }
1917
c203a277
SH
1918 // Default to a user event
1919 if (empty($data->eventtype)) {
1920 $data->eventtype = 'user';
1921 }
1922
1923 // Default to the current user
1924 if (empty($data->userid)) {
1925 $data->userid = $USER->id;
1926 }
1927
76d9df3f
SH
1928 if (!empty($data->timeduration) && is_array($data->timeduration)) {
1929 $data->timeduration = make_timestamp($data->timeduration['year'], $data->timeduration['month'], $data->timeduration['day'], $data->timeduration['hour'], $data->timeduration['minute']) - $data->timestart;
1930 }
1931 if (!empty($data->description) && is_array($data->description)) {
1932 $data->format = $data->description['format'];
1933 $data->description = $data->description['text'];
1934 } else if (empty($data->description)) {
1935 $data->description = '';
20e5da7d 1936 $data->format = editors_get_preferred_format();
76d9df3f 1937 }
c203a277
SH
1938 // Ensure form is defaulted correctly
1939 if (empty($data->format)) {
1940 $data->format = editors_get_preferred_format();
1941 }
76d9df3f 1942
f4700b91
RW
1943 if (empty($data->context)) {
1944 $data->context = $this->calculate_context($data);
1945 }
76d9df3f
SH
1946 $this->properties = $data;
1947 }
1948
1949 /**
1950 * Magic property method
1951 *
1952 * Attempts to call a set_$key method if one exists otherwise falls back
1953 * to simply set the property
1954 *
08b4a4e1
RW
1955 * @param string $key property name
1956 * @param mixed $value value of the property
76d9df3f
SH
1957 */
1958 public function __set($key, $value) {
1959 if (method_exists($this, 'set_'.$key)) {
1960 $this->{'set_'.$key}($value);
1961 }
1962 $this->properties->{$key} = $value;
1963 }
1964
1965 /**
1966 * Magic get method
1967 *
1968 * Attempts to call a get_$key method to return the property and ralls over
1969 * to return the raw property
1970 *
08b4a4e1
RW
1971 * @param string $key property name
1972 * @return mixed property value
76d9df3f
SH
1973 */
1974 public function __get($key) {
1975 if (method_exists($this, 'get_'.$key)) {
1976 return $this->{'get_'.$key}();
1977 }
c203a277
SH
1978 if (!isset($this->properties->{$key})) {
1979 throw new coding_exception('Undefined property requested');
1980 }
76d9df3f
SH
1981 return $this->properties->{$key};
1982 }
1983
1984 /**
1985 * Stupid PHP needs an isset magic method if you use the get magic method and
1986 * still want empty calls to work.... blah ~!
1987 *
08b4a4e1
RW
1988 * @param string $key $key property name
1989 * @return bool|mixed property value, false if property is not exist
76d9df3f
SH
1990 */
1991 public function __isset($key) {
1992 return !empty($this->properties->{$key});
1993 }
1994
f4700b91
RW
1995 /**
1996 * Calculate the context value needed for calendar_event.
1997 * Event's type can be determine by the available value store in $data
1998 * It is important to check for the existence of course/courseid to determine
1999 * the course event.
2000 * Default value is set to CONTEXT_USER
2001 *
08b4a4e1
RW
2002 * @param stdClass $data information about event
2003 * @return stdClass The context object.
f4700b91
RW
2004 */
2005 protected function calculate_context(stdClass $data) {
025d82e0 2006 global $USER, $DB;
f4700b91 2007
f4700b91
RW
2008 $context = null;
2009 if (isset($data->courseid) && $data->courseid > 0) {
6ca657a7 2010 $context = context_course::instance($data->courseid);
f4700b91 2011 } else if (isset($data->course) && $data->course > 0) {
6ca657a7 2012 $context = context_course::instance($data->course);
f4700b91
RW
2013 } else if (isset($data->groupid) && $data->groupid > 0) {
2014 $group = $DB->get_record('groups', array('id'=>$data->groupid));
6ca657a7 2015 $context = context_course::instance($group->courseid);
f4700b91 2016 } else if (isset($data->userid) && $data->userid > 0 && $data->userid == $USER->id) {
6ca657a7 2017 $context = context_user::instance($data->userid);
f4700b91
RW
2018 } else if (isset($data->userid) && $data->userid > 0 && $data->userid != $USER->id &&
2019 isset($data->instance) && $data->instance > 0) {
18d63ffb 2020 $cm = get_coursemodule_from_instance($data->modulename, $data->instance, 0, false, MUST_EXIST);
6ca657a7 2021 $context = context_course::instance($cm->course);
f4700b91 2022 } else {
e30390a0 2023 $context = context_user::instance($data->userid);
f4700b91
RW
2024 }
2025
2026 return $context;
2027 }
2028
76d9df3f
SH
2029 /**
2030 * Returns an array of editoroptions for this event: Called by __get
2031 * Please use $blah = $event->editoroptions;
08b4a4e1
RW
2032 *
2033 * @return array event editor options
76d9df3f
SH
2034 */
2035 protected function get_editoroptions() {
2036 return $this->editoroptions;
2037 }
2038
2039 /**
2040 * Returns an event description: Called by __get
2041 * Please use $blah = $event->description;
2042 *
08b4a4e1 2043 * @return string event description
76d9df3f
SH
2044 */
2045 protected function get_description() {
f4700b91 2046 global $CFG;
99d19c13
PS
2047
2048 require_once($CFG->libdir . '/filelib.php');
2049
76d9df3f
SH
2050 if ($this->_description === null) {
2051 // Check if we have already resolved the context for this event
2052 if ($this->editorcontext === null) {
2053 // Switch on the event type to decide upon the appropriate context
2054 // to use for this event
f4700b91
RW
2055 $this->editorcontext = $this->properties->context;
2056 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
2057 && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
2058 return clean_text($this->properties->description, $this->properties->format);
76d9df3f
SH
2059 }
2060 }
2061
2062 // Work out the item id for the editor, if this is a repeated event then the files will
2063 // be associated with the original
2064 if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
2065 $itemid = $this->properties->repeatid;
2066 } else {
2067 $itemid = $this->properties->id;
2068 }
2069
2070 // Convert file paths in the description so that things display correctly
64f93798 2071 $this->_description = file_rewrite_pluginfile_urls($this->properties->description, 'pluginfile.php', $this->editorcontext->id, 'calendar', 'event_description', $itemid);
76d9df3f
SH
2072 // Clean the text so no nasties get through
2073 $this->_description = clean_text($this->_description, $this->properties->format);
2074 }
2075 // Finally return the description
2076 return $this->_description;
2077 }
2078
2079 /**
2080 * Return the number of repeat events there are in this events series
aa6c1ced 2081 *
08b4a4e1 2082 * @return int number of event repeated
76d9df3f
SH
2083 */
2084 public function count_repeats() {
2085 global $DB;
2086 if (!empty($this->properties->repeatid)) {
2087 $this->properties->eventrepeats = $DB->count_records('event', array('repeatid'=>$this->properties->repeatid));
2088 // We don't want to count ourselves
2089 $this->properties->eventrepeats--;
2090 }
2091 return $this->properties->eventrepeats;
2092 }
2093
2094 /**
2095 * Update or create an event within the database
2096 *
2097 * Pass in a object containing the event properties and this function will
2098 * insert it into the database and deal with any associated files
2099 *
2100 * @see add_event()
2101 * @see update_event()
2102 *
08b4a4e1
RW
2103 * @param stdClass $data object of event
2104 * @param bool $checkcapability if moodle should check calendar managing capability or not
2105 * @return bool event updated
76d9df3f 2106 */
1d5bd3d2 2107 public function update($data, $checkcapability=true) {
76d9df3f
SH
2108 global $CFG, $DB, $USER;
2109
438e4add
SH
2110 foreach ($data as $key=>$value) {
2111 $this->properties->$key = $value;
2112 }
2113
76d9df3f
SH
2114 $this->properties->timemodified = time();
2115 $usingeditor = (!empty($this->properties->description) && is_array($this->properties->description));
2116
2117 if (empty($this->properties->id) || $this->properties->id < 1) {
2118
1d5bd3d2
DC
2119 if ($checkcapability) {
2120 if (!calendar_add_event_allowed($this->properties)) {
2121 print_error('nopermissiontoupdatecalendar');
2122 }
76d9df3f
SH
2123 }
2124
2125 if ($usingeditor) {
2126 switch ($this->properties->eventtype) {
2127 case 'user':
76d9df3f 2128 $this->properties->courseid = 0;
f19d57e5 2129 $this->properties->course = 0;
76d9df3f
SH
2130 $this->properties->groupid = 0;
2131 $this->properties->userid = $USER->id;
2132 break;
2133 case 'site':
76d9df3f 2134 $this->properties->courseid = SITEID;
f19d57e5 2135 $this->properties->course = SITEID;
76d9df3f
SH
2136 $this->properties->groupid = 0;
2137 $this->properties->userid = $USER->id;
2138 break;
2139 case 'course':
76d9df3f
SH
2140 $this->properties->groupid = 0;
2141 $this->properties->userid = $USER->id;
2142 break;
2143 case 'group':
76d9df3f
SH
2144 $this->properties->userid = $USER->id;
2145 break;
2146 default:
2147 // Ewww we should NEVER get here, but just incase we do lets
2148 // fail gracefully
2149 $usingeditor = false;
2150 break;
2151 }
2152
f19d57e5
FM
2153 // If we are actually using the editor, we recalculate the context because some default values
2154 // were set when calculate_context() was called from the constructor.
2155 if ($usingeditor) {
2156 $this->properties->context = $this->calculate_context($this->properties);
2157 $this->editorcontext = $this->properties->context;
2158 }
2159
76d9df3f
SH
2160 $editor = $this->properties->description;
2161 $this->properties->format = $this->properties->description['format'];
2162 $this->properties->description = $this->properties->description['text'];
2163 }
2164
2165 // Insert the event into the database
2166 $this->properties->id = $DB->insert_record('event', $this->properties);
2167
2168 if ($usingeditor) {
2169 $this->properties->description = file_save_draft_area_files(
2170 $editor['itemid'],
2171 $this->editorcontext->id,
64f93798
PS
2172 'calendar',
2173 'event_description',
76d9df3f
SH
2174 $this->properties->id,
2175 $this->editoroptions,
2176 $editor['text'],
2177 $this->editoroptions['forcehttps']);
76d9df3f
SH
2178 $DB->set_field('event', 'description', $this->properties->description, array('id'=>$this->properties->id));
2179 }
aa6c1ced 2180
76d9df3f
SH
2181 // Log the event entry.
2182 add_to_log($this->properties->courseid, 'calendar', 'add', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2183
2184 $repeatedids = array();
2185
2186 if (!empty($this->properties->repeat)) {
2187 $this->properties->repeatid = $this->properties->id;
2188 $DB->set_field('event', 'repeatid', $this->properties->repeatid, array('id'=>$this->properties->id));
2189
2190 $eventcopy = clone($this->properties);
2191 unset($eventcopy->id);
2192
2193 for($i = 1; $i < $eventcopy->repeats; $i++) {
2194
2195 $eventcopy->timestart = ($eventcopy->timestart+WEEKSECS) + dst_offset_on($eventcopy->timestart) - dst_offset_on($eventcopy->timestart+WEEKSECS);
2196
2197 // Get the event id for the log record.
2198 $eventcopyid = $DB->insert_record('event', $eventcopy);
2199
2200 // If the context has been set delete all associated files
2201 if ($usingeditor) {
2202 $fs = get_file_storage();
64f93798 2203 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
76d9df3f
SH
2204 foreach ($files as $file) {
2205 $fs->create_file_from_storedfile(array('itemid'=>$eventcopyid), $file);
2206 }
2207 }
2208
2209 $repeatedids[] = $eventcopyid;
2210 // Log the event entry.
2211 add_to_log($eventcopy->courseid, 'calendar', 'add', 'event.php?action=edit&amp;id='.$eventcopyid, $eventcopy->name);
2212 }
2213 }
2214
2215 // Hook for tracking added events
2216 self::calendar_event_hook('add_event', array($this->properties, $repeatedids));
2217 return true;
2218 } else {
2219
1d5bd3d2
DC
2220 if ($checkcapability) {
2221 if(!calendar_edit_event_allowed($this->properties)) {
2222 print_error('nopermissiontoupdatecalendar');
2223 }
76d9df3f
SH
2224 }
2225
2226 if ($usingeditor) {
2227 if ($this->editorcontext !== null) {
2228 $this->properties->description = file_save_draft_area_files(
2229 $this->properties->description['itemid'],
2230 $this->editorcontext->id,
64f93798
PS
2231 'calendar',
2232 'event_description',
76d9df3f
SH
2233 $this->properties->id,
2234 $this->editoroptions,
2235 $this->properties->description['text'],
2236 $this->editoroptions['forcehttps']);
2237 } else {
2238 $this->properties->format = $this->properties->description['format'];
2239 $this->properties->description = $this->properties->description['text'];
2240 }
2241 }
2242
2243 $event = $DB->get_record('event', array('id'=>$this->properties->id));
2244
2245 $updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
2246
2247 if ($updaterepeated) {
2248 // Update all
2249 if ($this->properties->timestart != $event->timestart) {
2250 $timestartoffset = $this->properties->timestart - $event->timestart;
2251 $sql = "UPDATE {event}
2252 SET name = ?,
2253 description = ?,
2254 timestart = timestart + ?,
2255 timeduration = ?,
2256 timemodified = ?
2257 WHERE repeatid = ?";
2258 $params = array($this->properties->name, $this->properties->description, $timestartoffset, $this->properties->timeduration, time(), $event->repeatid);
2259 } else {
2260 $sql = "UPDATE {event} SET name = ?, description = ?, timeduration = ?, timemodified = ? WHERE repeatid = ?";
2261 $params = array($this->properties->name, $this->properties->description, $this->properties->timeduration, time(), $event->repeatid);
2262 }
2263 $DB->execute($sql, $params);
2264
2265 // Log the event update.
2266 add_to_log($this->properties->courseid, 'calendar', 'edit all', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2267 } else {
2268 $DB->update_record('event', $this->properties);
ddaff608
SH
2269 $event = calendar_event::load($this->properties->id);
2270 $this->properties = $event->properties();
76d9df3f
SH
2271 add_to_log($this->properties->courseid, 'calendar', 'edit', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2272 }
2273
2274 // Hook for tracking event updates
2275 self::calendar_event_hook('update_event', array($this->properties, $updaterepeated));
2276 return true;
2277 }
2278 }
2279
2280 /**
2281 * Deletes an event and if selected an repeated events in the same series
2282 *
2283 * This function deletes an event, any associated events if $deleterepeated=true,
2284 * and cleans up any files associated with the events.
2285 *
2286 * @see delete_event()
2287 *
08b4a4e1
RW
2288 * @param bool $deleterepeated delete event repeatedly
2289 * @return bool succession of deleting event
76d9df3f
SH
2290 */
2291 public function delete($deleterepeated=false) {
f4700b91 2292 global $DB;
76d9df3f
SH
2293
2294 // If $this->properties->id is not set then something is wrong
2295 if (empty($this->properties->id)) {
2296 debugging('Attempting to delete an event before it has been loaded', DEBUG_DEVELOPER);
2297 return false;
2298 }
2299
2300 // Delete the event
2301 $DB->delete_records('event', array('id'=>$this->properties->id));
2302
ff284fac
AA
2303 // If we are deleting parent of a repeated event series, promote the next event in the series as parent
2304 if (($this->properties->id == $this->properties->repeatid) && !$deleterepeated) {
2305 $newparent = $DB->get_field_sql("SELECT id from {event} where repeatid = ? order by id ASC", array($this->properties->id), IGNORE_MULTIPLE);
2306 if (!empty($newparent)) {
2307 $DB->execute("UPDATE {event} SET repeatid = ? WHERE repeatid = ?", array($newparent, $this->properties->id));
5ff114ad
AA
2308 // Get all records where the repeatid is the same as the event being removed
2309 $events = $DB->get_records('event', array('repeatid' => $newparent));
2310 // For each of the returned events trigger the event_update hook.
2311 foreach ($events as $event) {
2312 self::calendar_event_hook('update_event', array($event, false));
2313 }
ff284fac
AA
2314 }
2315 }
2316
76d9df3f
SH
2317 // If the editor context hasn't already been set then set it now
2318 if ($this->editorcontext === null) {
f4700b91 2319 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2320 }
2321
2322 // If the context has been set delete all associated files
2323 if ($this->editorcontext !== null) {
2324 $fs = get_file_storage();
64f93798 2325 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
76d9df3f
SH
2326 foreach ($files as $file) {
2327 $file->delete();
2328 }
2329 }
2330
2331 // Fire the event deleted hook
2332 self::calendar_event_hook('delete_event', array($this->properties->id, $deleterepeated));
2333
2334 // If we need to delete repeated events then we will fetch them all and delete one by one
2335 if ($deleterepeated && !empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
2336 // Get all records where the repeatid is the same as the event being removed
2337 $events = $DB->get_records('event', array('repeatid'=>$this->properties->repeatid));
2338 // For each of the returned events populate a calendar_event object and call delete
2339 // make sure the arg passed is false as we are already deleting all repeats
2340 foreach ($events as $event) {
2341 $event = new calendar_event($event);
2342 $event->delete(false);
2343 }
2344 }
2345
2346 return true;
2347 }
2348
2349 /**
2350 * Fetch all event properties
2351 *
2352 * This function returns all of the events properties as an object and optionally
2353 * can prepare an editor for the description field at the same time. This is
2354 * designed to work when the properties are going to be used to set the default
2355 * values of a moodle forms form.
2356 *
2357 * @param bool $prepareeditor If set to true a editor is prepared for use with
2358 * the mforms editor element. (for description)
2359 * @return stdClass Object containing event properties
2360 */
2361 public function properties($prepareeditor=false) {
2362 global $USER, $CFG, $DB;
2363
2364 // First take a copy of the properties. We don't want to actually change the
2365 // properties or we'd forever be converting back and forwards between an
2366 // editor formatted description and not
2367 $properties = clone($this->properties);
2368 // Clean the description here
2369 $properties->description = clean_text($properties->description, $properties->format);
2370
2371 // If set to true we need to prepare the properties for use with an editor
2372 // and prepare the file area
2373 if ($prepareeditor) {
2374
2375 // We may or may not have a property id. If we do then we need to work
2376 // out the context so we can copy the existing files to the draft area
2377 if (!empty($properties->id)) {
2378
2379 if ($properties->eventtype === 'site') {
2380 // Site context
f4700b91 2381 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2382 } else if ($properties->eventtype === 'user') {
2383 // User context
f4700b91 2384 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2385 } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
2386 // First check the course is valid
2387 $course = $DB->get_record('course', array('id'=>$properties->courseid));
2388 if (!$course) {
2389 print_error('invalidcourse');
2390 }
2391 // Course context
f4700b91 2392 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2393 // We have a course and are within the course context so we had
2394 // better use the courses max bytes value
2395 $this->editoroptions['maxbytes'] = $course->maxbytes;
2396 } else {
2397 // If we get here we have a custom event type as used by some
2398 // modules. In this case the event will have been added by
2399 // code and we won't need the editor
2400 $this->editoroptions['maxbytes'] = 0;
2401 $this->editoroptions['maxfiles'] = 0;
2402 }
2403
2404 if (empty($this->editorcontext) || empty($this->editorcontext->id)) {
2405 $contextid = false;
2406 } else {
2407 // Get the context id that is what we really want
2408 $contextid = $this->editorcontext->id;
2409 }
2410 } else {
2411
2412 // If we get here then this is a new event in which case we don't need a
2413 // context as there is no existing files to copy to the draft area.
2414 $contextid = null;
2415 }
2416
2417 // If the contextid === false we don't support files so no preparing
2418 // a draft area
2419 if ($contextid !== false) {
2420 // Just encase it has already been submitted
2421 $draftiddescription = file_get_submitted_draft_itemid('description');
2422 // Prepare the draft area, this copies existing files to the draft area as well
64f93798 2423 $properties->description = file_prepare_draft_area($draftiddescription, $contextid, 'calendar', 'event_description', $properties->id, $this->editoroptions, $properties->description);
76d9df3f
SH
2424 } else {
2425 $draftiddescription = 0;
2426 }
aa6c1ced 2427
76d9df3f
SH
2428 // Structure the description field as the editor requires
2429 $properties->description = array('text'=>$properties->description, 'format'=>$properties->format, 'itemid'=>$draftiddescription);
2430 }
2431
2432 // Finally return the properties
2433 return $properties;
2434 }
2435
2436 /**
2437 * Toggles the visibility of an event
2438 *
2439 * @param null|bool $force If it is left null the events visibility is flipped,
2440 * If it is false the event is made hidden, if it is true it
2441 * is made visible.
08b4a4e1 2442 * @return bool if event is successfully updated, toggle will be visible
76d9df3f
SH
2443 */
2444 public function toggle_visibility($force=null) {
2445 global $CFG, $DB;
2446
2447 // Set visible to the default if it is not already set
2448 if (empty($this->properties->visible)) {
2449 $this->properties->visible = 1;
2450 }
2451
2452 if ($force === true || ($force !== false && $this->properties->visible == 0)) {
2453 // Make this event visible
2454 $this->properties->visible = 1;
2455 // Fire the hook
2456 self::calendar_event_hook('show_event', array($this->properties));
2457 } else {
2458 // Make this event hidden
2459 $this->properties->visible = 0;
2460 // Fire the hook
2461 self::calendar_event_hook('hide_event', array($this->properties));
2462 }
2463
2464 // Update the database to reflect this change
2465 return $DB->set_field('event', 'visible', $this->properties->visible, array('id'=>$this->properties->id));
2466 }
2467
2468 /**
2469 * Attempts to call the hook for the specified action should a calendar type
2470 * by set $CFG->calendar, and the appopriate function defined
2471 *
76d9df3f
SH
2472 * @param string $action One of `update_event`, `add_event`, `delete_event`, `show_event`, `hide_event`
2473 * @param array $args The args to pass to the hook, usually the event is the first element
08b4a4e1 2474 * @return bool attempts to call event hook
76d9df3f
SH
2475 */
2476 public static function calendar_event_hook($action, array $args) {
2477 global $CFG;
2478 static $extcalendarinc;
2479 if ($extcalendarinc === null) {
d2b46849
BJ
2480 if (!empty($CFG->calendar)) {
2481 if (is_readable($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
2482 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
2483 $extcalendarinc = true;
2484 } else {
2485 debugging("Calendar lib file missing or not readable at /calendar/{$CFG->calendar}/lib.php.",
2486 DEBUG_DEVELOPER);
2487 $extcalendarinc = false;
2488 }
76d9df3f
SH
2489 } else {
2490 $extcalendarinc = false;
2491 }
2492 }
2493 if($extcalendarinc === false) {
2494 return false;
2495 }
438e4add 2496 $hook = $CFG->calendar .'_'.$action;
76d9df3f
SH
2497 if (function_exists($hook)) {
2498 call_user_func_array($hook, $args);
2499 return true;
2500 }
2501 return false;
2502 }
2503
2504 /**
2505 * Returns a calendar_event object when provided with an event id
2506 *
2507 * This function makes use of MUST_EXIST, if the event id passed in is invalid
2508 * it will result in an exception being thrown
2509 *
08b4a4e1
RW
2510 * @param int|object $param event object or event id
2511 * @return calendar_event|false status for loading calendar_event
76d9df3f 2512 */
fd699564 2513 public static function load($param) {
76d9df3f 2514 global $DB;
fd699564
SH
2515 if (is_object($param)) {
2516 $event = new calendar_event($param);
2517 } else {
2518 $event = $DB->get_record('event', array('id'=>(int)$param), '*', MUST_EXIST);
2519 $event = new calendar_event($event);
2520 }
76d9df3f
SH
2521 return $event;
2522 }
2523
2524 /**
2525 * Creates a new event and returns a calendar_event object
2526 *
2527 * @param object|array $properties An object containing event properties
2528 * @return calendar_event|false The event object or false if it failed
2529 */
2530 public static function create($properties) {
2531 if (is_array($properties)) {
2532 $properties = (object)$properties;
2533 }
2534 if (!is_object($properties)) {
2535 throw new coding_exception('When creating an event properties should be either an object or an assoc array');
2536 }
f4700b91 2537 $event = new calendar_event($properties);
76d9df3f
SH
2538 if ($event->update($properties)) {
2539 return $event;
2540 } else {
2541 return false;
2542 }
2543 }
2544}
36dc3b71
SH
2545
2546/**
2547 * Calendar information class
2548 *
2549 * This class is used simply to organise the information pertaining to a calendar
2550 * and is used primarily to make information easily available.
08b4a4e1
RW
2551 *
2552 * @package core_calendar
2553 * @category calendar
2554 * @copyright 2010 Sam Hemelryk
2555 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36dc3b71
SH
2556 */
2557class calendar_information {
08b4a4e1 2558 /** @var int The day */
36dc3b71 2559 public $day;
08b4a4e1
RW
2560
2561 /** @var int The month */
36dc3b71 2562 public $month;
08b4a4e1
RW
2563
2564 /** @var int The year */
36dc3b71
SH
2565 public $year;
2566
08b4a4e1 2567 /** @var int A course id */
36dc3b71 2568 public $courseid = null;
08b4a4e1
RW
2569
2570 /** @var array An array of courses */
36dc3b71 2571 public $courses = array();
08b4a4e1
RW
2572
2573 /** @var array An array of groups */
36dc3b71 2574 public $groups = array();
08b4a4e1
RW
2575
2576 /** @var array An array of users */
36dc3b71
SH
2577 public $users = array();
2578
2579 /**
2580 * Creates a new instance
2581 *
08b4a4e1
RW
2582 * @param int $day the number of the day
2583 * @param int $month the number of the month
2584 * @param int $year the number of the year
36dc3b71 2585 */
d8d8bdd9
SH
2586 public function __construct($day=0, $month=0, $year=0) {
2587
2588 $date = usergetdate(time());
2589
2590 if (empty($day)) {
2591 $day = $date['mday'];
2592 }
2593
2594 if (empty($month)) {
2595 $month = $date['mon'];
2596 }
2597
2598 if (empty($year)) {
2599 $year = $date['year'];
2600 }
2601
36dc3b71
SH
2602 $this->day = $day;
2603 $this->month = $month;
2604 $this->year = $year;
2605 }
2606
797cedc7 2607 /**
08b4a4e1 2608 * Initialize calendar information
797cedc7 2609 *
08b4a4e1 2610 * @param stdClass $course object
797cedc7 2611 * @param array $coursestoload An array of courses [$course->id => $course]
08b4a4e1 2612 * @param bool $ignorefilters options to use filter
797cedc7
SH
2613 */
2614 public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) {
2615 $this->courseid = $course->id;
2616 $this->course = $course;
2617 list($courses, $group, $user) = calendar_set_filters($coursestoload, $ignorefilters);
2618 $this->courses = $courses;
2619 $this->groups = $group;
2620 $this->users = $user;
2621 }
2622
36dc3b71
SH
2623 /**
2624 * Ensures the date for the calendar is correct and either sets it to now
2625 * or throws a moodle_exception if not
2626 *
08b4a4e1
RW
2627 * @param bool $defaultonow use current time
2628 * @throws moodle_exception
2629 * @return bool validation of checkdate
36dc3b71
SH
2630 */
2631 public function checkdate($defaultonow = true) {
2632 if (!checkdate($this->month, $this->day, $this->year)) {
2633 if ($defaultonow) {
2634 $now = usergetdate(time());
2635 $this->day = intval($now['mday']);
2636 $this->month = intval($now['mon']);
2637 $this->year = intval($now['year']);
2638 return true;
2639 } else {
2640 throw new moodle_exception('invaliddate');
2641 }
2642 }
2643 return true;
2644 }
2645 /**
2646 * Gets todays timestamp for the calendar
08b4a4e1
RW
2647 *
2648 * @return int today timestamp
36dc3b71
SH
2649 */
2650 public function timestamp_today() {
2651 return make_timestamp($this->year, $this->month, $this->day);
2652 }
2653 /**
2654 * Gets tomorrows timestamp for the calendar
08b4a4e1
RW
2655 *
2656 * @return int tomorrow timestamp
36dc3b71
SH
2657 */
2658 public function timestamp_tomorrow() {
2659 return make_timestamp($this->year, $this->month, $this->day+1);
2660 }
2661 /**
e30390a0 2662 * Adds the pretend blocks for the calendar
36dc3b71
SH
2663 *
2664 * @param core_calendar_renderer $renderer
08b4a4e1
RW
2665 * @param bool $showfilters display filters, false is set as default
2666 * @param string|null $view preference view options (eg: day, month, upcoming)
36dc3b71
SH
2667 */
2668 public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
2669 if ($showfilters) {
2670 $filters = new block_contents();
2671 $filters->content = $renderer->fake_block_filters($this->courseid, $this->day, $this->month, $this->year, $view, $this->courses);
2672 $filters->footer = '';
2673 $filters->title = get_string('eventskey', 'calendar');
2674 $renderer->add_pretend_calendar_block($filters, BLOCK_POS_RIGHT);
2675 }
2676 $block = new block_contents;
2677 $block->content = $renderer->fake_block_threemonths($this);
2678 $block->footer = '';
2679 $block->title = get_string('monthlyview', 'calendar');
2680 $renderer->add_pretend_calendar_block($block, BLOCK_POS_RIGHT);
2681 }
1d5bd3d2 2682}
b5a52acd
JH
2683
2684/**
e30390a0
SH
2685 * Returns option list for the poll interval setting.
2686 *
2687 * @return array An array of poll interval options. Interval => description.
b5a52acd
JH
2688 */
2689function calendar_get_pollinterval_choices() {
2690 return array(
e30390a0
SH
2691 '0' => new lang_string('never', 'calendar'),
2692 '3600' => new lang_string('hourly', 'calendar'),
2693 '86400' => new lang_string('daily', 'calendar'),
2694 '604800' => new lang_string('weekly', 'calendar'),
2695 '2628000' => new lang_string('monthly', 'calendar'),
2696 '31536000' => new lang_string('annually', 'calendar')
2697 );
b5a52acd
JH
2698}
2699
2700/**
e30390a0
SH
2701 * Returns option list of available options for the calendar event type, given the current user and course.
2702 *
4c349ad7 2703 * @param int $courseid The id of the course
e30390a0 2704 * @return array An array containing the event types the user can create.
b5a52acd
JH
2705 */
2706function calendar_get_eventtype_choices($courseid) {
2707 $choices = array();
2708 $allowed = new stdClass;
2709 calendar_get_allowed_types($allowed, $courseid);
2710
2711 if ($allowed->user) {
35ad5fc6 2712 $choices['user'] = get_string('userevents', 'calendar');
b5a52acd
JH
2713 }
2714 if ($allowed->site) {
6bf410ed 2715 $choices['site'] = get_string('siteevents', 'calendar');
b5a52acd
JH
2716 }
2717 if (!empty($allowed->courses)) {
35ad5fc6 2718 $choices['course'] = get_string('courseevents', 'calendar');
b5a52acd
JH
2719 }
2720 if (!empty($allowed->groups) and is_array($allowed->groups)) {
2721 $choices['group'] = get_string('group');
2722 }
2723
2724 return array($choices, $allowed->groups);
2725}
2726
b5a52acd
JH
2727/**
2728 * Add an iCalendar subscription to the database.
e30390a0
SH
2729 *
2730 * @param stdClass $sub The subscription object (e.g. from the form)
2731 * @return int The insert ID, if any.
b5a52acd
JH
2732 */
2733function calendar_add_subscription($sub) {
35ad5fc6 2734 global $DB, $USER, $SITE;
e30390a0 2735
35ad5fc6
AA
2736 if ($sub->eventtype === 'site') {
2737 $sub->courseid = $SITE->id;
2738 } else if ($sub->eventtype === 'group' || $sub->eventtype === 'course') {
b5a52acd 2739 $sub->courseid = $sub->course;
35ad5fc6
AA
2740 } else {
2741 // User events.
2742 $sub->courseid = 0;
b5a52acd
JH
2743 }
2744 $sub->userid = $USER->id;
2745
e30390a0 2746 // File subscriptions never update.
b5a52acd
JH
2747 if (empty($sub->url)) {
2748 $sub->pollinterval = 0;
2749 }
2750
2751 if (!empty($sub->name)) {
2752 if (empty($sub->id)) {
2753 $id = $DB->insert_record('event_subscriptions', $sub);
2754 return $id;
2755 } else {
2756 $DB->update_record('event_subscriptions', $sub);
2757 return $sub->id;
2758 }
2759 } else {
2760 print_error('errorbadsubscription', 'importcalendar');
2761 }
2762}
2763
2764/**
2765 * Add an iCalendar event to the Moodle calendar.
e30390a0
SH
2766 *
2767 * @param object $event The RFC-2445 iCalendar event
2768 * @param int $courseid The course ID
2769 * @param int $subscriptionid The iCalendar subscription ID
2770 * @return int Code: 1=updated, 2=inserted, 0=error
b5a52acd 2771 */
e30390a0 2772function calendar_add_icalendar_event($event, $courseid, $subscriptionid = null) {
b5a52acd
JH
2773 global $DB, $USER;
2774
e30390a0 2775 // Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event.
b5a52acd
JH
2776 if (empty($event->properties['SUMMARY'])) {
2777 return 0;
2778 }
2779
2780 $name = $event->properties['SUMMARY'][0]->value;
2781 $name = str_replace('\n', '<br />', $name);
2782 $name = str_replace('\\', '', $name);
2783 $name = preg_replace('/\s+/', ' ', $name);
e30390a0
SH
2784
2785 $eventrecord = new stdClass;
4c349ad7 2786 $eventrecord->name = clean_param($name, PARAM_NOTAGS);
b5a52acd
JH
2787
2788 if (empty($event->properties['DESCRIPTION'][0]->value)) {
2789 $description = '';
2790 } else {
2791 $description = $event->properties['DESCRIPTION'][0]->value;
2792 $description = str_replace('\n', '<br />', $description);
2793 $description = str_replace('\\', '', $description);
2794 $description = preg_replace('/\s+/', ' ', $description);
2795 }
4c349ad7 2796 $eventrecord->description = clean_param($description, PARAM_NOTAGS);
b5a52acd 2797
e30390a0 2798 // Probably a repeating event with RRULE etc. TODO: skip for now.
b5a52acd
JH
2799 if (empty($event->properties['DTSTART'][0]->value)) {
2800 return 0;
2801 }
2802
2803 $eventrecord->timestart = strtotime($event->properties['DTSTART'][0]->value);
2804 if (empty($event->properties['DTEND'])) {
2805 $eventrecord->timeduration = 3600; // one hour if no end time specified
2806 } else {
2807 $eventrecord->timeduration = strtotime($event->properties['DTEND'][0]->value) - $eventrecord->timestart;
2808 }
2809 $eventrecord->uuid = $event->properties['UID'][0]->value;
2810 $eventrecord->timemodified = time();
2811
e30390a0 2812 // Add the iCal subscription details if required.
b5a52acd
JH
2813 if ($sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid))) {
2814 $eventrecord->subscriptionid = $subscriptionid;
2815 $eventrecord->userid = $sub->userid;
2816 $eventrecord->groupid = $sub->groupid;
2817 $eventrecord->courseid = $sub->courseid;
35ad5fc6 2818 $eventrecord->eventtype = $sub->eventtype;
b5a52acd 2819 } else {
35ad5fc6
AA
2820 // We should never do anything with an event without a subscription reference.
2821 return 0;
b5a52acd
JH
2822 }
2823
2824 if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid))) {
2825 $eventrecord->id = $updaterecord->id;
2826 if ($DB->update_record('event', $eventrecord)) {
2827 return CALENDAR_IMPORT_EVENT_UPDATED;
2828 } else {
2829 return 0;
2830 }
2831 } else {
2832 if ($DB->insert_record('event', $eventrecord)) {
2833 return CALENDAR_IMPORT_EVENT_INSERTED;
2834 } else {
2835 return 0;
2836 }
2837 }
2838}
2839
2840/**
e30390a0
SH
2841 * Update a subscription from the form data in one of the rows in the existing subscriptions table.
2842 *
2843 * @param int $subscriptionid The ID of the subscription we are acting upon.
2844 * @param int $pollinterval The poll interval to use.
2845 * @param int $action The action to be performed. One of update or remove.
2846 * @return string A log of the import progress, including errors
b5a52acd 2847 */
e30390a0 2848function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) {
b5a52acd
JH
2849 global $DB;
2850
e30390a0 2851 if (empty($subscriptionid)) {
b5a52acd
JH
2852 return '';
2853 }
2854
e30390a0
SH
2855 // Fetch the subscription from the database making sure it exists.
2856 $sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid), '*', MUST_EXIST);
b5a52acd 2857
e30390a0
SH
2858 $strupdate = get_string('update');
2859 $strremove = get_string('remove');
2860 // Update or remove the subscription, based on action.
b5a52acd 2861 switch ($action) {
e30390a0
SH
2862 case $strupdate:
2863 // Skip updating file subscriptions.
2864 if (empty($sub->url)) {
2865 break;
2866 }
2867 $sub->pollinterval = $pollinterval;
2868 $DB->update_record('event_subscriptions', $sub);
b5a52acd 2869
e30390a0
SH
2870 // Update the events.
2871 return "<p>".get_string('subscriptionupdated', 'calendar', $sub->name)."</p>" . calendar_update_subscription_events($subscriptionid);
b5a52acd 2872
e30390a0
SH
2873 case $strremove:
2874 $DB->delete_records('event', array('subscriptionid' => $subscriptionid));
2875 $DB->delete_records('event_subscriptions', array('id' => $subscriptionid));
2876 return get_string('subscriptionremoved', 'calendar', $sub->name);
2877 break;
b5a52acd 2878
e30390a0
SH
2879 default:
2880 break;
b5a52acd
JH
2881 }
2882 return '';
2883}
2884
2885/**
2886 * From a URL, fetch the calendar and return an iCalendar object.
e30390a0
SH
2887 *
2888 * @param string $url The iCalendar URL
2889 * @return stdClass The iCalendar object
b5a52acd
JH
2890 */
2891function calendar_get_icalendar($url) {
e30390a0
SH
2892 global $CFG;
2893
2894 require_once($CFG->libdir.'/filelib.php');
2895
2896 $curl = new curl();
20bdf997 2897 $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => 1, 'CURLOPT_MAXREDIRS' => 5));
e30390a0 2898 $calendar = $curl->get($url);
20bdf997
AA
2899 // Http code validation should actually be the job of curl class.
2900 if (!$calendar || $curl->info['http_code'] != 200 || !empty($curl->errorno)) {
e30390a0
SH
2901 throw new moodle_exception('errorinvalidicalurl', 'calendar');
2902 }
2903
b5a52acd
JH
2904 $ical = new iCalendar();
2905 $ical->unserialize($calendar);
2906 return $ical;
2907}
2908
2909/**
2910 * Import events from an iCalendar object into a course calendar.
e30390a0
SH
2911 *
2912 * @param stdClass $ical The iCalendar object.
2913 * @param int $courseid The course ID for the calendar.
2914 * @param int $subscriptionid The subscription ID.
2915 * @return string A log of the import progress, including errors.
b5a52acd 2916 */
e30390a0 2917function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = null) {
b5a52acd
JH
2918 global $DB;
2919 $return = '';
2920 $eventcount = 0;
2921 $updatecount = 0;
2922
e30390a0
SH
2923 // Large calendars take a while...
2924 set_time_limit(300);
b5a52acd 2925
e30390a0 2926 // Mark all events in a subscription with a zero timestamp.
b5a52acd 2927 if (!empty($subscriptionid)) {
e30390a0 2928 $sql = "UPDATE {event} SET timemodified = :time WHERE subscriptionid = :id";
b5a52acd
JH
2929 $DB->execute($sql, array('time' => 0, 'id' => $subscriptionid));
2930 }
4c349ad7 2931 foreach ($ical->components['VEVENT'] as $event) {
b5a52acd
JH
2932 $res = calendar_add_icalendar_event($event, $courseid, $subscriptionid);
2933 switch ($res) {
2934 case CALENDAR_IMPORT_EVENT_UPDATED:
2935 $updatecount++;
2936 break;
2937 case CALENDAR_IMPORT_EVENT_INSERTED:
2938 $eventcount++;
2939 break;
2940 case 0:
4c349ad7 2941 $return .= '<p>'.get_string('erroraddingevent', 'calendar').': '.(empty($event->properties['SUMMARY'])?'('.get_string('notitle', 'calendar').')':$event->properties['SUMMARY'][0]->value)." </p>\n";
b5a52acd
JH
2942 break;
2943 }
2944 }
e30390a0
SH
2945 $return .= "<p> ".get_string('eventsimported', 'calendar', $eventcount)."</p>";
2946 $return .= "<p> ".get_string('eventsupdated', 'calendar', $updatecount)."</p>";
b5a52acd 2947
e30390a0 2948 // Delete remaining zero-marked events since they're not in remote calendar.
b5a52acd
JH
2949 if (!empty($subscriptionid)) {
2950 $deletecount = $DB->count_records('event', array('timemodified' => 0, 'subscriptionid' => $subscriptionid));
2951 if (!empty($deletecount)) {
e30390a0 2952 $sql = "DELETE FROM {event} WHERE timemodified = :time AND subscriptionid = :id";
b5a52acd 2953 $DB->execute($sql, array('time' => 0, 'id' => $subscriptionid));
4c349ad7 2954 $return .= "<p> ".get_string('eventsdeleted', 'calendar').": {$deletecount} </p>\n";
b5a52acd
JH
2955 }
2956 }
2957
2958 return $return;
2959}
2960
2961/**
2962 * Fetch a calendar subscription and update the events in the calendar.
e30390a0
SH
2963 *
2964 * @param int $subscriptionid The course ID for the calendar.
2965 * @return string A log of the import progress, including errors.
b5a52acd
JH
2966 */
2967function calendar_update_subscription_events($subscriptionid) {
2968 global $DB;
b5a52acd
JH
2969
2970 $sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid));
2971 if (empty($sub)) {
2972 print_error('errorbadsubscription', 'calendar');
2973 }
e30390a0 2974 // Don't update a file subscription. TODO: Update from a new uploaded file.
b5a52acd
JH
2975 if (empty($sub->url)) {
2976 return 'File subscription not updated.';
2977 }
2978 $ical = calendar_get_icalendar($sub->url);
2979 $return = calendar_import_icalendar_events($ical, $sub->courseid, $subscriptionid);
2980 $sub->lastupdated = time();
2981 $DB->update_record('event_subscriptions', $sub);
2982 return $return;
2983}
2984
7a216229
AA
2985/**
2986 * Checks to see if the user can edit a given subscription feed.
2987 *
2988 * @param mixed $subscriptionorid Subscription object or id
2989 * @return bool true if current user can edit the subscription else false
2990 */
2991function calendar_can_edit_subscription($subscriptionorid) {
2992 global $DB;
2993
2994 if (is_array($subscriptionorid)) {
2995 $subscription = (object)$subscriptionorid;
2996 } else if (is_object($subscriptionorid)) {
2997 $subscription = $subscriptionorid;
2998 } else {
2999 $subscription = $DB->get_record('event_subscriptions', array('id' => $subscriptionorid), '*', MUST_EXIST);
3000 }
3001 $allowed = new stdClass;
3002 $courseid = $subscription->courseid;
3003 $groupid = $subscription->groupid;
3004 calendar_get_allowed_types($allowed, $courseid);
3005 switch ($subscription->eventtype) {
3006 case 'user':
3007 return $allowed->user;
3008 case 'course':
3009 if (isset($allowed->courses[$courseid])) {
3010 return $allowed->courses[$courseid];
3011 } else {
3012 return false;
3013 }
3014 case 'site':
3015 return $allowed->site;
3016 case 'group':
3017 if (isset($allowed->groups[$groupid])) {
3018 return $allowed->groups[$groupid];
3019 } else {
3020 return false;
3021 }
3022 default:
3023 return false;
3024 }
3025}
3026
b5a52acd
JH
3027/**
3028 * Update calendar subscriptions.
e30390a0
SH
3029 *
3030 * @return bool
b5a52acd
JH
3031 */
3032function calendar_cron() {
e30390a0
SH
3033 global $CFG, $DB;
3034
3035 // In order to execute this we need bennu.
3036 require_once($CFG->libdir.'/bennu/bennu.inc.php');
3037
24a3115c 3038 mtrace('Updating calendar subscriptions:');
e30390a0 3039
b5a52acd 3040 $time = time();
e30390a0 3041 $subscriptions = $DB->get_records_sql('SELECT * FROM {event_subscriptions} WHERE pollinterval > 0 AND lastupdated + pollinterval < ?', array($time));
4c349ad7 3042 foreach ($subscriptions as $sub) {
24a3115c 3043 mtrace("Updating calendar subscription {$sub->name} in course {$sub->courseid}");
e30390a0
SH
3044 try {
3045 $log = calendar_update_subscription_events($sub->id);
3046 } catch (moodle_exception $ex) {
3047
3048 }
b5a52acd
JH
3049 mtrace(trim(strip_tags($log)));
3050 }
e30390a0 3051
24a3115c 3052 mtrace('Finished updating calendar subscriptions.');
b5a52acd 3053
e30390a0 3054 return true;
24a3115c 3055}