Merge branch 'MDL-30731-master' of git://github.com/FMCorz/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
0f927f1e 325 $popupicon = 'c/site';
c3d3b6d4 326 } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event
0f927f1e 327 $popupicon = 'c/course';
41d30a8e 328 } else if ($event->groupid) { // Group event
0f927f1e 329 $popupicon = 'c/group';
41d30a8e 330 } else if ($event->userid) { // User event
0f927f1e 331 $popupicon = 'c/user';
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
10daca92 610 $event->icon = '<img height="16" width="16" src="'.$icon.'" alt="'.$eventtype.'" title="'.$modulename.'" style="vertical-align: middle;" />';
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
b5d0cafc 617 $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/site') . '" alt="'.get_string('globalevent', 'calendar').'" style="vertical-align: middle;" />';
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
b5d0cafc 625 $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/course') . '" alt="'.get_string('courseevent', 'calendar').'" style="vertical-align: middle;" />';
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
b5d0cafc 629 $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/group') . '" alt="'.get_string('groupevent', 'calendar').'" style="vertical-align: middle;" />';
90723839 630 $event->cssclass = 'calendar_event_group';
3c134875 631 } else if($event->userid) { // User event
b5d0cafc 632 $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/user') . '" alt="'.get_string('userevent', 'calendar').'" style="vertical-align: middle;" />';
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]);
ea21c1f4 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
SH
1755 if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
1756 $allowed->groups = groups_get_all_groups($course->id);
1757 }
1758 } else if (has_capability('moodle/calendar:managegroupentries', $coursecontext)) {
1759 if($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
1760 $allowed->groups = groups_get_all_groups($course->id);
1761 }
f63d2922 1762 }
86ac8b24 1763 }
1764 }
1765}
1766
1767/**
08b4a4e1 1768 * See if user can add calendar entries at all
86ac8b24 1769 * used to print the "New Event" button
08b4a4e1
RW
1770 *
1771 * @param stdClass $course object of a course or course id
1772 * @return bool has the capability to add at least one event type
86ac8b24 1773 */
797cedc7
SH
1774function calendar_user_can_add_event($course) {
1775 if (!isloggedin() || isguestuser()) {
1776 return false;
1777 }
1778 calendar_get_allowed_types($allowed, $course);
e295df44 1779 return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->site);
86ac8b24 1780}
76d9df3f
SH
1781
1782/**
1783 * Check wether the current user is permitted to add events
1784 *
08b4a4e1
RW
1785 * @param stdClass $event object of event
1786 * @return bool has the capability to add event
76d9df3f
SH
1787 */
1788function calendar_add_event_allowed($event) {
1789 global $USER, $DB;
1790
1791 // can not be using guest account
4f0c2d00 1792 if (!isloggedin() or isguestuser()) {
76d9df3f
SH
1793 return false;
1794 }
1795
6ca657a7 1796 $sitecontext = context_system::instance();
76d9df3f
SH
1797 // if user has manageentries at site level, always return true
1798 if (has_capability('moodle/calendar:manageentries', $sitecontext)) {
1799 return true;
1800 }
1801
1802 switch ($event->eventtype) {
1803 case 'course':
f4700b91 1804 return has_capability('moodle/calendar:manageentries', $event->context);
76d9df3f
SH
1805
1806 case 'group':
1807 // Allow users to add/edit group events if:
1808 // 1) They have manageentries (= entries for whole course)
1809 // 2) They have managegroupentries AND are in the group
1810 $group = $DB->get_record('groups', array('id'=>$event->groupid));
1811 return $group && (
f4700b91
RW
1812 has_capability('moodle/calendar:manageentries', $event->context) ||
1813 (has_capability('moodle/calendar:managegroupentries', $event->context)
76d9df3f
SH
1814 && groups_is_member($event->groupid)));
1815
1816 case 'user':
1817 if ($event->userid == $USER->id) {
f4700b91 1818 return (has_capability('moodle/calendar:manageownentries', $event->context));
76d9df3f
SH
1819 }
1820 //there is no 'break;' intentionally
1821
1822 case 'site':
f4700b91 1823 return has_capability('moodle/calendar:manageentries', $event->context);
76d9df3f
SH
1824
1825 default:
f4700b91 1826 return has_capability('moodle/calendar:manageentries', $event->context);
76d9df3f
SH
1827 }
1828}
1829
1830/**
08b4a4e1 1831 * Manage calendar events
76d9df3f
SH
1832 *
1833 * This class provides the required functionality in order to manage calendar events.
1834 * It was introduced as part of Moodle 2.0 and was created in order to provide a
1835 * better framework for dealing with calendar events in particular regard to file
1836 * handling through the new file API
1837 *
08b4a4e1
RW
1838 * @package core_calendar
1839 * @category calendar
1840 * @copyright 2009 Sam Hemelryk
1841 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1842 *
76d9df3f
SH
1843 * @property int $id The id within the event table
1844 * @property string $name The name of the event
1845 * @property string $description The description of the event
1846 * @property int $format The format of the description FORMAT_?
1847 * @property int $courseid The course the event is associated with (0 if none)
1848 * @property int $groupid The group the event is associated with (0 if none)
1849 * @property int $userid The user the event is associated with (0 if none)
1850 * @property int $repeatid If this is a repeated event this will be set to the
1851 * id of the original
1852 * @property string $modulename If added by a module this will be the module name
1853 * @property int $instance If added by a module this will be the module instance
1854 * @property string $eventtype The event type
1855 * @property int $timestart The start time as a timestamp
1856 * @property int $timeduration The duration of the event in seconds
1857 * @property int $visible 1 if the event is visible
1858 * @property int $uuid ?
1859 * @property int $sequence ?
1860 * @property int $timemodified The time last modified as a timestamp
1861 */
1862class calendar_event {
1863
08b4a4e1 1864 /** @var array An object containing the event properties can be accessed via the magic __get/set methods */
76d9df3f 1865 protected $properties = null;
08b4a4e1 1866
76d9df3f 1867 /**
08b4a4e1 1868 * @var string The converted event discription with file paths resolved. This gets populated when someone requests description for the first time */
76d9df3f 1869 protected $_description = null;
08b4a4e1
RW
1870
1871 /** @var array The options to use with this description editor */
76d9df3f
SH
1872 protected $editoroptions = array(
1873 'subdirs'=>false,
1874 'forcehttps'=>false,
2b35ed1a 1875 'maxfiles'=>-1,
76d9df3f
SH
1876 'maxbytes'=>null,
1877 'trusttext'=>false);
08b4a4e1
RW
1878
1879 /** @var object The context to use with the description editor */
76d9df3f
SH
1880 protected $editorcontext = null;
1881
1882 /**
1883 * Instantiates a new event and optionally populates its properties with the
1884 * data provided
1885 *
1886 * @param stdClass $data Optional. An object containing the properties to for
1887 * an event
1888 */
1889 public function __construct($data=null) {
c203a277 1890 global $CFG, $USER;
76d9df3f
SH
1891
1892 // First convert to object if it is not already (should either be object or assoc array)
1893 if (!is_object($data)) {
1894 $data = (object)$data;
1895 }
1896
1897 $this->editoroptions['maxbytes'] = $CFG->maxbytes;
1898
1899 $data->eventrepeats = 0;
1900
1901 if (empty($data->id)) {
1902 $data->id = null;
1903 }
1904
c203a277
SH
1905 // Default to a user event
1906 if (empty($data->eventtype)) {
1907 $data->eventtype = 'user';
1908 }
1909
1910 // Default to the current user
1911 if (empty($data->userid)) {
1912 $data->userid = $USER->id;
1913 }
1914
76d9df3f
SH
1915 if (!empty($data->timeduration) && is_array($data->timeduration)) {
1916 $data->timeduration = make_timestamp($data->timeduration['year'], $data->timeduration['month'], $data->timeduration['day'], $data->timeduration['hour'], $data->timeduration['minute']) - $data->timestart;
1917 }
1918 if (!empty($data->description) && is_array($data->description)) {
1919 $data->format = $data->description['format'];
1920 $data->description = $data->description['text'];
1921 } else if (empty($data->description)) {
1922 $data->description = '';
20e5da7d 1923 $data->format = editors_get_preferred_format();
76d9df3f 1924 }
c203a277
SH
1925 // Ensure form is defaulted correctly
1926 if (empty($data->format)) {
1927 $data->format = editors_get_preferred_format();
1928 }
76d9df3f 1929
f4700b91
RW
1930 if (empty($data->context)) {
1931 $data->context = $this->calculate_context($data);
1932 }
76d9df3f
SH
1933 $this->properties = $data;
1934 }
1935
1936 /**
1937 * Magic property method
1938 *
1939 * Attempts to call a set_$key method if one exists otherwise falls back
1940 * to simply set the property
1941 *
08b4a4e1
RW
1942 * @param string $key property name
1943 * @param mixed $value value of the property
76d9df3f
SH
1944 */
1945 public function __set($key, $value) {
1946 if (method_exists($this, 'set_'.$key)) {
1947 $this->{'set_'.$key}($value);
1948 }
1949 $this->properties->{$key} = $value;
1950 }
1951
1952 /**
1953 * Magic get method
1954 *
1955 * Attempts to call a get_$key method to return the property and ralls over
1956 * to return the raw property
1957 *
08b4a4e1
RW
1958 * @param string $key property name
1959 * @return mixed property value
76d9df3f
SH
1960 */
1961 public function __get($key) {
1962 if (method_exists($this, 'get_'.$key)) {
1963 return $this->{'get_'.$key}();
1964 }
c203a277
SH
1965 if (!isset($this->properties->{$key})) {
1966 throw new coding_exception('Undefined property requested');
1967 }
76d9df3f
SH
1968 return $this->properties->{$key};
1969 }
1970
1971 /**
1972 * Stupid PHP needs an isset magic method if you use the get magic method and
1973 * still want empty calls to work.... blah ~!
1974 *
08b4a4e1
RW
1975 * @param string $key $key property name
1976 * @return bool|mixed property value, false if property is not exist
76d9df3f
SH
1977 */
1978 public function __isset($key) {
1979 return !empty($this->properties->{$key});
1980 }
1981
f4700b91
RW
1982 /**
1983 * Calculate the context value needed for calendar_event.
1984 * Event's type can be determine by the available value store in $data
1985 * It is important to check for the existence of course/courseid to determine
1986 * the course event.
1987 * Default value is set to CONTEXT_USER
1988 *
08b4a4e1
RW
1989 * @param stdClass $data information about event
1990 * @return stdClass The context object.
f4700b91
RW
1991 */
1992 protected function calculate_context(stdClass $data) {
025d82e0 1993 global $USER, $DB;
f4700b91 1994
f4700b91
RW
1995 $context = null;
1996 if (isset($data->courseid) && $data->courseid > 0) {
6ca657a7 1997 $context = context_course::instance($data->courseid);
f4700b91 1998 } else if (isset($data->course) && $data->course > 0) {
6ca657a7 1999 $context = context_course::instance($data->course);
f4700b91
RW
2000 } else if (isset($data->groupid) && $data->groupid > 0) {
2001 $group = $DB->get_record('groups', array('id'=>$data->groupid));
6ca657a7 2002 $context = context_course::instance($group->courseid);
f4700b91 2003 } else if (isset($data->userid) && $data->userid > 0 && $data->userid == $USER->id) {
6ca657a7 2004 $context = context_user::instance($data->userid);
f4700b91
RW
2005 } else if (isset($data->userid) && $data->userid > 0 && $data->userid != $USER->id &&
2006 isset($data->instance) && $data->instance > 0) {
18d63ffb 2007 $cm = get_coursemodule_from_instance($data->modulename, $data->instance, 0, false, MUST_EXIST);
6ca657a7 2008 $context = context_course::instance($cm->course);
f4700b91 2009 } else {
e30390a0 2010 $context = context_user::instance($data->userid);
f4700b91
RW
2011 }
2012
2013 return $context;
2014 }
2015
76d9df3f
SH
2016 /**
2017 * Returns an array of editoroptions for this event: Called by __get
2018 * Please use $blah = $event->editoroptions;
08b4a4e1
RW
2019 *
2020 * @return array event editor options
76d9df3f
SH
2021 */
2022 protected function get_editoroptions() {
2023 return $this->editoroptions;
2024 }
2025
2026 /**
2027 * Returns an event description: Called by __get
2028 * Please use $blah = $event->description;
2029 *
08b4a4e1 2030 * @return string event description
76d9df3f
SH
2031 */
2032 protected function get_description() {
f4700b91 2033 global $CFG;
99d19c13
PS
2034
2035 require_once($CFG->libdir . '/filelib.php');
2036
76d9df3f
SH
2037 if ($this->_description === null) {
2038 // Check if we have already resolved the context for this event
2039 if ($this->editorcontext === null) {
2040 // Switch on the event type to decide upon the appropriate context
2041 // to use for this event
f4700b91
RW
2042 $this->editorcontext = $this->properties->context;
2043 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
2044 && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
2045 return clean_text($this->properties->description, $this->properties->format);
76d9df3f
SH
2046 }
2047 }
2048
2049 // Work out the item id for the editor, if this is a repeated event then the files will
2050 // be associated with the original
2051 if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
2052 $itemid = $this->properties->repeatid;
2053 } else {
2054 $itemid = $this->properties->id;
2055 }
2056
2057 // Convert file paths in the description so that things display correctly
64f93798 2058 $this->_description = file_rewrite_pluginfile_urls($this->properties->description, 'pluginfile.php', $this->editorcontext->id, 'calendar', 'event_description', $itemid);
76d9df3f
SH
2059 // Clean the text so no nasties get through
2060 $this->_description = clean_text($this->_description, $this->properties->format);
2061 }
2062 // Finally return the description
2063 return $this->_description;
2064 }
2065
2066 /**
2067 * Return the number of repeat events there are in this events series
aa6c1ced 2068 *
08b4a4e1 2069 * @return int number of event repeated
76d9df3f
SH
2070 */
2071 public function count_repeats() {
2072 global $DB;
2073 if (!empty($this->properties->repeatid)) {
2074 $this->properties->eventrepeats = $DB->count_records('event', array('repeatid'=>$this->properties->repeatid));
2075 // We don't want to count ourselves
2076 $this->properties->eventrepeats--;
2077 }
2078 return $this->properties->eventrepeats;
2079 }
2080
2081 /**
2082 * Update or create an event within the database
2083 *
2084 * Pass in a object containing the event properties and this function will
2085 * insert it into the database and deal with any associated files
2086 *
2087 * @see add_event()
2088 * @see update_event()
2089 *
08b4a4e1
RW
2090 * @param stdClass $data object of event
2091 * @param bool $checkcapability if moodle should check calendar managing capability or not
2092 * @return bool event updated
76d9df3f 2093 */
1d5bd3d2 2094 public function update($data, $checkcapability=true) {
76d9df3f
SH
2095 global $CFG, $DB, $USER;
2096
438e4add
SH
2097 foreach ($data as $key=>$value) {
2098 $this->properties->$key = $value;
2099 }
2100
76d9df3f
SH
2101 $this->properties->timemodified = time();
2102 $usingeditor = (!empty($this->properties->description) && is_array($this->properties->description));
2103
2104 if (empty($this->properties->id) || $this->properties->id < 1) {
2105
1d5bd3d2
DC
2106 if ($checkcapability) {
2107 if (!calendar_add_event_allowed($this->properties)) {
2108 print_error('nopermissiontoupdatecalendar');
2109 }
76d9df3f
SH
2110 }
2111
2112 if ($usingeditor) {
2113 switch ($this->properties->eventtype) {
2114 case 'user':
76d9df3f 2115 $this->properties->courseid = 0;
f19d57e5 2116 $this->properties->course = 0;
76d9df3f
SH
2117 $this->properties->groupid = 0;
2118 $this->properties->userid = $USER->id;
2119 break;
2120 case 'site':
76d9df3f 2121 $this->properties->courseid = SITEID;
f19d57e5 2122 $this->properties->course = SITEID;
76d9df3f
SH
2123 $this->properties->groupid = 0;
2124 $this->properties->userid = $USER->id;
2125 break;
2126 case 'course':
76d9df3f
SH
2127 $this->properties->groupid = 0;
2128 $this->properties->userid = $USER->id;
2129 break;
2130 case 'group':
76d9df3f
SH
2131 $this->properties->userid = $USER->id;
2132 break;
2133 default:
2134 // Ewww we should NEVER get here, but just incase we do lets
2135 // fail gracefully
2136 $usingeditor = false;
2137 break;
2138 }
2139
f19d57e5
FM
2140 // If we are actually using the editor, we recalculate the context because some default values
2141 // were set when calculate_context() was called from the constructor.
2142 if ($usingeditor) {
2143 $this->properties->context = $this->calculate_context($this->properties);
2144 $this->editorcontext = $this->properties->context;
2145 }
2146
76d9df3f
SH
2147 $editor = $this->properties->description;
2148 $this->properties->format = $this->properties->description['format'];
2149 $this->properties->description = $this->properties->description['text'];
2150 }
2151
2152 // Insert the event into the database
2153 $this->properties->id = $DB->insert_record('event', $this->properties);
2154
2155 if ($usingeditor) {
2156 $this->properties->description = file_save_draft_area_files(
2157 $editor['itemid'],
2158 $this->editorcontext->id,
64f93798
PS
2159 'calendar',
2160 'event_description',
76d9df3f
SH
2161 $this->properties->id,
2162 $this->editoroptions,
2163 $editor['text'],
2164 $this->editoroptions['forcehttps']);
76d9df3f
SH
2165 $DB->set_field('event', 'description', $this->properties->description, array('id'=>$this->properties->id));
2166 }
aa6c1ced 2167
76d9df3f
SH
2168 // Log the event entry.
2169 add_to_log($this->properties->courseid, 'calendar', 'add', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2170
2171 $repeatedids = array();
2172
2173 if (!empty($this->properties->repeat)) {
2174 $this->properties->repeatid = $this->properties->id;
2175 $DB->set_field('event', 'repeatid', $this->properties->repeatid, array('id'=>$this->properties->id));
2176
2177 $eventcopy = clone($this->properties);
2178 unset($eventcopy->id);
2179
2180 for($i = 1; $i < $eventcopy->repeats; $i++) {
2181
2182 $eventcopy->timestart = ($eventcopy->timestart+WEEKSECS) + dst_offset_on($eventcopy->timestart) - dst_offset_on($eventcopy->timestart+WEEKSECS);
2183
2184 // Get the event id for the log record.
2185 $eventcopyid = $DB->insert_record('event', $eventcopy);
2186
2187 // If the context has been set delete all associated files
2188 if ($usingeditor) {
2189 $fs = get_file_storage();
64f93798 2190 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
76d9df3f
SH
2191 foreach ($files as $file) {
2192 $fs->create_file_from_storedfile(array('itemid'=>$eventcopyid), $file);
2193 }
2194 }
2195
2196 $repeatedids[] = $eventcopyid;
2197 // Log the event entry.
2198 add_to_log($eventcopy->courseid, 'calendar', 'add', 'event.php?action=edit&amp;id='.$eventcopyid, $eventcopy->name);
2199 }
2200 }
2201
2202 // Hook for tracking added events
2203 self::calendar_event_hook('add_event', array($this->properties, $repeatedids));
2204 return true;
2205 } else {
2206
1d5bd3d2
DC
2207 if ($checkcapability) {
2208 if(!calendar_edit_event_allowed($this->properties)) {
2209 print_error('nopermissiontoupdatecalendar');
2210 }
76d9df3f
SH
2211 }
2212
2213 if ($usingeditor) {
2214 if ($this->editorcontext !== null) {
2215 $this->properties->description = file_save_draft_area_files(
2216 $this->properties->description['itemid'],
2217 $this->editorcontext->id,
64f93798
PS
2218 'calendar',
2219 'event_description',
76d9df3f
SH
2220 $this->properties->id,
2221 $this->editoroptions,
2222 $this->properties->description['text'],
2223 $this->editoroptions['forcehttps']);
2224 } else {
2225 $this->properties->format = $this->properties->description['format'];
2226 $this->properties->description = $this->properties->description['text'];
2227 }
2228 }
2229
2230 $event = $DB->get_record('event', array('id'=>$this->properties->id));
2231
2232 $updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
2233
2234 if ($updaterepeated) {
2235 // Update all
2236 if ($this->properties->timestart != $event->timestart) {
2237 $timestartoffset = $this->properties->timestart - $event->timestart;
2238 $sql = "UPDATE {event}
2239 SET name = ?,
2240 description = ?,
2241 timestart = timestart + ?,
2242 timeduration = ?,
2243 timemodified = ?
2244 WHERE repeatid = ?";
2245 $params = array($this->properties->name, $this->properties->description, $timestartoffset, $this->properties->timeduration, time(), $event->repeatid);
2246 } else {
2247 $sql = "UPDATE {event} SET name = ?, description = ?, timeduration = ?, timemodified = ? WHERE repeatid = ?";
2248 $params = array($this->properties->name, $this->properties->description, $this->properties->timeduration, time(), $event->repeatid);
2249 }
2250 $DB->execute($sql, $params);
2251
2252 // Log the event update.
2253 add_to_log($this->properties->courseid, 'calendar', 'edit all', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2254 } else {
2255 $DB->update_record('event', $this->properties);
ddaff608
SH
2256 $event = calendar_event::load($this->properties->id);
2257 $this->properties = $event->properties();
76d9df3f
SH
2258 add_to_log($this->properties->courseid, 'calendar', 'edit', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2259 }
2260
2261 // Hook for tracking event updates
2262 self::calendar_event_hook('update_event', array($this->properties, $updaterepeated));
2263 return true;
2264 }
2265 }
2266
2267 /**
2268 * Deletes an event and if selected an repeated events in the same series
2269 *
2270 * This function deletes an event, any associated events if $deleterepeated=true,
2271 * and cleans up any files associated with the events.
2272 *
2273 * @see delete_event()
2274 *
08b4a4e1
RW
2275 * @param bool $deleterepeated delete event repeatedly
2276 * @return bool succession of deleting event
76d9df3f
SH
2277 */
2278 public function delete($deleterepeated=false) {
f4700b91 2279 global $DB;
76d9df3f
SH
2280
2281 // If $this->properties->id is not set then something is wrong
2282 if (empty($this->properties->id)) {
2283 debugging('Attempting to delete an event before it has been loaded', DEBUG_DEVELOPER);
2284 return false;
2285 }
2286
2287 // Delete the event
2288 $DB->delete_records('event', array('id'=>$this->properties->id));
2289
ff284fac
AA
2290 // If we are deleting parent of a repeated event series, promote the next event in the series as parent
2291 if (($this->properties->id == $this->properties->repeatid) && !$deleterepeated) {
2292 $newparent = $DB->get_field_sql("SELECT id from {event} where repeatid = ? order by id ASC", array($this->properties->id), IGNORE_MULTIPLE);
2293 if (!empty($newparent)) {
2294 $DB->execute("UPDATE {event} SET repeatid = ? WHERE repeatid = ?", array($newparent, $this->properties->id));
5ff114ad
AA
2295 // Get all records where the repeatid is the same as the event being removed
2296 $events = $DB->get_records('event', array('repeatid' => $newparent));
2297 // For each of the returned events trigger the event_update hook.
2298 foreach ($events as $event) {
2299 self::calendar_event_hook('update_event', array($event, false));
2300 }
ff284fac
AA
2301 }
2302 }
2303
76d9df3f
SH
2304 // If the editor context hasn't already been set then set it now
2305 if ($this->editorcontext === null) {
f4700b91 2306 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2307 }
2308
2309 // If the context has been set delete all associated files
2310 if ($this->editorcontext !== null) {
2311 $fs = get_file_storage();
64f93798 2312 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
76d9df3f
SH
2313 foreach ($files as $file) {
2314 $file->delete();
2315 }
2316 }
2317
2318 // Fire the event deleted hook
2319 self::calendar_event_hook('delete_event', array($this->properties->id, $deleterepeated));
2320
2321 // If we need to delete repeated events then we will fetch them all and delete one by one
2322 if ($deleterepeated && !empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
2323 // Get all records where the repeatid is the same as the event being removed
2324 $events = $DB->get_records('event', array('repeatid'=>$this->properties->repeatid));
2325 // For each of the returned events populate a calendar_event object and call delete
2326 // make sure the arg passed is false as we are already deleting all repeats
2327 foreach ($events as $event) {
2328 $event = new calendar_event($event);
2329 $event->delete(false);
2330 }
2331 }
2332
2333 return true;
2334 }
2335
2336 /**
2337 * Fetch all event properties
2338 *
2339 * This function returns all of the events properties as an object and optionally
2340 * can prepare an editor for the description field at the same time. This is
2341 * designed to work when the properties are going to be used to set the default
2342 * values of a moodle forms form.
2343 *
2344 * @param bool $prepareeditor If set to true a editor is prepared for use with
2345 * the mforms editor element. (for description)
2346 * @return stdClass Object containing event properties
2347 */
2348 public function properties($prepareeditor=false) {
2349 global $USER, $CFG, $DB;
2350
2351 // First take a copy of the properties. We don't want to actually change the
2352 // properties or we'd forever be converting back and forwards between an
2353 // editor formatted description and not
2354 $properties = clone($this->properties);
2355 // Clean the description here
2356 $properties->description = clean_text($properties->description, $properties->format);
2357
2358 // If set to true we need to prepare the properties for use with an editor
2359 // and prepare the file area
2360 if ($prepareeditor) {
2361
2362 // We may or may not have a property id. If we do then we need to work
2363 // out the context so we can copy the existing files to the draft area
2364 if (!empty($properties->id)) {
2365
2366 if ($properties->eventtype === 'site') {
2367 // Site context
f4700b91 2368 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2369 } else if ($properties->eventtype === 'user') {
2370 // User context
f4700b91 2371 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2372 } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
2373 // First check the course is valid
2374 $course = $DB->get_record('course', array('id'=>$properties->courseid));
2375 if (!$course) {
2376 print_error('invalidcourse');
2377 }
2378 // Course context
f4700b91 2379 $this->editorcontext = $this->properties->context;
76d9df3f
SH
2380 // We have a course and are within the course context so we had
2381 // better use the courses max bytes value
2382 $this->editoroptions['maxbytes'] = $course->maxbytes;
2383 } else {
2384 // If we get here we have a custom event type as used by some
2385 // modules. In this case the event will have been added by
2386 // code and we won't need the editor
2387 $this->editoroptions['maxbytes'] = 0;
2388 $this->editoroptions['maxfiles'] = 0;
2389 }
2390
2391 if (empty($this->editorcontext) || empty($this->editorcontext->id)) {
2392 $contextid = false;
2393 } else {
2394 // Get the context id that is what we really want
2395 $contextid = $this->editorcontext->id;
2396 }
2397 } else {
2398
2399 // If we get here then this is a new event in which case we don't need a
2400 // context as there is no existing files to copy to the draft area.
2401 $contextid = null;
2402 }
2403
2404 // If the contextid === false we don't support files so no preparing
2405 // a draft area
2406 if ($contextid !== false) {
2407 // Just encase it has already been submitted
2408 $draftiddescription = file_get_submitted_draft_itemid('description');
2409 // Prepare the draft area, this copies existing files to the draft area as well
64f93798 2410 $properties->description = file_prepare_draft_area($draftiddescription, $contextid, 'calendar', 'event_description', $properties->id, $this->editoroptions, $properties->description);
76d9df3f
SH
2411 } else {
2412 $draftiddescription = 0;
2413 }
aa6c1ced 2414
76d9df3f
SH
2415 // Structure the description field as the editor requires
2416 $properties->description = array('text'=>$properties->description, 'format'=>$properties->format, 'itemid'=>$draftiddescription);
2417 }
2418
2419 // Finally return the properties
2420 return $properties;
2421 }
2422
2423 /**
2424 * Toggles the visibility of an event
2425 *
2426 * @param null|bool $force If it is left null the events visibility is flipped,
2427 * If it is false the event is made hidden, if it is true it
2428 * is made visible.
08b4a4e1 2429 * @return bool if event is successfully updated, toggle will be visible
76d9df3f
SH
2430 */
2431 public function toggle_visibility($force=null) {
2432 global $CFG, $DB;
2433
2434 // Set visible to the default if it is not already set
2435 if (empty($this->properties->visible)) {
2436 $this->properties->visible = 1;
2437 }
2438
2439 if ($force === true || ($force !== false && $this->properties->visible == 0)) {
2440 // Make this event visible
2441 $this->properties->visible = 1;
2442 // Fire the hook
2443 self::calendar_event_hook('show_event', array($this->properties));
2444 } else {
2445 // Make this event hidden
2446 $this->properties->visible = 0;
2447 // Fire the hook
2448 self::calendar_event_hook('hide_event', array($this->properties));
2449 }
2450
2451 // Update the database to reflect this change
2452 return $DB->set_field('event', 'visible', $this->properties->visible, array('id'=>$this->properties->id));
2453 }
2454
2455 /**
2456 * Attempts to call the hook for the specified action should a calendar type
2457 * by set $CFG->calendar, and the appopriate function defined
2458 *
76d9df3f
SH
2459 * @param string $action One of `update_event`, `add_event`, `delete_event`, `show_event`, `hide_event`
2460 * @param array $args The args to pass to the hook, usually the event is the first element
08b4a4e1 2461 * @return bool attempts to call event hook
76d9df3f
SH
2462 */
2463 public static function calendar_event_hook($action, array $args) {
2464 global $CFG;
2465 static $extcalendarinc;
2466 if ($extcalendarinc === null) {
d2b46849
BJ
2467 if (!empty($CFG->calendar)) {
2468 if (is_readable($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
2469 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
2470 $extcalendarinc = true;
2471 } else {
2472 debugging("Calendar lib file missing or not readable at /calendar/{$CFG->calendar}/lib.php.",
2473 DEBUG_DEVELOPER);
2474 $extcalendarinc = false;
2475 }
76d9df3f
SH
2476 } else {
2477 $extcalendarinc = false;
2478 }
2479 }
2480 if($extcalendarinc === false) {
2481 return false;
2482 }
438e4add 2483 $hook = $CFG->calendar .'_'.$action;
76d9df3f
SH
2484 if (function_exists($hook)) {
2485 call_user_func_array($hook, $args);
2486 return true;
2487 }
2488 return false;
2489 }
2490
2491 /**
2492 * Returns a calendar_event object when provided with an event id
2493 *
2494 * This function makes use of MUST_EXIST, if the event id passed in is invalid
2495 * it will result in an exception being thrown
2496 *
08b4a4e1
RW
2497 * @param int|object $param event object or event id
2498 * @return calendar_event|false status for loading calendar_event
76d9df3f 2499 */
fd699564 2500 public static function load($param) {
76d9df3f 2501 global $DB;
fd699564
SH
2502 if (is_object($param)) {
2503 $event = new calendar_event($param);
2504 } else {
2505 $event = $DB->get_record('event', array('id'=>(int)$param), '*', MUST_EXIST);
2506 $event = new calendar_event($event);
2507 }
76d9df3f
SH
2508 return $event;
2509 }
2510
2511 /**
2512 * Creates a new event and returns a calendar_event object
2513 *
2514 * @param object|array $properties An object containing event properties
2515 * @return calendar_event|false The event object or false if it failed
2516 */
2517 public static function create($properties) {
2518 if (is_array($properties)) {
2519 $properties = (object)$properties;
2520 }
2521 if (!is_object($properties)) {
2522 throw new coding_exception('When creating an event properties should be either an object or an assoc array');
2523 }
f4700b91 2524 $event = new calendar_event($properties);
76d9df3f
SH
2525 if ($event->update($properties)) {
2526 return $event;
2527 } else {
2528 return false;
2529 }
2530 }
2531}
36dc3b71
SH
2532
2533/**
2534 * Calendar information class
2535 *
2536 * This class is used simply to organise the information pertaining to a calendar
2537 * and is used primarily to make information easily available.
08b4a4e1
RW
2538 *
2539 * @package core_calendar
2540 * @category calendar
2541 * @copyright 2010 Sam Hemelryk
2542 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36dc3b71
SH
2543 */
2544class calendar_information {
08b4a4e1 2545 /** @var int The day */
36dc3b71 2546 public $day;
08b4a4e1
RW
2547
2548 /** @var int The month */
36dc3b71 2549 public $month;
08b4a4e1
RW
2550
2551 /** @var int The year */
36dc3b71
SH
2552 public $year;
2553
08b4a4e1 2554 /** @var int A course id */
36dc3b71 2555 public $courseid = null;
08b4a4e1
RW
2556
2557 /** @var array An array of courses */
36dc3b71 2558 public $courses = array();
08b4a4e1
RW
2559
2560 /** @var array An array of groups */
36dc3b71 2561 public $groups = array();
08b4a4e1
RW
2562
2563 /** @var array An array of users */
36dc3b71
SH
2564 public $users = array();
2565
2566 /**
2567 * Creates a new instance
2568 *
08b4a4e1
RW
2569 * @param int $day the number of the day
2570 * @param int $month the number of the month
2571 * @param int $year the number of the year
36dc3b71 2572 */
d8d8bdd9
SH
2573 public function __construct($day=0, $month=0, $year=0) {
2574
2575 $date = usergetdate(time());
2576
2577 if (empty($day)) {
2578 $day = $date['mday'];
2579 }
2580
2581 if (empty($month)) {
2582 $month = $date['mon'];
2583 }
2584
2585 if (empty($year)) {
2586 $year = $date['year'];
2587 }
2588
36dc3b71
SH
2589 $this->day = $day;
2590 $this->month = $month;
2591 $this->year = $year;
2592 }
2593
797cedc7 2594 /**
08b4a4e1 2595 * Initialize calendar information
797cedc7 2596 *
08b4a4e1 2597 * @param stdClass $course object
797cedc7 2598 * @param array $coursestoload An array of courses [$course->id => $course]
08b4a4e1 2599 * @param bool $ignorefilters options to use filter
797cedc7
SH
2600 */
2601 public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) {
2602 $this->courseid = $course->id;
2603 $this->course = $course;
2604 list($courses, $group, $user) = calendar_set_filters($coursestoload, $ignorefilters);
2605 $this->courses = $courses;
2606 $this->groups = $group;
2607 $this->users = $user;
2608 }
2609
36dc3b71
SH
2610 /**
2611 * Ensures the date for the calendar is correct and either sets it to now
2612 * or throws a moodle_exception if not
2613 *
08b4a4e1
RW
2614 * @param bool $defaultonow use current time
2615 * @throws moodle_exception
2616 * @return bool validation of checkdate
36dc3b71
SH
2617 */
2618 public function checkdate($defaultonow = true) {
2619 if (!checkdate($this->month, $this->day, $this->year)) {
2620 if ($defaultonow) {
2621 $now = usergetdate(time());
2622 $this->day = intval($now['mday']);
2623 $this->month = intval($now['mon']);
2624 $this->year = intval($now['year']);
2625 return true;
2626 } else {
2627 throw new moodle_exception('invaliddate');
2628 }
2629 }
2630 return true;
2631 }
2632 /**
2633 * Gets todays timestamp for the calendar
08b4a4e1
RW
2634 *
2635 * @return int today timestamp
36dc3b71
SH
2636 */
2637 public function timestamp_today() {
2638 return make_timestamp($this->year, $this->month, $this->day);
2639 }
2640 /**
2641 * Gets tomorrows timestamp for the calendar
08b4a4e1
RW
2642 *
2643 * @return int tomorrow timestamp
36dc3b71
SH
2644 */
2645 public function timestamp_tomorrow() {
2646 return make_timestamp($this->year, $this->month, $this->day+1);
2647 }
2648 /**
e30390a0 2649 * Adds the pretend blocks for the calendar
36dc3b71
SH
2650 *
2651 * @param core_calendar_renderer $renderer
08b4a4e1
RW
2652 * @param bool $showfilters display filters, false is set as default
2653 * @param string|null $view preference view options (eg: day, month, upcoming)
36dc3b71
SH
2654 */
2655 public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
2656 if ($showfilters) {
2657 $filters = new block_contents();
2658 $filters->content = $renderer->fake_block_filters($this->courseid, $this->day, $this->month, $this->year, $view, $this->courses);
2659 $filters->footer = '';
2660 $filters->title = get_string('eventskey', 'calendar');
2661 $renderer->add_pretend_calendar_block($filters, BLOCK_POS_RIGHT);
2662 }
2663 $block = new block_contents;
2664 $block->content = $renderer->fake_block_threemonths($this);
2665 $block->footer = '';
2666 $block->title = get_string('monthlyview', 'calendar');
2667 $renderer->add_pretend_calendar_block($block, BLOCK_POS_RIGHT);
2668 }
1d5bd3d2 2669}
b5a52acd
JH
2670
2671/**
e30390a0
SH
2672 * Returns option list for the poll interval setting.
2673 *
2674 * @return array An array of poll interval options. Interval => description.
b5a52acd
JH
2675 */
2676function calendar_get_pollinterval_choices() {
2677 return array(
e30390a0
SH
2678 '0' => new lang_string('never', 'calendar'),
2679 '3600' => new lang_string('hourly', 'calendar'),
2680 '86400' => new lang_string('daily', 'calendar'),
2681 '604800' => new lang_string('weekly', 'calendar'),
2682 '2628000' => new lang_string('monthly', 'calendar'),
2683 '31536000' => new lang_string('annually', 'calendar')
2684 );
b5a52acd
JH
2685}
2686
2687/**
e30390a0
SH
2688 * Returns option list of available options for the calendar event type, given the current user and course.
2689 *
4c349ad7 2690 * @param int $courseid The id of the course
e30390a0 2691 * @return array An array containing the event types the user can create.
b5a52acd
JH
2692 */
2693function calendar_get_eventtype_choices($courseid) {
2694 $choices = array();
2695 $allowed = new stdClass;
2696 calendar_get_allowed_types($allowed, $courseid);
2697
2698 if ($allowed->user) {
2699 $choices[0] = get_string('userevents', 'calendar');
2700 }
2701 if ($allowed->site) {
f5b20ea3 2702 $choices[SITEID] = get_string('siteevents', 'calendar');
b5a52acd
JH
2703 }
2704 if (!empty($allowed->courses)) {
2705 $choices[$courseid] = get_string('courseevents', 'calendar');
2706 }
2707 if (!empty($allowed->groups) and is_array($allowed->groups)) {
2708 $choices['group'] = get_string('group');
2709 }
2710
2711 return array($choices, $allowed->groups);
2712}
2713
b5a52acd
JH
2714/**
2715 * Add an iCalendar subscription to the database.
e30390a0
SH
2716 *
2717 * @param stdClass $sub The subscription object (e.g. from the form)
2718 * @return int The insert ID, if any.
b5a52acd
JH
2719 */
2720function calendar_add_subscription($sub) {
2721 global $DB, $USER;
e30390a0 2722
b5a52acd
JH
2723 $sub->courseid = $sub->eventtype;
2724 if ($sub->eventtype == 'group') {
2725 $sub->courseid = $sub->course;
2726 }
2727 $sub->userid = $USER->id;
2728
e30390a0 2729 // File subscriptions never update.
b5a52acd
JH
2730 if (empty($sub->url)) {
2731 $sub->pollinterval = 0;
2732 }
2733
2734 if (!empty($sub->name)) {
2735 if (empty($sub->id)) {
2736 $id = $DB->insert_record('event_subscriptions', $sub);
2737 return $id;
2738 } else {
2739 $DB->update_record('event_subscriptions', $sub);
2740 return $sub->id;
2741 }
2742 } else {
2743 print_error('errorbadsubscription', 'importcalendar');
2744 }
2745}
2746
2747/**
2748 * Add an iCalendar event to the Moodle calendar.
e30390a0
SH
2749 *
2750 * @param object $event The RFC-2445 iCalendar event
2751 * @param int $courseid The course ID
2752 * @param int $subscriptionid The iCalendar subscription ID
2753 * @return int Code: 1=updated, 2=inserted, 0=error
b5a52acd 2754 */
e30390a0 2755function calendar_add_icalendar_event($event, $courseid, $subscriptionid = null) {
b5a52acd
JH
2756 global $DB, $USER;
2757
e30390a0 2758 // Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event.
b5a52acd
JH
2759 if (empty($event->properties['SUMMARY'])) {
2760 return 0;
2761 }
2762
2763 $name = $event->properties['SUMMARY'][0]->value;
2764 $name = str_replace('\n', '<br />', $name);
2765 $name = str_replace('\\', '', $name);
2766 $name = preg_replace('/\s+/', ' ', $name);
e30390a0
SH
2767
2768 $eventrecord = new stdClass;
4c349ad7 2769 $eventrecord->name = clean_param($name, PARAM_NOTAGS);
b5a52acd
JH
2770
2771 if (empty($event->properties['DESCRIPTION'][0]->value)) {
2772 $description = '';
2773 } else {
2774 $description = $event->properties['DESCRIPTION'][0]->value;
2775 $description = str_replace('\n', '<br />', $description);
2776 $description = str_replace('\\', '', $description);
2777 $description = preg_replace('/\s+/', ' ', $description);
2778 }
4c349ad7 2779 $eventrecord->description = clean_param($description, PARAM_NOTAGS);
b5a52acd 2780
e30390a0 2781 // Probably a repeating event with RRULE etc. TODO: skip for now.
b5a52acd
JH
2782 if (empty($event->properties['DTSTART'][0]->value)) {
2783 return 0;
2784 }
2785
2786 $eventrecord->timestart = strtotime($event->properties['DTSTART'][0]->value);
2787 if (empty($event->properties['DTEND'])) {
2788 $eventrecord->timeduration = 3600; // one hour if no end time specified
2789 } else {
2790 $eventrecord->timeduration = strtotime($event->properties['DTEND'][0]->value) - $eventrecord->timestart;
2791 }
2792 $eventrecord->uuid = $event->properties['UID'][0]->value;
2793 $eventrecord->timemodified = time();
2794
e30390a0 2795 // Add the iCal subscription details if required.
b5a52acd
JH
2796 if ($sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid))) {
2797 $eventrecord->subscriptionid = $subscriptionid;
2798 $eventrecord->userid = $sub->userid;
2799 $eventrecord->groupid = $sub->groupid;
2800 $eventrecord->courseid = $sub->courseid;
2801 } else {
2802 $eventrecord->userid = $USER->id;
2803 $eventrecord->groupid = 0; // TODO: ???
2804 $eventrecord->courseid = $courseid;
2805 }
2806
2807 if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid))) {
2808 $eventrecord->id = $updaterecord->id;
2809 if ($DB->update_record('event', $eventrecord)) {
2810 return CALENDAR_IMPORT_EVENT_UPDATED;
2811 } else {
2812 return 0;
2813 }
2814 } else {
2815 if ($DB->insert_record('event', $eventrecord)) {
2816 return CALENDAR_IMPORT_EVENT_INSERTED;
2817 } else {
2818 return 0;
2819 }
2820 }
2821}
2822
2823/**
e30390a0
SH
2824 * Update a subscription from the form data in one of the rows in the existing subscriptions table.
2825 *
2826 * @param int $subscriptionid The ID of the subscription we are acting upon.
2827 * @param int $pollinterval The poll interval to use.
2828 * @param int $action The action to be performed. One of update or remove.
2829 * @return string A log of the import progress, including errors
b5a52acd 2830 */
e30390a0 2831function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) {
b5a52acd
JH
2832 global $DB;
2833
e30390a0 2834 if (empty($subscriptionid)) {
b5a52acd
JH
2835 return '';
2836 }
2837
e30390a0
SH
2838 // Fetch the subscription from the database making sure it exists.
2839 $sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid), '*', MUST_EXIST);
b5a52acd 2840
e30390a0
SH
2841 $strupdate = get_string('update');
2842 $strremove = get_string('remove');
2843 // Update or remove the subscription, based on action.
b5a52acd 2844 switch ($action) {
e30390a0
SH
2845 case $strupdate:
2846 // Skip updating file subscriptions.
2847 if (empty($sub->url)) {
2848 break;
2849 }
2850 $sub->pollinterval = $pollinterval;
2851 $DB->update_record('event_subscriptions', $sub);
b5a52acd 2852
e30390a0
SH
2853 // Update the events.
2854 return "<p>".get_string('subscriptionupdated', 'calendar', $sub->name)."</p>" . calendar_update_subscription_events($subscriptionid);
b5a52acd 2855
e30390a0
SH
2856 case $strremove:
2857 $DB->delete_records('event', array('subscriptionid' => $subscriptionid));
2858 $DB->delete_records('event_subscriptions', array('id' => $subscriptionid));
2859 return get_string('subscriptionremoved', 'calendar', $sub->name);
2860 break;
b5a52acd 2861
e30390a0
SH
2862 default:
2863 break;
b5a52acd
JH
2864 }
2865 return '';
2866}
2867
2868/**
2869 * From a URL, fetch the calendar and return an iCalendar object.
e30390a0
SH
2870 *
2871 * @param string $url The iCalendar URL
2872 * @return stdClass The iCalendar object
b5a52acd
JH
2873 */
2874function calendar_get_icalendar($url) {
e30390a0
SH
2875 global $CFG;
2876
2877 require_once($CFG->libdir.'/filelib.php');
2878
2879 $curl = new curl();
20bdf997 2880 $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => 1, 'CURLOPT_MAXREDIRS' => 5));
e30390a0 2881 $calendar = $curl->get($url);
20bdf997
AA
2882 // Http code validation should actually be the job of curl class.
2883 if (!$calendar || $curl->info['http_code'] != 200 || !empty($curl->errorno)) {
e30390a0
SH
2884 throw new moodle_exception('errorinvalidicalurl', 'calendar');
2885 }
2886
b5a52acd
JH
2887 $ical = new iCalendar();
2888 $ical->unserialize($calendar);
2889 return $ical;
2890}
2891
2892/**
2893 * Import events from an iCalendar object into a course calendar.
e30390a0
SH
2894 *
2895 * @param stdClass $ical The iCalendar object.
2896 * @param int $courseid The course ID for the calendar.
2897 * @param int $subscriptionid The subscription ID.
2898 * @return string A log of the import progress, including errors.
b5a52acd 2899 */
e30390a0 2900function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = null) {
b5a52acd
JH
2901 global $DB;
2902 $return = '';
2903 $eventcount = 0;
2904 $updatecount = 0;
2905
e30390a0
SH
2906 // Large calendars take a while...
2907 set_time_limit(300);
b5a52acd 2908
e30390a0 2909 // Mark all events in a subscription with a zero timestamp.
b5a52acd 2910 if (!empty($subscriptionid)) {
e30390a0 2911 $sql = "UPDATE {event} SET timemodified = :time WHERE subscriptionid = :id";
b5a52acd
JH
2912 $DB->execute($sql, array('time' => 0, 'id' => $subscriptionid));
2913 }
4c349ad7 2914 foreach ($ical->components['VEVENT'] as $event) {
b5a52acd
JH
2915 $res = calendar_add_icalendar_event($event, $courseid, $subscriptionid);
2916 switch ($res) {
2917 case CALENDAR_IMPORT_EVENT_UPDATED:
2918 $updatecount++;
2919 break;
2920 case CALENDAR_IMPORT_EVENT_INSERTED:
2921 $eventcount++;
2922 break;
2923 case 0:
4c349ad7 2924 $return .= '<p>'.get_string('erroraddingevent', 'calendar').': '.(empty($event->properties['SUMMARY'])?'('.get_string('notitle', 'calendar').')':$event->properties['SUMMARY'][0]->value)." </p>\n";
b5a52acd
JH
2925 break;
2926 }
2927 }
e30390a0
SH
2928 $return .= "<p> ".get_string('eventsimported', 'calendar', $eventcount)."</p>";
2929 $return .= "<p> ".get_string('eventsupdated', 'calendar', $updatecount)."</p>";
b5a52acd 2930
e30390a0 2931 // Delete remaining zero-marked events since they're not in remote calendar.
b5a52acd
JH
2932 if (!empty($subscriptionid)) {
2933 $deletecount = $DB->count_records('event', array('timemodified' => 0, 'subscriptionid' => $subscriptionid));
2934 if (!empty($deletecount)) {
e30390a0 2935 $sql = "DELETE FROM {event} WHERE timemodified = :time AND subscriptionid = :id";
b5a52acd 2936 $DB->execute($sql, array('time' => 0, 'id' => $subscriptionid));
4c349ad7 2937 $return .= "<p> ".get_string('eventsdeleted', 'calendar').": {$deletecount} </p>\n";
b5a52acd
JH
2938 }
2939 }
2940
2941 return $return;
2942}
2943
2944/**
2945 * Fetch a calendar subscription and update the events in the calendar.
e30390a0
SH
2946 *
2947 * @param int $subscriptionid The course ID for the calendar.
2948 * @return string A log of the import progress, including errors.
b5a52acd
JH
2949 */
2950function calendar_update_subscription_events($subscriptionid) {
2951 global $DB;
b5a52acd
JH
2952
2953 $sub = $DB->get_record('event_subscriptions', array('id' => $subscriptionid));
2954 if (empty($sub)) {
2955 print_error('errorbadsubscription', 'calendar');
2956 }
e30390a0 2957 // Don't update a file subscription. TODO: Update from a new uploaded file.
b5a52acd
JH
2958 if (empty($sub->url)) {
2959 return 'File subscription not updated.';
2960 }
2961 $ical = calendar_get_icalendar($sub->url);
2962 $return = calendar_import_icalendar_events($ical, $sub->courseid, $subscriptionid);
2963 $sub->lastupdated = time();
2964 $DB->update_record('event_subscriptions', $sub);
2965 return $return;
2966}
2967
2968/**
2969 * Update calendar subscriptions.
e30390a0
SH
2970 *
2971 * @return bool
b5a52acd
JH
2972 */
2973function calendar_cron() {
e30390a0
SH
2974 global $CFG, $DB;
2975
2976 // In order to execute this we need bennu.
2977 require_once($CFG->libdir.'/bennu/bennu.inc.php');
2978
24a3115c 2979 mtrace('Updating calendar subscriptions:');
e30390a0 2980
b5a52acd 2981 $time = time();
e30390a0 2982 $subscriptions = $DB->get_records_sql('SELECT * FROM {event_subscriptions} WHERE pollinterval > 0 AND lastupdated + pollinterval < ?', array($time));
4c349ad7 2983 foreach ($subscriptions as $sub) {
24a3115c 2984 mtrace("Updating calendar subscription {$sub->name} in course {$sub->courseid}");
e30390a0
SH
2985 try {
2986 $log = calendar_update_subscription_events($sub->id);
2987 } catch (moodle_exception $ex) {
2988
2989 }
b5a52acd
JH
2990 mtrace(trim(strip_tags($log)));
2991 }
e30390a0 2992
24a3115c 2993 mtrace('Finished updating calendar subscriptions.');
b5a52acd 2994
e30390a0 2995 return true;
24a3115c 2996}