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