16c5f283feb73fb9ed7e4a883da531ec4f13b7f0
[moodle.git] / calendar / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
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  */
26 /**
27  *  These are read by the administration component to provide default values
28  */
30 /**
31  * CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD - default value of upcoming event preference
32  */
33 define('CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD', 21);
35 /**
36  * CALENDAR_DEFAULT_UPCOMING_MAXEVENTS - default value to display the maximum number of upcoming event
37  */
38 define('CALENDAR_DEFAULT_UPCOMING_MAXEVENTS', 10);
40 /**
41  * CALENDAR_DEFAULT_STARTING_WEEKDAY - default value to display the starting weekday
42  */
43 define('CALENDAR_DEFAULT_STARTING_WEEKDAY', 1);
45 // This is a packed bitfield: day X is "weekend" if $field & (1 << X) is true
46 // Default value = 65 = 64 + 1 = 2^6 + 2^0 = Saturday & Sunday
48 /**
49  * CALENDAR_DEFAULT_WEEKEND - default value for weekend (Saturday & Sunday)
50  */
51 define('CALENDAR_DEFAULT_WEEKEND', 65);
53 /**
54  * CALENDAR_URL - path to calendar's folder
55  */
56 define('CALENDAR_URL', $CFG->wwwroot.'/calendar/');
58 /**
59  * CALENDAR_TF_24 - Calendar time in 24 hours format
60  */
61 define('CALENDAR_TF_24', '%H:%M');
63 /**
64  * CALENDAR_TF_12 - Calendar time in 12 hours format
65  */
66 define('CALENDAR_TF_12', '%I:%M %p');
68 /**
69  * CALENDAR_EVENT_GLOBAL - Global calendar event types
70  */
71 define('CALENDAR_EVENT_GLOBAL', 1);
73 /**
74  * CALENDAR_EVENT_COURSE - Course calendar event types
75  */
76 define('CALENDAR_EVENT_COURSE', 2);
78 /**
79  * CALENDAR_EVENT_GROUP - group calendar event types
80  */
81 define('CALENDAR_EVENT_GROUP', 4);
83 /**
84  * CALENDAR_EVENT_USER - user calendar event types
85  */
86 define('CALENDAR_EVENT_USER', 8);
88 /**
89  * CALENDAR_STARTING_WEEKDAY - has since been deprecated please call calendar_get_starting_weekday() instead
90  *
91  * @deprecated Moodle 2.0 MDL-24284- please do not use this function any more.
92  * @todo MDL-31132 This will be deleted in Moodle 2.3.
93  * @see calendar_get_starting_weekday()
94  */
95 define('CALENDAR_STARTING_WEEKDAY', CALENDAR_DEFAULT_STARTING_WEEKDAY);
97 /**
98  * Return the days of the week
99  *
100  * @return array array of days
101  */
102 function calendar_get_days() {
103     return array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
106 /**
107  * Gets the first day of the week
108  *
109  * Used to be define('CALENDAR_STARTING_WEEKDAY', blah);
110  *
111  * @return int
112  */
113 function calendar_get_starting_weekday() {
114     global $CFG;
116     if (isset($CFG->calendar_startwday)) {
117         $firstday = $CFG->calendar_startwday;
118     } else {
119         $firstday = get_string('firstdayofweek', 'langconfig');
120     }
122     if(!is_numeric($firstday)) {
123         return CALENDAR_DEFAULT_STARTING_WEEKDAY;
124     } else {
125         return intval($firstday) % 7;
126     }
129 /**
130  * Generates the HTML for a miniature calendar
131  *
132  * @param array $courses list of course
133  * @param array $groups list of group
134  * @param array $users user's info
135  * @param int $cal_month calendar month in numeric, default is set to false
136  * @param int $cal_year calendar month in numeric, default is set to false
137  * @return string $content return html table for mini calendar
138  */
139 function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_year = false) {
140     global $CFG, $USER, $OUTPUT;
142     $display = new stdClass;
143     $display->minwday = get_user_preferences('calendar_startwday', calendar_get_starting_weekday());
144     $display->maxwday = $display->minwday + 6;
146     $content = '';
148     if(!empty($cal_month) && !empty($cal_year)) {
149         $thisdate = usergetdate(time()); // Date and time the user sees at his location
150         if($cal_month == $thisdate['mon'] && $cal_year == $thisdate['year']) {
151             // Navigated to this month
152             $date = $thisdate;
153             $display->thismonth = true;
154         } else {
155             // Navigated to other month, let's do a nice trick and save us a lot of work...
156             if(!checkdate($cal_month, 1, $cal_year)) {
157                 $date = array('mday' => 1, 'mon' => $thisdate['mon'], 'year' => $thisdate['year']);
158                 $display->thismonth = true;
159             } else {
160                 $date = array('mday' => 1, 'mon' => $cal_month, 'year' => $cal_year);
161                 $display->thismonth = false;
162             }
163         }
164     } else {
165         $date = usergetdate(time()); // Date and time the user sees at his location
166         $display->thismonth = true;
167     }
169     // Fill in the variables we 're going to use, nice and tidy
170     list($d, $m, $y) = array($date['mday'], $date['mon'], $date['year']); // This is what we want to display
171     $display->maxdays = calendar_days_in_month($m, $y);
173     if (get_user_timezone_offset() < 99) {
174         // We 'll keep these values as GMT here, and offset them when the time comes to query the db
175         $display->tstart = gmmktime(0, 0, 0, $m, 1, $y); // This is GMT
176         $display->tend = gmmktime(23, 59, 59, $m, $display->maxdays, $y); // GMT
177     } else {
178         // no timezone info specified
179         $display->tstart = mktime(0, 0, 0, $m, 1, $y);
180         $display->tend = mktime(23, 59, 59, $m, $display->maxdays, $y);
181     }
183     $startwday = dayofweek(1, $m, $y);
185     // Align the starting weekday to fall in our display range
186     // This is simple, not foolproof.
187     if($startwday < $display->minwday) {
188         $startwday += 7;
189     }
191     // TODO: THIS IS TEMPORARY CODE!
192     // [pj] I was just reading through this and realized that I when writing this code I was probably
193     // asking for trouble, as all these time manipulations seem to be unnecessary and a simple
194     // make_timestamp would accomplish the same thing. So here goes a test:
195     //$test_start = make_timestamp($y, $m, 1);
196     //$test_end   = make_timestamp($y, $m, $display->maxdays, 23, 59, 59);
197     //if($test_start != usertime($display->tstart) - dst_offset_on($display->tstart)) {
198         //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);
199     //}
200     //if($test_end != usertime($display->tend) - dst_offset_on($display->tend)) {
201         //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);
202     //}
205     // Get the events matching our criteria. Don't forget to offset the timestamps for the user's TZ!
206     $events = calendar_get_events(
207         usertime($display->tstart) - dst_offset_on($display->tstart),
208         usertime($display->tend) - dst_offset_on($display->tend),
209         $users, $groups, $courses);
211     // Set event course class for course events
212     if (!empty($events)) {
213         foreach ($events as $eventid => $event) {
214             if (!empty($event->modulename)) {
215                 $cm = get_coursemodule_from_instance($event->modulename, $event->instance);
216                 if (!groups_course_module_visible($cm)) {
217                     unset($events[$eventid]);
218                 }
219             }
220         }
221     }
223     // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after
224     // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month
225     // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra
226     // arguments to this function.
228     $hrefparams = array();
229     if(!empty($courses)) {
230         $courses = array_diff($courses, array(SITEID));
231         if(count($courses) == 1) {
232             $hrefparams['course'] = reset($courses);
233         }
234     }
236     // We want to have easy access by day, since the display is on a per-day basis.
237     // Arguments passed by reference.
238     //calendar_events_by_day($events, $display->tstart, $eventsbyday, $durationbyday, $typesbyday);
239     calendar_events_by_day($events, $m, $y, $eventsbyday, $durationbyday, $typesbyday, $courses);
241     //Accessibility: added summary and <abbr> elements.
242     $days_title = calendar_get_days();
244     $summary = get_string('calendarheading', 'calendar', userdate(make_timestamp($y, $m), get_string('strftimemonthyear')));
245     $summary = get_string('tabledata', 'access', $summary);
246     $content .= '<table class="minicalendar calendartable" summary="'.$summary.'">'; // Begin table
247     $content .= '<tr class="weekdays">'; // Header row: day names
249     // Print out the names of the weekdays
250     $days = array('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat');
251     for($i = $display->minwday; $i <= $display->maxwday; ++$i) {
252         // This uses the % operator to get the correct weekday no matter what shift we have
253         // applied to the $display->minwday : $display->maxwday range from the default 0 : 6
254         $content .= '<th scope="col"><abbr title="'. get_string($days_title[$i % 7], 'calendar') .'">'.
255             get_string($days[$i % 7], 'calendar') ."</abbr></th>\n";
256     }
258     $content .= '</tr><tr>'; // End of day names; prepare for day numbers
260     // For the table display. $week is the row; $dayweek is the column.
261     $dayweek = $startwday;
263     // Paddding (the first week may have blank days in the beginning)
264     for($i = $display->minwday; $i < $startwday; ++$i) {
265         $content .= '<td class="dayblank">&nbsp;</td>'."\n";
266     }
268     $weekend = CALENDAR_DEFAULT_WEEKEND;
269     if (isset($CFG->calendar_weekend)) {
270         $weekend = intval($CFG->calendar_weekend);
271     }
273     // Now display all the calendar
274     for($day = 1; $day <= $display->maxdays; ++$day, ++$dayweek) {
275         if($dayweek > $display->maxwday) {
276             // We need to change week (table row)
277             $content .= '</tr><tr>';
278             $dayweek = $display->minwday;
279         }
281         // Reset vars
282         $cell = '';
283         if ($weekend & (1 << ($dayweek % 7))) {
284             // Weekend. This is true no matter what the exact range is.
285             $class = 'weekend day';
286         } else {
287             // Normal working day.
288             $class = 'day';
289         }
291         // Special visual fx if an event is defined
292         if(isset($eventsbyday[$day])) {
293             $class .= ' hasevent';
294             $hrefparams['view'] = 'day';
295             $dayhref = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $hrefparams), $day, $m, $y);
297             $popupcontent = '';
298             foreach($eventsbyday[$day] as $eventid) {
299                 if (!isset($events[$eventid])) {
300                     continue;
301                 }
302                 $event = $events[$eventid];
303                 $popupalt  = '';
304                 $component = 'moodle';
305                 if(!empty($event->modulename)) {
306                     $popupicon = 'icon';
307                     $popupalt  = $event->modulename;
308                     $component = $event->modulename;
309                 } else if ($event->courseid == SITEID) {                                // Site event
310                     $popupicon = 'c/site';
311                 } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {      // Course event
312                     $popupicon = 'c/course';
313                 } else if ($event->groupid) {                                      // Group event
314                     $popupicon = 'c/group';
315                 } else if ($event->userid) {                                       // User event
316                     $popupicon = 'c/user';
317                 }
319                 $dayhref->set_anchor('event_'.$event->id);
321                 $popupcontent .= html_writer::start_tag('div');
322                 $popupcontent .= $OUTPUT->pix_icon($popupicon, $popupalt, $component);
323                 $popupcontent .= html_writer::link($dayhref, format_string($event->name, true));
324                 $popupcontent .= html_writer::end_tag('div');
325             }
327             //Accessibility: functionality moved to calendar_get_popup.
328             if($display->thismonth && $day == $d) {
329                 $popup = calendar_get_popup(true, $events[$eventid]->timestart, $popupcontent);
330             } else {
331                 $popup = calendar_get_popup(false, $events[$eventid]->timestart, $popupcontent);
332             }
334             // Class and cell content
335             if(isset($typesbyday[$day]['startglobal'])) {
336                 $class .= ' calendar_event_global';
337             } else if(isset($typesbyday[$day]['startcourse'])) {
338                 $class .= ' calendar_event_course';
339             } else if(isset($typesbyday[$day]['startgroup'])) {
340                 $class .= ' calendar_event_group';
341             } else if(isset($typesbyday[$day]['startuser'])) {
342                 $class .= ' calendar_event_user';
343             }
344             $cell = '<a href="'.(string)$dayhref.'" '.$popup.'>'.$day.'</a>';
345         } else {
346             $cell = $day;
347         }
349         $durationclass = false;
350         if (isset($typesbyday[$day]['durationglobal'])) {
351             $durationclass = ' duration_global';
352         } else if(isset($typesbyday[$day]['durationcourse'])) {
353             $durationclass = ' duration_course';
354         } else if(isset($typesbyday[$day]['durationgroup'])) {
355             $durationclass = ' duration_group';
356         } else if(isset($typesbyday[$day]['durationuser'])) {
357             $durationclass = ' duration_user';
358         }
359         if ($durationclass) {
360             $class .= ' duration '.$durationclass;
361         }
363         // If event has a class set then add it to the table day <td> tag
364         // Note: only one colour for minicalendar
365         if(isset($eventsbyday[$day])) {
366             foreach($eventsbyday[$day] as $eventid) {
367                 if (!isset($events[$eventid])) {
368                     continue;
369                 }
370                 $event = $events[$eventid];
371                 if (!empty($event->class)) {
372                     $class .= ' '.$event->class;
373                 }
374                 break;
375             }
376         }
378         // Special visual fx for today
379         //Accessibility: hidden text for today, and popup.
380         if($display->thismonth && $day == $d) {
381             $class .= ' today';
382             $today = get_string('today', 'calendar').' '.userdate(time(), get_string('strftimedayshort'));
384             if(! isset($eventsbyday[$day])) {
385                 $class .= ' eventnone';
386                 $popup = calendar_get_popup(true, false);
387                 $cell = '<a href="#" '.$popup.'>'.$day.'</a>';
388             }
389             $cell = get_accesshide($today.' ').$cell;
390         }
392         // Just display it
393         if(!empty($class)) {
394             $class = ' class="'.$class.'"';
395         }
396         $content .= '<td'.$class.'>'.$cell."</td>\n";
397     }
399     // Paddding (the last week may have blank days at the end)
400     for($i = $dayweek; $i <= $display->maxwday; ++$i) {
401         $content .= '<td class="dayblank">&nbsp;</td>';
402     }
403     $content .= '</tr>'; // Last row ends
405     $content .= '</table>'; // Tabular display of days ends
407     return $content;
410 /**
411  * Gets the calendar popup
412  *
413  * It called at multiple points in from calendar_get_mini.
414  * Copied and modified from calendar_get_mini.
415  *
416  * @param bool $is_today false except when called on the current day.
417  * @param mixed $event_timestart $events[$eventid]->timestart, OR false if there are no events.
418  * @param string $popupcontent content for the popup window/layout
419  * @return string of eventid for the calendar_tooltip popup window/layout
420  */
421 function calendar_get_popup($is_today, $event_timestart, $popupcontent='') {
422     global $PAGE;
423     static $popupcount;
424     if ($popupcount === null) {
425         $popupcount = 1;
426     }
427     $popupcaption = '';
428     if($is_today) {
429         $popupcaption = get_string('today', 'calendar').' ';
430     }
431     if (false === $event_timestart) {
432         $popupcaption .= userdate(time(), get_string('strftimedayshort'));
433         $popupcontent = get_string('eventnone', 'calendar');
435     } else {
436         $popupcaption .= get_string('eventsfor', 'calendar', userdate($event_timestart, get_string('strftimedayshort')));
437     }
438     $id = 'calendar_tooltip_'.$popupcount;
439     $PAGE->requires->yui_module('moodle-calendar-eventmanager', 'M.core_calendar.add_event', array(array('eventId'=>$id,'title'=>$popupcaption, 'content'=>$popupcontent)));
441     $popupcount++;
442     return 'id="'.$id.'"';
445 /**
446  * Gets the calendar upcoming event
447  *
448  * @param array $courses array of courses
449  * @param array|int|bool $groups array of groups, group id or boolean for all/no group events
450  * @param array|int|bool $users array of users, user id or boolean for all/no user events
451  * @param int $daysinfuture number of days in the future we 'll look
452  * @param int $maxevents maximum number of events
453  * @param int $fromtime start time
454  * @return array $output array of upcoming events
455  */
456 function calendar_get_upcoming($courses, $groups, $users, $daysinfuture, $maxevents, $fromtime=0) {
457     global $CFG, $COURSE, $DB;
459     $display = new stdClass;
460     $display->range = $daysinfuture; // How many days in the future we 'll look
461     $display->maxevents = $maxevents;
463     $output = array();
465     // Prepare "course caching", since it may save us a lot of queries
466     $coursecache = array();
468     $processed = 0;
469     $now = time(); // We 'll need this later
470     $usermidnighttoday = usergetmidnight($now);
472     if ($fromtime) {
473         $display->tstart = $fromtime;
474     } else {
475         $display->tstart = $usermidnighttoday;
476     }
478     // This works correctly with respect to the user's DST, but it is accurate
479     // only because $fromtime is always the exact midnight of some day!
480     $display->tend = usergetmidnight($display->tstart + DAYSECS * $display->range + 3 * HOURSECS) - 1;
482     // Get the events matching our criteria
483     $events = calendar_get_events($display->tstart, $display->tend, $users, $groups, $courses);
485     // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after
486     // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month
487     // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra
488     // arguments to this function.
490     $hrefparams = array();
491     if(!empty($courses)) {
492         $courses = array_diff($courses, array(SITEID));
493         if(count($courses) == 1) {
494             $hrefparams['course'] = reset($courses);
495         }
496     }
498     if ($events !== false) {
500         $modinfo = get_fast_modinfo($COURSE);
502         foreach($events as $event) {
505             if (!empty($event->modulename)) {
506                 if ($event->courseid == $COURSE->id) {
507                     if (isset($modinfo->instances[$event->modulename][$event->instance])) {
508                         $cm = $modinfo->instances[$event->modulename][$event->instance];
509                         if (!$cm->uservisible) {
510                             continue;
511                         }
512                     }
513                 } else {
514                     if (!$cm = get_coursemodule_from_instance($event->modulename, $event->instance)) {
515                         continue;
516                     }
517                     if (!coursemodule_visible_for_user($cm)) {
518                         continue;
519                     }
520                 }
521                 if ($event->modulename == 'assignment'){
522                     // create calendar_event to test edit_event capability
523                     // this new event will also prevent double creation of calendar_event object
524                     $checkevent = new calendar_event($event);
525                     // TODO: rewrite this hack somehow
526                     if (!calendar_edit_event_allowed($checkevent)){ // cannot manage entries, eg. student
527                         if (!$assignment = $DB->get_record('assignment', array('id'=>$event->instance))) {
528                             // print_error("invalidid", 'assignment');
529                             continue;
530                         }
531                         // assign assignment to assignment object to use hidden_is_hidden method
532                         require_once($CFG->dirroot.'/mod/assignment/lib.php');
534                         if (!file_exists($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php')) {
535                             continue;
536                         }
537                         require_once ($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
539                         $assignmentclass = 'assignment_'.$assignment->assignmenttype;
540                         $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm);
542                         if ($assignmentinstance->description_is_hidden()){//force not to show description before availability
543                             $event->description = get_string('notavailableyet', 'assignment');
544                         }
545                     }
546                 }
547             }
549             if ($processed >= $display->maxevents) {
550                 break;
551             }
553             $event->time = calendar_format_event_time($event, $now, $hrefparams);
554             $output[] = $event;
555             ++$processed;
556         }
557     }
558     return $output;
561 /**
562  * Add calendar event metadata
563  *
564  * @param stdClass $event event info
565  * @return stdClass $event metadata
566  */
567 function calendar_add_event_metadata($event) {
568     global $CFG, $OUTPUT;
570     //Support multilang in event->name
571     $event->name = format_string($event->name,true);
573     if(!empty($event->modulename)) {                                // Activity event
574         // The module name is set. I will assume that it has to be displayed, and
575         // also that it is an automatically-generated event. And of course that the
576         // fields for get_coursemodule_from_instance are set correctly.
577         $module = calendar_get_module_cached($coursecache, $event->modulename, $event->instance);
579         if ($module === false) {
580             return;
581         }
583         $modulename = get_string('modulename', $event->modulename);
584         if (get_string_manager()->string_exists($event->eventtype, $event->modulename)) {
585             // will be used as alt text if the event icon
586             $eventtype = get_string($event->eventtype, $event->modulename);
587         } else {
588             $eventtype = '';
589         }
590         $icon = $OUTPUT->pix_url('icon', $event->modulename) . '';
592         $context = get_context_instance(CONTEXT_COURSE, $module->course);
593         $fullname = format_string($coursecache[$module->course]->fullname, true, array('context' => $context));
595         $event->icon = '<img height="16" width="16" src="'.$icon.'" alt="'.$eventtype.'" title="'.$modulename.'" style="vertical-align: middle;" />';
596         $event->referer = '<a href="'.$CFG->wwwroot.'/mod/'.$event->modulename.'/view.php?id='.$module->id.'">'.$event->name.'</a>';
597         $event->courselink = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$module->course.'">'.$fullname.'</a>';
598         $event->cmid = $module->id;
601     } else if($event->courseid == SITEID) {                              // Site event
602         $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/site') . '" alt="'.get_string('globalevent', 'calendar').'" style="vertical-align: middle;" />';
603         $event->cssclass = 'calendar_event_global';
604     } else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {          // Course event
605         calendar_get_course_cached($coursecache, $event->courseid);
607         $context = get_context_instance(CONTEXT_COURSE, $event->courseid);
608         $fullname = format_string($coursecache[$event->courseid]->fullname, true, array('context' => $context));
610         $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/course') . '" alt="'.get_string('courseevent', 'calendar').'" style="vertical-align: middle;" />';
611         $event->courselink = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$event->courseid.'">'.$fullname.'</a>';
612         $event->cssclass = 'calendar_event_course';
613     } else if ($event->groupid) {                                    // Group event
614         $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/group') . '" alt="'.get_string('groupevent', 'calendar').'" style="vertical-align: middle;" />';
615         $event->cssclass = 'calendar_event_group';
616     } else if($event->userid) {                                      // User event
617         $event->icon = '<img height="16" width="16" src="'.$OUTPUT->pix_url('c/user') . '" alt="'.get_string('userevent', 'calendar').'" style="vertical-align: middle;" />';
618         $event->cssclass = 'calendar_event_user';
619     }
620     return $event;
623 /**
624  * Get calendar events
625  *
626  * @param int $tstart Start time of time range for events
627  * @param int $tend End time of time range for events
628  * @param array|int|boolean $users array of users, user id or boolean for all/no user events
629  * @param array|int|boolean $groups array of groups, group id or boolean for all/no group events
630  * @param array|int|boolean $courses array of courses, course id or boolean for all/no course events
631  * @param boolean $withduration whether only events starting within time range selected
632  *                              or events in progress/already started selected as well
633  * @param boolean $ignorehidden whether to select only visible events or all events
634  * @return array $events of selected events or an empty array if there aren't any (or there was an error)
635  */
636 function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withduration=true, $ignorehidden=true) {
637     global $DB;
639     $whereclause = '';
640     // Quick test
641     if(is_bool($users) && is_bool($groups) && is_bool($courses)) {
642         return array();
643     }
645     if(is_array($users) && !empty($users)) {
646         // Events from a number of users
647         if(!empty($whereclause)) $whereclause .= ' OR';
648         $whereclause .= ' (userid IN ('.implode(',', $users).') AND courseid = 0 AND groupid = 0)';
649     } else if(is_numeric($users)) {
650         // Events from one user
651         if(!empty($whereclause)) $whereclause .= ' OR';
652         $whereclause .= ' (userid = '.$users.' AND courseid = 0 AND groupid = 0)';
653     } else if($users === true) {
654         // Events from ALL users
655         if(!empty($whereclause)) $whereclause .= ' OR';
656         $whereclause .= ' (userid != 0 AND courseid = 0 AND groupid = 0)';
657     } else if($users === false) {
658         // No user at all, do nothing
659     }
661     if(is_array($groups) && !empty($groups)) {
662         // Events from a number of groups
663         if(!empty($whereclause)) $whereclause .= ' OR';
664         $whereclause .= ' groupid IN ('.implode(',', $groups).')';
665     } else if(is_numeric($groups)) {
666         // Events from one group
667         if(!empty($whereclause)) $whereclause .= ' OR ';
668         $whereclause .= ' groupid = '.$groups;
669     } else if($groups === true) {
670         // Events from ALL groups
671         if(!empty($whereclause)) $whereclause .= ' OR ';
672         $whereclause .= ' groupid != 0';
673     }
674     // boolean false (no groups at all): we don't need to do anything
676     if(is_array($courses) && !empty($courses)) {
677         if(!empty($whereclause)) {
678             $whereclause .= ' OR';
679         }
680         $whereclause .= ' (groupid = 0 AND courseid IN ('.implode(',', $courses).'))';
681     } else if(is_numeric($courses)) {
682         // One course
683         if(!empty($whereclause)) $whereclause .= ' OR';
684         $whereclause .= ' (groupid = 0 AND courseid = '.$courses.')';
685     } else if ($courses === true) {
686         // Events from ALL courses
687         if(!empty($whereclause)) $whereclause .= ' OR';
688         $whereclause .= ' (groupid = 0 AND courseid != 0)';
689     }
691     // Security check: if, by now, we have NOTHING in $whereclause, then it means
692     // that NO event-selecting clauses were defined. Thus, we won't be returning ANY
693     // events no matter what. Allowing the code to proceed might return a completely
694     // valid query with only time constraints, thus selecting ALL events in that time frame!
695     if(empty($whereclause)) {
696         return array();
697     }
699     if($withduration) {
700         $timeclause = '(timestart >= '.$tstart.' OR timestart + timeduration > '.$tstart.') AND timestart <= '.$tend;
701     }
702     else {
703         $timeclause = 'timestart >= '.$tstart.' AND timestart <= '.$tend;
704     }
705     if(!empty($whereclause)) {
706         // We have additional constraints
707         $whereclause = $timeclause.' AND ('.$whereclause.')';
708     }
709     else {
710         // Just basic time filtering
711         $whereclause = $timeclause;
712     }
714     if ($ignorehidden) {
715         $whereclause .= ' AND visible = 1';
716     }
718     $events = $DB->get_records_select('event', $whereclause, null, 'timestart');
719     if ($events === false) {
720         $events = array();
721     }
722     return $events;
725 /**
726  * Get control options for Calendar
727  *
728  * @param string $type of calendar
729  * @param array $data calendar information
730  * @return string $content return available control for the calender in html
731  */
732 function calendar_top_controls($type, $data) {
733     global $CFG;
734     $content = '';
735     if(!isset($data['d'])) {
736         $data['d'] = 1;
737     }
739     // Ensure course id passed if relevant
740     // Required due to changes in view/lib.php mainly (calendar_session_vars())
741     $courseid = '';
742     if (!empty($data['id'])) {
743         $courseid = '&amp;course='.$data['id'];
744     }
746     if(!checkdate($data['m'], $data['d'], $data['y'])) {
747         $time = time();
748     }
749     else {
750         $time = make_timestamp($data['y'], $data['m'], $data['d']);
751     }
752     $date = usergetdate($time);
754     $data['m'] = $date['mon'];
755     $data['y'] = $date['year'];
757     //Accessibility: calendar block controls, replaced <table> with <div>.
758     //$nexttext = link_arrow_right(get_string('monthnext', 'access'), $url='', $accesshide=true);
759     //$prevtext = link_arrow_left(get_string('monthprev', 'access'), $url='', $accesshide=true);
761     switch($type) {
762         case 'frontpage':
763             list($prevmonth, $prevyear) = calendar_sub_month($data['m'], $data['y']);
764             list($nextmonth, $nextyear) = calendar_add_month($data['m'], $data['y']);
765             $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), 'index.php?', 0, $nextmonth, $nextyear, $accesshide=true);
766             $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), 'index.php?', 0, $prevmonth, $prevyear, true);
768             $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'month')), 1, $data['m'], $data['y']);
769             if (!empty($data['id'])) {
770                 $calendarlink->param('course', $data['id']);
771             }
773             if (right_to_left()) {
774                 $left = $nextlink;
775                 $right = $prevlink;
776             } else {
777                 $left = $prevlink;
778                 $right = $nextlink;
779             }
781             $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
782             $content .= $left.'<span class="hide"> | </span>';
783             $content .= html_writer::tag('span', html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')), array('title'=>get_string('monththis','calendar'))), array('class'=>'current'));
784             $content .= '<span class="hide"> | </span>'. $right;
785             $content .= "<span class=\"clearer\"><!-- --></span>\n";
786             $content .= html_writer::end_tag('div');
788             break;
789         case 'course':
790             list($prevmonth, $prevyear) = calendar_sub_month($data['m'], $data['y']);
791             list($nextmonth, $nextyear) = calendar_add_month($data['m'], $data['y']);
792             $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), 'view.php?id='.$data['id'].'&amp;', 0, $nextmonth, $nextyear, $accesshide=true);
793             $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), 'view.php?id='.$data['id'].'&amp;', 0, $prevmonth, $prevyear, true);
795             $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'month')), 1, $data['m'], $data['y']);
796             if (!empty($data['id'])) {
797                 $calendarlink->param('course', $data['id']);
798             }
800             if (right_to_left()) {
801                 $left = $nextlink;
802                 $right = $prevlink;
803             } else {
804                 $left = $prevlink;
805                 $right = $nextlink;
806             }
808             $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
809             $content .= $left.'<span class="hide"> | </span>';
810             $content .= html_writer::tag('span', html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')), array('title'=>get_string('monththis','calendar'))), array('class'=>'current'));
811             $content .= '<span class="hide"> | </span>'. $right;
812             $content .= "<span class=\"clearer\"><!-- --></span>";
813             $content .= html_writer::end_tag('div');
814             break;
815         case 'upcoming':
816             $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'upcoming')), 1, $data['m'], $data['y']);
817             if (!empty($data['id'])) {
818                 $calendarlink->param('course', $data['id']);
819             }
820             $calendarlink = html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')));
821             $content .= html_writer::tag('div', $calendarlink, array('class'=>'centered'));
822             break;
823         case 'display':
824             $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view'=>'month')), 1, $data['m'], $data['y']);
825             if (!empty($data['id'])) {
826                 $calendarlink->param('course', $data['id']);
827             }
828             $calendarlink = html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')));
829             $content .= html_writer::tag('h3', $calendarlink);
830             break;
831         case 'month':
832             list($prevmonth, $prevyear) = calendar_sub_month($data['m'], $data['y']);
833             list($nextmonth, $nextyear) = calendar_add_month($data['m'], $data['y']);
834             $prevdate = make_timestamp($prevyear, $prevmonth, 1);
835             $nextdate = make_timestamp($nextyear, $nextmonth, 1);
836             $prevlink = calendar_get_link_previous(userdate($prevdate, get_string('strftimemonthyear')), 'view.php?view=month'.$courseid.'&amp;', 1, $prevmonth, $prevyear);
837             $nextlink = calendar_get_link_next(userdate($nextdate, get_string('strftimemonthyear')), 'view.php?view=month'.$courseid.'&amp;', 1, $nextmonth, $nextyear);
839             if (right_to_left()) {
840                 $left = $nextlink;
841                 $right = $prevlink;
842             } else {
843                 $left = $prevlink;
844                 $right = $nextlink;
845             }
847             $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
848             $content .= $left . '<span class="hide"> | </span><h1 class="current">'.userdate($time, get_string('strftimemonthyear'))."</h1>";
849             $content .= '<span class="hide"> | </span>' . $right;
850             $content .= '<span class="clearer"><!-- --></span>';
851             $content .= html_writer::end_tag('div')."\n";
852             break;
853         case 'day':
854             $days = calendar_get_days();
855             $data['d'] = $date['mday']; // Just for convenience
856             $prevdate = usergetdate(make_timestamp($data['y'], $data['m'], $data['d'] - 1));
857             $nextdate = usergetdate(make_timestamp($data['y'], $data['m'], $data['d'] + 1));
858             $prevname = calendar_wday_name($days[$prevdate['wday']]);
859             $nextname = calendar_wday_name($days[$nextdate['wday']]);
860             $prevlink = calendar_get_link_previous($prevname, 'view.php?view=day'.$courseid.'&amp;', $prevdate['mday'], $prevdate['mon'], $prevdate['year']);
861             $nextlink = calendar_get_link_next($nextname, 'view.php?view=day'.$courseid.'&amp;', $nextdate['mday'], $nextdate['mon'], $nextdate['year']);
863             if (right_to_left()) {
864                 $left = $nextlink;
865                 $right = $prevlink;
866             } else {
867                 $left = $prevlink;
868                 $right = $nextlink;
869             }
871             $content .= html_writer::start_tag('div', array('class'=>'calendar-controls'));
872             $content .= $left;
873             $content .= '<span class="hide"> | </span><span class="current">'.userdate($time, get_string('strftimedaydate')).'</span>';
874             $content .= '<span class="hide"> | </span>'. $right;
875             $content .= "<span class=\"clearer\"><!-- --></span>";
876             $content .= html_writer::end_tag('div')."\n";
878             break;
879     }
880     return $content;
883 /**
884  * Get the controls filter for calendar.
885  *
886  * Filter is used to hide calendar info from the display page
887  *
888  * @param moodle_url $returnurl return-url for filter controls
889  * @return string $content return filter controls in html
890  */
891 function calendar_filter_controls(moodle_url $returnurl) {
892     global $CFG, $USER, $OUTPUT;
894     $groupevents = true;
896     $id = optional_param( 'id',0,PARAM_INT );
898     $seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out(false)), 'sesskey'=>sesskey()));
900     $content = '<table>';
901     $content .= '<tr>';
903     $seturl->param('var', 'showglobal');
904     if (calendar_show_event_type(CALENDAR_EVENT_GLOBAL)) {
905         $content .= '<td class="eventskey calendar_event_global" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hideglobal', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
906         $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hideglobal', 'calendar').'">'.get_string('global', 'calendar').'</a></td>'."\n";
907     } else {
908         $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('show').'" title="'.get_string('tt_showglobal', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
909         $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showglobal', 'calendar').'">'.get_string('global', 'calendar').'</a></td>'."\n";
910     }
912     $seturl->param('var', 'showcourses');
913     if (calendar_show_event_type(CALENDAR_EVENT_COURSE)) {
914         $content .= '<td class="eventskey calendar_event_course" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hidecourse', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
915         $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hidecourse', 'calendar').'">'.get_string('course', 'calendar').'</a></td>'."\n";
916     } else {
917         $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_showcourse', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
918         $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showcourse', 'calendar').'">'.get_string('course', 'calendar').'</a></td>'."\n";
919     }
921     if (isloggedin() && !isguestuser()) {
922         $content .= "</tr>\n<tr>";
924         if ($groupevents) {
925             // This course MIGHT have group events defined, so show the filter
926             $seturl->param('var', 'showgroups');
927             if (calendar_show_event_type(CALENDAR_EVENT_GROUP)) {
928                 $content .= '<td class="eventskey calendar_event_group" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hidegroups', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
929                 $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hidegroups', 'calendar').'">'.get_string('group', 'calendar').'</a></td>'."\n";
930             } else {
931                 $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('show').'" title="'.get_string('tt_showgroups', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
932                 $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showgroups', 'calendar').'">'.get_string('group', 'calendar').'</a></td>'."\n";
933             }
934         } else {
935             // This course CANNOT have group events, so lose the filter
936             $content .= '<td style="width: 11px;"></td><td>&nbsp;</td>'."\n";
937         }
939         $seturl->param('var', 'showuser');
940         if (calendar_show_event_type(CALENDAR_EVENT_USER)) {
941             $content .= '<td class="eventskey calendar_event_user" style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.get_string('hide').'" title="'.get_string('tt_hideuser', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
942             $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_hideuser', 'calendar').'">'.get_string('user', 'calendar').'</a></td>'."\n";
943         } else {
944             $content .= '<td style="width: 11px;"><img src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.get_string('show').'" title="'.get_string('tt_showuser', 'calendar').'" style="cursor:pointer" onclick="location.href='."'".$seturl."'".'" /></td>';
945             $content .= '<td><a href="'.$seturl.'" title="'.get_string('tt_showuser', 'calendar').'">'.get_string('user', 'calendar').'</a></td>'."\n";
946         }
947     }
948     $content .= "</tr>\n</table>\n";
950     return $content;
953 /**
954  * Return the representation day
955  *
956  * @param int $tstamp Timestamp in GMT
957  * @param int $now current Unix timestamp
958  * @param bool $usecommonwords
959  * @return string the formatted date/time
960  */
961 function calendar_day_representation($tstamp, $now = false, $usecommonwords = true) {
963     static $shortformat;
964     if(empty($shortformat)) {
965         $shortformat = get_string('strftimedayshort');
966     }
968     if($now === false) {
969         $now = time();
970     }
972     // To have it in one place, if a change is needed
973     $formal = userdate($tstamp, $shortformat);
975     $datestamp = usergetdate($tstamp);
976     $datenow   = usergetdate($now);
978     if($usecommonwords == false) {
979         // We don't want words, just a date
980         return $formal;
981     }
982     else if($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday']) {
983         // Today
984         return get_string('today', 'calendar');
985     }
986     else if(
987         ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] - 1 ) ||
988         ($datestamp['year'] == $datenow['year'] - 1 && $datestamp['mday'] == 31 && $datestamp['mon'] == 12 && $datenow['yday'] == 1)
989         ) {
990         // Yesterday
991         return get_string('yesterday', 'calendar');
992     }
993     else if(
994         ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] + 1 ) ||
995         ($datestamp['year'] == $datenow['year'] + 1 && $datenow['mday'] == 31 && $datenow['mon'] == 12 && $datestamp['yday'] == 1)
996         ) {
997         // Tomorrow
998         return get_string('tomorrow', 'calendar');
999     }
1000     else {
1001         return $formal;
1002     }
1005 /**
1006  * return the formatted representation time
1007  *
1008  * @param int $time the timestamp in UTC, as obtained from the database
1009  * @return string the formatted date/time
1010  */
1011 function calendar_time_representation($time) {
1012     static $langtimeformat = NULL;
1013     if($langtimeformat === NULL) {
1014         $langtimeformat = get_string('strftimetime');
1015     }
1016     $timeformat = get_user_preferences('calendar_timeformat');
1017     if(empty($timeformat)){
1018         $timeformat = get_config(NULL,'calendar_site_timeformat');
1019     }
1020     // The ? is needed because the preference might be present, but empty
1021     return userdate($time, empty($timeformat) ? $langtimeformat : $timeformat);
1024 /**
1025  * Adds day, month, year arguments to a URL and returns a moodle_url object.
1026  *
1027  * @param string|moodle_url $linkbase
1028  * @param int $d The number of the day.
1029  * @param int $m The number of the month.
1030  * @param int $y The number of the year.
1031  * @return moodle_url|null $linkbase
1032  */
1033 function calendar_get_link_href($linkbase, $d, $m, $y) {
1034     if (empty($linkbase)) {
1035         return '';
1036     }
1037     if (!($linkbase instanceof moodle_url)) {
1038         $linkbase = new moodle_url();
1039     }
1040     if (!empty($d)) {
1041         $linkbase->param('cal_d', $d);
1042     }
1043     if (!empty($m)) {
1044         $linkbase->param('cal_m', $m);
1045     }
1046     if (!empty($y)) {
1047         $linkbase->param('cal_y', $y);
1048     }
1049     return $linkbase;
1052 /**
1053  * Build and return a previous month HTML link, with an arrow.
1054  *
1055  * @param string $text The text label.
1056  * @param string|moodle_url $linkbase The URL stub.
1057  * @param int $d The number of the date.
1058  * @param int $m The number of the month.
1059  * @param int $y year The number of the year.
1060  * @param bool $accesshide Default visible, or hide from all except screenreaders.
1061  * @return string HTML string.
1062  */
1063 function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide=false) {
1064     $href = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y);
1065     if (empty($href)) {
1066         return $text;
1067     }
1068     return link_arrow_left($text, (string)$href, $accesshide, 'previous');
1071 /**
1072  * Build and return a next month HTML link, with an arrow.
1073  *
1074  * @param string $text The text label.
1075  * @param string|moodle_url $linkbase The URL stub.
1076  * @param int $d the number of the Day
1077  * @param int $m The number of the month.
1078  * @param int $y The number of the year.
1079  * @param bool $accesshide Default visible, or hide from all except screenreaders.
1080  * @return string HTML string.
1081  */
1082 function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide=false) {
1083     $href = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y);
1084     if (empty($href)) {
1085         return $text;
1086     }
1087     return link_arrow_right($text, (string)$href, $accesshide, 'next');
1090 /**
1091  * Return the name of the weekday
1092  *
1093  * @param string $englishname
1094  * @return string of the weekeday
1095  */
1096 function calendar_wday_name($englishname) {
1097     return get_string(strtolower($englishname), 'calendar');
1100 /**
1101  * Return the number of days in month
1102  *
1103  * @param int $month the number of the month.
1104  * @param int $year the number of the year
1105  * @return int
1106  */
1107 function calendar_days_in_month($month, $year) {
1108    return intval(date('t', mktime(0, 0, 0, $month, 1, $year)));
1111 /**
1112  * Get the upcoming event block
1113  *
1114  * @param array $events list of events
1115  * @param moodle_url|string $linkhref link to event referer
1116  * @return string|null $content html block content
1117  */
1118 function calendar_get_block_upcoming($events, $linkhref = NULL) {
1119     $content = '';
1120     $lines = count($events);
1121     if (!$lines) {
1122         return $content;
1123     }
1125     for ($i = 0; $i < $lines; ++$i) {
1126         if (!isset($events[$i]->time)) {   // Just for robustness
1127             continue;
1128         }
1129         $events[$i] = calendar_add_event_metadata($events[$i]);
1130         $content .= '<div class="event"><span class="icon c0">'.$events[$i]->icon.'</span> ';
1131         if (!empty($events[$i]->referer)) {
1132             // That's an activity event, so let's provide the hyperlink
1133             $content .= $events[$i]->referer;
1134         } else {
1135             if(!empty($linkhref)) {
1136                 $ed = usergetdate($events[$i]->timestart);
1137                 $href = calendar_get_link_href(new moodle_url(CALENDAR_URL.$linkhref), $ed['mday'], $ed['mon'], $ed['year']);
1138                 $href->set_anchor('event_'.$events[$i]->id);
1139                 $content .= html_writer::link($href, $events[$i]->name);
1140             }
1141             else {
1142                 $content .= $events[$i]->name;
1143             }
1144         }
1145         $events[$i]->time = str_replace('&raquo;', '<br />&raquo;', $events[$i]->time);
1146         $content .= '<div class="date">'.$events[$i]->time.'</div></div>';
1147         if ($i < $lines - 1) $content .= '<hr />';
1148     }
1150     return $content;
1153 /**
1154  * Get the next following month
1155  *
1156  * If the current month is December, it will get the first month of the following year.
1157  *
1158  *
1159  * @param int $month the number of the month.
1160  * @param int $year the number of the year.
1161  * @return array the following month
1162  */
1163 function calendar_add_month($month, $year) {
1164     if($month == 12) {
1165         return array(1, $year + 1);
1166     }
1167     else {
1168         return array($month + 1, $year);
1169     }
1172 /**
1173  * Get the previous month
1174  *
1175  * If the current month is January, it will get the last month of the previous year.
1176  *
1177  * @param int $month the number of the month.
1178  * @param int $year the number of the year.
1179  * @return array previous month
1180  */
1181 function calendar_sub_month($month, $year) {
1182     if($month == 1) {
1183         return array(12, $year - 1);
1184     }
1185     else {
1186         return array($month - 1, $year);
1187     }
1190 /**
1191  * Get per-day basis events
1192  *
1193  * @param array $events list of events
1194  * @param int $month the number of the month
1195  * @param int $year the number of the year
1196  * @param array $eventsbyday event on specific day
1197  * @param array $durationbyday duration of the event in days
1198  * @param array $typesbyday event type (eg: global, course, user, or group)
1199  * @param array $courses list of courses
1200  * @return void
1201  */
1202 function calendar_events_by_day($events, $month, $year, &$eventsbyday, &$durationbyday, &$typesbyday, &$courses) {
1203     $eventsbyday = array();
1204     $typesbyday = array();
1205     $durationbyday = array();
1207     if($events === false) {
1208         return;
1209     }
1211     foreach($events as $event) {
1213         $startdate = usergetdate($event->timestart);
1214         // Set end date = start date if no duration
1215         if ($event->timeduration) {
1216             $enddate   = usergetdate($event->timestart + $event->timeduration - 1);
1217         } else {
1218             $enddate = $startdate;
1219         }
1221         // Simple arithmetic: $year * 13 + $month is a distinct integer for each distinct ($year, $month) pair
1222         if(!($startdate['year'] * 13 + $startdate['mon'] <= $year * 13 + $month) && ($enddate['year'] * 13 + $enddate['mon'] >= $year * 13 + $month)) {
1223             // Out of bounds
1224             continue;
1225         }
1227         $eventdaystart = intval($startdate['mday']);
1229         if($startdate['mon'] == $month && $startdate['year'] == $year) {
1230             // Give the event to its day
1231             $eventsbyday[$eventdaystart][] = $event->id;
1233             // Mark the day as having such an event
1234             if($event->courseid == SITEID && $event->groupid == 0) {
1235                 $typesbyday[$eventdaystart]['startglobal'] = true;
1236                 // Set event class for global event
1237                 $events[$event->id]->class = 'calendar_event_global';
1238             }
1239             else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {
1240                 $typesbyday[$eventdaystart]['startcourse'] = true;
1241                 // Set event class for course event
1242                 $events[$event->id]->class = 'calendar_event_course';
1243             }
1244             else if($event->groupid) {
1245                 $typesbyday[$eventdaystart]['startgroup'] = true;
1246                 // Set event class for group event
1247                 $events[$event->id]->class = 'calendar_event_group';
1248             }
1249             else if($event->userid) {
1250                 $typesbyday[$eventdaystart]['startuser'] = true;
1251                 // Set event class for user event
1252                 $events[$event->id]->class = 'calendar_event_user';
1253             }
1254         }
1256         if($event->timeduration == 0) {
1257             // Proceed with the next
1258             continue;
1259         }
1261         // The event starts on $month $year or before. So...
1262         $lowerbound = $startdate['mon'] == $month && $startdate['year'] == $year ? intval($startdate['mday']) : 0;
1264         // Also, it ends on $month $year or later...
1265         $upperbound = $enddate['mon'] == $month && $enddate['year'] == $year ? intval($enddate['mday']) : calendar_days_in_month($month, $year);
1267         // Mark all days between $lowerbound and $upperbound (inclusive) as duration
1268         for($i = $lowerbound + 1; $i <= $upperbound; ++$i) {
1269             $durationbyday[$i][] = $event->id;
1270             if($event->courseid == SITEID && $event->groupid == 0) {
1271                 $typesbyday[$i]['durationglobal'] = true;
1272             }
1273             else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) {
1274                 $typesbyday[$i]['durationcourse'] = true;
1275             }
1276             else if($event->groupid) {
1277                 $typesbyday[$i]['durationgroup'] = true;
1278             }
1279             else if($event->userid) {
1280                 $typesbyday[$i]['durationuser'] = true;
1281             }
1282         }
1284     }
1285     return;
1288 /**
1289  * Get current module cache
1290  *
1291  * @param array $coursecache list of course cache
1292  * @param string $modulename name of the module
1293  * @param int $instance module instance number
1294  * @return stdClass|bool $module information
1295  */
1296 function calendar_get_module_cached(&$coursecache, $modulename, $instance) {
1297     $module = get_coursemodule_from_instance($modulename, $instance);
1299     if($module === false) return false;
1300     if(!calendar_get_course_cached($coursecache, $module->course)) {
1301         return false;
1302     }
1303     return $module;
1306 /**
1307  * Get current course cache
1308  *
1309  * @param array $coursecache list of course cache
1310  * @param int $courseid id of the course
1311  * @return stdClass $coursecache[$courseid] return the specific course cache
1312  */
1313 function calendar_get_course_cached(&$coursecache, $courseid) {
1314     global $COURSE, $DB;
1316     if (!isset($coursecache[$courseid])) {
1317         if ($courseid == $COURSE->id) {
1318             $coursecache[$courseid] = $COURSE;
1319         } else {
1320             $coursecache[$courseid] = $DB->get_record('course', array('id'=>$courseid));
1321         }
1322     }
1323     return $coursecache[$courseid];
1326 /**
1327  * Returns the courses to load events for, the
1328  *
1329  * @param array $courseeventsfrom An array of courses to load calendar events for
1330  * @param bool $ignorefilters specify the use of filters, false is set as default
1331  * @return array An array of courses, groups, and user to load calendar events for based upon filters
1332  */
1333 function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
1334     global $USER, $CFG, $DB;
1336     // For backwards compatability we have to check whether the courses array contains
1337     // just id's in which case we need to load course objects.
1338     $coursestoload = array();
1339     foreach ($courseeventsfrom as $id => $something) {
1340         if (!is_object($something)) {
1341             $coursestoload[] = $id;
1342             unset($courseeventsfrom[$id]);
1343         }
1344     }
1345     if (!empty($coursestoload)) {
1346         // TODO remove this in 2.2
1347         debugging('calendar_set_filters now preferes an array of course objects with preloaded contexts', DEBUG_DEVELOPER);
1348         $courseeventsfrom = array_merge($courseeventsfrom, $DB->get_records_list('course', 'id', $coursestoload));
1349     }
1351     $courses = array();
1352     $user = false;
1353     $group = false;
1355     $isloggedin = isloggedin();
1357     if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_COURSE)) {
1358         $courses = array_keys($courseeventsfrom);
1359     }
1360     if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_GLOBAL)) {
1361         $courses[] = SITEID;
1362     }
1363     $courses = array_unique($courses);
1364     sort($courses);
1366     if (!empty($courses) && in_array(SITEID, $courses)) {
1367         // Sort courses for consistent colour highlighting
1368         // Effectively ignoring SITEID as setting as last course id
1369         $key = array_search(SITEID, $courses);
1370         unset($courses[$key]);
1371         $courses[] = SITEID;
1372     }
1374     if ($ignorefilters || ($isloggedin && calendar_show_event_type(CALENDAR_EVENT_USER))) {
1375         $user = $USER->id;
1376     }
1378     if (!empty($courseeventsfrom) && (calendar_show_event_type(CALENDAR_EVENT_GROUP) || $ignorefilters)) {
1380         if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', get_system_context())) {
1381             $group = true;
1382         } else if ($isloggedin) {
1383             $groupids = array();
1385             // We already have the courses to examine in $courses
1386             // For each course...
1387             foreach ($courseeventsfrom as $courseid => $course) {
1388                 // If the user is an editing teacher in there,
1389                 if (!empty($USER->groupmember[$course->id])) {
1390                     // We've already cached the users groups for this course so we can just use that
1391                     $groupids = array_merge($groupids, $USER->groupmember[$course->id]);
1392                 } else if (($course->groupmode != NOGROUPS || !$course->groupmodeforce) && has_capability('moodle/calendar:manageentries', get_context_instance(CONTEXT_COURSE, $course->id))) {
1393                     // If this course has groups, show events from all of them
1394                     $coursegroups = groups_get_user_groups($course->id, $USER->id);
1395                     $groupids = array_merge($groupids, $coursegroups['0']);
1396                 }
1397             }
1398             if (!empty($groupids)) {
1399                 $group = $groupids;
1400             }
1401         }
1402     }
1403     if (empty($courses)) {
1404         $courses = false;
1405     }
1407     return array($courses, $group, $user);
1410 /**
1411  * Return the capability for editing calendar event
1412  *
1413  * @param calendar_event $event event object
1414  * @return bool capability to edit event
1415  */
1416 function calendar_edit_event_allowed($event) {
1417     global $USER, $DB;
1419     // Must be logged in
1420     if (!isloggedin()) {
1421         return false;
1422     }
1424     // can not be using guest account
1425     if (isguestuser()) {
1426         return false;
1427     }
1429     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
1430     // if user has manageentries at site level, return true
1431     if (has_capability('moodle/calendar:manageentries', $sitecontext)) {
1432         return true;
1433     }
1435     // if groupid is set, it's definitely a group event
1436     if (!empty($event->groupid)) {
1437         // Allow users to add/edit group events if:
1438         // 1) They have manageentries (= entries for whole course)
1439         // 2) They have managegroupentries AND are in the group
1440         $group = $DB->get_record('groups', array('id'=>$event->groupid));
1441         return $group && (
1442             has_capability('moodle/calendar:manageentries', $event->context) ||
1443             (has_capability('moodle/calendar:managegroupentries', $event->context)
1444                 && groups_is_member($event->groupid)));
1445     } else if (!empty($event->courseid)) {
1446     // if groupid is not set, but course is set,
1447     // it's definiely a course event
1448         return has_capability('moodle/calendar:manageentries', $event->context);
1449     } else if (!empty($event->userid) && $event->userid == $USER->id) {
1450     // if course is not set, but userid id set, it's a user event
1451         return (has_capability('moodle/calendar:manageownentries', $event->context));
1452     } else if (!empty($event->userid)) {
1453         return (has_capability('moodle/calendar:manageentries', $event->context));
1454     }
1455     return false;
1458 /**
1459  * Returns the default courses to display on the calendar when there isn't a specific
1460  * course to display.
1461  *
1462  * @return array $courses Array of courses to display
1463  */
1464 function calendar_get_default_courses() {
1465     global $CFG, $DB;
1467     if (!isloggedin()) {
1468         return array();
1469     }
1471     $courses = array();
1472     if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', context_system::instance())) {
1473         list ($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
1474         $sql = "SELECT c.* $select
1475                   FROM {course} c
1476                   $join
1477                   WHERE EXISTS (SELECT 1 FROM {event} e WHERE e.courseid = c.id)
1478                   ";
1479         $courses = $DB->get_records_sql($sql, null, 0, 20);
1480         foreach ($courses as $course) {
1481             context_helper::preload_from_record($course);
1482         }
1483         return $courses;
1484     }
1486     $courses = enrol_get_my_courses();
1488     return $courses;
1491 /**
1492  * Display calendar preference button
1493  *
1494  * @param stdClass $course course object
1495  * @return string return preference button in html
1496  */
1497 function calendar_preferences_button(stdClass $course) {
1498     global $OUTPUT;
1500     // Guests have no preferences
1501     if (!isloggedin() || isguestuser()) {
1502         return '';
1503     }
1505     return $OUTPUT->single_button(new moodle_url('/calendar/preferences.php', array('course' => $course->id)), get_string("preferences", "calendar"));
1508 /**
1509  * Get event format time
1510  *
1511  * @param calendar_event $event event object
1512  * @param int $now current time in gmt
1513  * @param array $linkparams list of params for event link
1514  * @param bool $usecommonwords the words as formatted date/time.
1515  * @param int $showtime determine the show time GMT timestamp
1516  * @return string $eventtime link/string for event time
1517  */
1518 function calendar_format_event_time($event, $now, $linkparams = null, $usecommonwords = true, $showtime=0) {
1519     $startdate = usergetdate($event->timestart);
1520     $enddate = usergetdate($event->timestart + $event->timeduration);
1521     $usermidnightstart = usergetmidnight($event->timestart);
1523     if($event->timeduration) {
1524         // To avoid doing the math if one IF is enough :)
1525         $usermidnightend = usergetmidnight($event->timestart + $event->timeduration);
1526     }
1527     else {
1528         $usermidnightend = $usermidnightstart;
1529     }
1531     if (empty($linkparams) || !is_array($linkparams)) {
1532         $linkparams = array();
1533     }
1534     $linkparams['view'] = 'day';
1536     // OK, now to get a meaningful display...
1537     // First of all we have to construct a human-readable date/time representation
1539     if($event->timeduration) {
1540         // It has a duration
1541         if($usermidnightstart == $usermidnightend ||
1542            ($event->timestart == $usermidnightstart) && ($event->timeduration == 86400 || $event->timeduration == 86399) ||
1543            ($event->timestart + $event->timeduration <= $usermidnightstart + 86400)) {
1544             // But it's all on the same day
1545             $timestart = calendar_time_representation($event->timestart);
1546             $timeend = calendar_time_representation($event->timestart + $event->timeduration);
1547             $time = $timestart.' <strong>&raquo;</strong> '.$timeend;
1549             if ($event->timestart == $usermidnightstart && ($event->timeduration == 86400 || $event->timeduration == 86399)) {
1550                 $time = get_string('allday', 'calendar');
1551             }
1553             // Set printable representation
1554             if (!$showtime) {
1555                 $day = calendar_day_representation($event->timestart, $now, $usecommonwords);
1556                 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $enddate['mday'], $enddate['mon'], $enddate['year']);
1557                 $eventtime = html_writer::link($url, $day).', '.$time;
1558             } else {
1559                 $eventtime = $time;
1560             }
1561         } else {
1562             // It spans two or more days
1563             $daystart = calendar_day_representation($event->timestart, $now, $usecommonwords).', ';
1564             if ($showtime == $usermidnightstart) {
1565                 $daystart = '';
1566             }
1567             $timestart = calendar_time_representation($event->timestart);
1568             $dayend = calendar_day_representation($event->timestart + $event->timeduration, $now, $usecommonwords).', ';
1569             if ($showtime == $usermidnightend) {
1570                 $dayend = '';
1571             }
1572             $timeend = calendar_time_representation($event->timestart + $event->timeduration);
1574             // Set printable representation
1575             if ($now >= $usermidnightstart && $now < ($usermidnightstart + 86400)) {
1576                 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $enddate['mday'], $enddate['mon'], $enddate['year']);
1577                 $eventtime = $timestart.' <strong>&raquo;</strong> '.html_writer::link($url, $dayend).$timeend;
1578             } else {
1579                 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $enddate['mday'], $enddate['mon'], $enddate['year']);
1580                 $eventtime  = html_writer::link($url, $daystart).$timestart.' <strong>&raquo;</strong> ';
1582                 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $startdate['mday'], $startdate['mon'], $startdate['year']);
1583                 $eventtime .= html_writer::link($url, $dayend).$timeend;
1584             }
1585         }
1586     } else {
1587         $time = calendar_time_representation($event->timestart);
1589         // Set printable representation
1590         if (!$showtime) {
1591             $day = calendar_day_representation($event->timestart, $now, $usecommonwords);
1592             $url = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', $linkparams), $startdate['mday'], $startdate['mon'], $startdate['year']);
1593             $eventtime = html_writer::link($url, $day).', '.trim($time);
1594         } else {
1595             $eventtime = $time;
1596         }
1597     }
1599     if($event->timestart + $event->timeduration < $now) {
1600         // It has expired
1601         $eventtime = '<span class="dimmed_text">'.str_replace(' href=', ' class="dimmed" href=', $eventtime).'</span>';
1602     }
1604     return $eventtime;
1607 /**
1608  * Display month selector options
1609  *
1610  * @param string $name for the select element
1611  * @param string|array $selected options for select elements
1612  */
1613 function calendar_print_month_selector($name, $selected) {
1614     $months = array();
1615     for ($i=1; $i<=12; $i++) {
1616         $months[$i] = userdate(gmmktime(12, 0, 0, $i, 15, 2000), '%B');
1617     }
1618     echo html_writer::select($months, $name, $selected, false);
1621 /**
1622  * Checks to see if the requested type of event should be shown for the given user.
1623  *
1624  * @param CALENDAR_EVENT_GLOBAL|CALENDAR_EVENT_COURSE|CALENDAR_EVENT_GROUP|CALENDAR_EVENT_USER $type
1625  *          The type to check the display for (default is to display all)
1626  * @param stdClass|int|null $user The user to check for - by default the current user
1627  * @return bool True if the tyep should be displayed false otherwise
1628  */
1629 function calendar_show_event_type($type, $user = null) {
1630     $default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
1631     if (get_user_preferences('calendar_persistflt', 0, $user) === 0) {
1632         global $SESSION;
1633         if (!isset($SESSION->calendarshoweventtype)) {
1634             $SESSION->calendarshoweventtype = $default;
1635         }
1636         return $SESSION->calendarshoweventtype & $type;
1637     } else {
1638         return get_user_preferences('calendar_savedflt', $default, $user) & $type;
1639     }
1642 /**
1643  * Sets the display of the event type given $display.
1644  *
1645  * If $display = true the event type will be shown.
1646  * If $display = false the event type will NOT be shown.
1647  * If $display = null the current value will be toggled and saved.
1648  *
1649  * @param CALENDAR_EVENT_GLOBAL|CALENDAR_EVENT_COURSE|CALENDAR_EVENT_GROUP|CALENDAR_EVENT_USER $type object of CALENDAR_EVENT_XXX
1650  * @param bool $display option to display event type
1651  * @param stdClass|int $user moodle user object or id, null means current user
1652  */
1653 function calendar_set_event_type_display($type, $display = null, $user = null) {
1654     $persist = get_user_preferences('calendar_persistflt', 0, $user);
1655     $default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
1656     if ($persist === 0) {
1657         global $SESSION;
1658         if (!isset($SESSION->calendarshoweventtype)) {
1659             $SESSION->calendarshoweventtype = $default;
1660         }
1661         $preference = $SESSION->calendarshoweventtype;
1662     } else {
1663         $preference = get_user_preferences('calendar_savedflt', $default, $user);
1664     }
1665     $current = $preference & $type;
1666     if ($display === null) {
1667         $display = !$current;
1668     }
1669     if ($display && !$current) {
1670         $preference += $type;
1671     } else if (!$display && $current) {
1672         $preference -= $type;
1673     }
1674     if ($persist === 0) {
1675         $SESSION->calendarshoweventtype = $preference;
1676     } else {
1677         if ($preference == $default) {
1678             unset_user_preference('calendar_savedflt', $user);
1679         } else {
1680             set_user_preference('calendar_savedflt', $preference, $user);
1681         }
1682     }
1685 /**
1686  * Get calendar's allowed types
1687  *
1688  * @param stdClass $allowed list of allowed edit for event  type
1689  * @param stdClass|int $course object of a course or course id
1690  */
1691 function calendar_get_allowed_types(&$allowed, $course = null) {
1692     global $USER, $CFG, $DB;
1693     $allowed = new stdClass();
1694     $allowed->user = has_capability('moodle/calendar:manageownentries', get_system_context());
1695     $allowed->groups = false; // This may change just below
1696     $allowed->courses = false; // This may change just below
1697     $allowed->site = has_capability('moodle/calendar:manageentries', get_context_instance(CONTEXT_COURSE, SITEID));
1699     if (!empty($course)) {
1700         if (!is_object($course)) {
1701             $course = $DB->get_record('course', array('id' => $course), '*', MUST_EXIST);
1702         }
1703         if ($course->id != SITEID) {
1704             $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1705             $allowed->user = has_capability('moodle/calendar:manageownentries', $coursecontext);
1707             if (has_capability('moodle/calendar:manageentries', $coursecontext)) {
1708                 $allowed->courses = array($course->id => 1);
1710                 if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
1711                     $allowed->groups = groups_get_all_groups($course->id);
1712                 }
1713             } else if (has_capability('moodle/calendar:managegroupentries', $coursecontext)) {
1714                 if($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
1715                     $allowed->groups = groups_get_all_groups($course->id);
1716                 }
1717             }
1718         }
1719     }
1722 /**
1723  * See if user can add calendar entries at all
1724  * used to print the "New Event" button
1725  *
1726  * @param stdClass $course object of a course or course id
1727  * @return bool has the capability to add at least one event type
1728  */
1729 function calendar_user_can_add_event($course) {
1730     if (!isloggedin() || isguestuser()) {
1731         return false;
1732     }
1733     calendar_get_allowed_types($allowed, $course);
1734     return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->site);
1737 /**
1738  * Check wether the current user is permitted to add events
1739  *
1740  * @param stdClass $event object of event
1741  * @return bool has the capability to add event
1742  */
1743 function calendar_add_event_allowed($event) {
1744     global $USER, $DB;
1746     // can not be using guest account
1747     if (!isloggedin() or isguestuser()) {
1748         return false;
1749     }
1751     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
1752     // if user has manageentries at site level, always return true
1753     if (has_capability('moodle/calendar:manageentries', $sitecontext)) {
1754         return true;
1755     }
1757     switch ($event->eventtype) {
1758         case 'course':
1759             return has_capability('moodle/calendar:manageentries', $event->context);
1761         case 'group':
1762             // Allow users to add/edit group events if:
1763             // 1) They have manageentries (= entries for whole course)
1764             // 2) They have managegroupentries AND are in the group
1765             $group = $DB->get_record('groups', array('id'=>$event->groupid));
1766             return $group && (
1767                 has_capability('moodle/calendar:manageentries', $event->context) ||
1768                 (has_capability('moodle/calendar:managegroupentries', $event->context)
1769                     && groups_is_member($event->groupid)));
1771         case 'user':
1772             if ($event->userid == $USER->id) {
1773                 return (has_capability('moodle/calendar:manageownentries', $event->context));
1774             }
1775             //there is no 'break;' intentionally
1777         case 'site':
1778             return has_capability('moodle/calendar:manageentries', $event->context);
1780         default:
1781             return has_capability('moodle/calendar:manageentries', $event->context);
1782     }
1785 /**
1786  * Manage calendar events
1787  *
1788  * This class provides the required functionality in order to manage calendar events.
1789  * It was introduced as part of Moodle 2.0 and was created in order to provide a
1790  * better framework for dealing with calendar events in particular regard to file
1791  * handling through the new file API
1792  *
1793  * @package    core_calendar
1794  * @category   calendar
1795  * @copyright  2009 Sam Hemelryk
1796  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1797  *
1798  * @property int $id The id within the event table
1799  * @property string $name The name of the event
1800  * @property string $description The description of the event
1801  * @property int $format The format of the description FORMAT_?
1802  * @property int $courseid The course the event is associated with (0 if none)
1803  * @property int $groupid The group the event is associated with (0 if none)
1804  * @property int $userid The user the event is associated with (0 if none)
1805  * @property int $repeatid If this is a repeated event this will be set to the
1806  *                          id of the original
1807  * @property string $modulename If added by a module this will be the module name
1808  * @property int $instance If added by a module this will be the module instance
1809  * @property string $eventtype The event type
1810  * @property int $timestart The start time as a timestamp
1811  * @property int $timeduration The duration of the event in seconds
1812  * @property int $visible 1 if the event is visible
1813  * @property int $uuid ?
1814  * @property int $sequence ?
1815  * @property int $timemodified The time last modified as a timestamp
1816  */
1817 class calendar_event {
1819     /** @var array An object containing the event properties can be accessed via the magic __get/set methods */
1820     protected $properties = null;
1822     /**
1823      * @var string The converted event discription with file paths resolved. This gets populated when someone requests description for the first time */
1824     protected $_description = null;
1826     /** @var array The options to use with this description editor */
1827     protected $editoroptions = array(
1828             'subdirs'=>false,
1829             'forcehttps'=>false,
1830             'maxfiles'=>-1,
1831             'maxbytes'=>null,
1832             'trusttext'=>false);
1834     /** @var object The context to use with the description editor */
1835     protected $editorcontext = null;
1837     /**
1838      * Instantiates a new event and optionally populates its properties with the
1839      * data provided
1840      *
1841      * @param stdClass $data Optional. An object containing the properties to for
1842      *                  an event
1843      */
1844     public function __construct($data=null) {
1845         global $CFG, $USER;
1847         // First convert to object if it is not already (should either be object or assoc array)
1848         if (!is_object($data)) {
1849             $data = (object)$data;
1850         }
1852         $this->editoroptions['maxbytes'] = $CFG->maxbytes;
1854         $data->eventrepeats = 0;
1856         if (empty($data->id)) {
1857             $data->id = null;
1858         }
1860         // Default to a user event
1861         if (empty($data->eventtype)) {
1862             $data->eventtype = 'user';
1863         }
1865         // Default to the current user
1866         if (empty($data->userid)) {
1867             $data->userid = $USER->id;
1868         }
1870         if (!empty($data->timeduration) && is_array($data->timeduration)) {
1871             $data->timeduration = make_timestamp($data->timeduration['year'], $data->timeduration['month'], $data->timeduration['day'], $data->timeduration['hour'], $data->timeduration['minute']) - $data->timestart;
1872         }
1873         if (!empty($data->description) && is_array($data->description)) {
1874             $data->format = $data->description['format'];
1875             $data->description = $data->description['text'];
1876         } else if (empty($data->description)) {
1877             $data->description = '';
1878             $data->format = editors_get_preferred_format();
1879         }
1880         // Ensure form is defaulted correctly
1881         if (empty($data->format)) {
1882             $data->format = editors_get_preferred_format();
1883         }
1885         if (empty($data->context)) {
1886             $data->context = $this->calculate_context($data);
1887         }
1888         $this->properties = $data;
1889     }
1891     /**
1892      * Magic property method
1893      *
1894      * Attempts to call a set_$key method if one exists otherwise falls back
1895      * to simply set the property
1896      *
1897      * @param string $key property name
1898      * @param mixed $value value of the property
1899      */
1900     public function __set($key, $value) {
1901         if (method_exists($this, 'set_'.$key)) {
1902             $this->{'set_'.$key}($value);
1903         }
1904         $this->properties->{$key} = $value;
1905     }
1907     /**
1908      * Magic get method
1909      *
1910      * Attempts to call a get_$key method to return the property and ralls over
1911      * to return the raw property
1912      *
1913      * @param string $key property name
1914      * @return mixed property value
1915      */
1916     public function __get($key) {
1917         if (method_exists($this, 'get_'.$key)) {
1918             return $this->{'get_'.$key}();
1919         }
1920         if (!isset($this->properties->{$key})) {
1921             throw new coding_exception('Undefined property requested');
1922         }
1923         return $this->properties->{$key};
1924     }
1926     /**
1927      * Stupid PHP needs an isset magic method if you use the get magic method and
1928      * still want empty calls to work.... blah ~!
1929      *
1930      * @param string $key $key property name
1931      * @return bool|mixed property value, false if property is not exist
1932      */
1933     public function __isset($key) {
1934         return !empty($this->properties->{$key});
1935     }
1937     /**
1938      * Calculate the context value needed for calendar_event.
1939      * Event's type can be determine by the available value store in $data
1940      * It is important to check for the existence of course/courseid to determine
1941      * the course event.
1942      * Default value is set to CONTEXT_USER
1943      *
1944      * @param stdClass $data information about event
1945      * @return stdClass The context object.
1946      */
1947     protected function calculate_context(stdClass $data) {
1948         global $USER, $DB;
1950         $context = null;
1951         if (isset($data->courseid) && $data->courseid > 0) {
1952             $context =  get_context_instance(CONTEXT_COURSE, $data->courseid);
1953         } else if (isset($data->course) && $data->course > 0) {
1954             $context =  get_context_instance(CONTEXT_COURSE, $data->course);
1955         } else if (isset($data->groupid) && $data->groupid > 0) {
1956             $group = $DB->get_record('groups', array('id'=>$data->groupid));
1957             $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
1958         } else if (isset($data->userid) && $data->userid > 0 && $data->userid == $USER->id) {
1959             $context =  get_context_instance(CONTEXT_USER, $data->userid);
1960         } else if (isset($data->userid) && $data->userid > 0 && $data->userid != $USER->id &&
1961                    isset($data->instance) && $data->instance > 0) {
1962             $cm = get_coursemodule_from_instance($data->modulename, $data->instance, 0, false, MUST_EXIST);
1963             $context =  get_context_instance(CONTEXT_COURSE, $cm->course);
1964         } else {
1965             $context =  get_context_instance(CONTEXT_USER);
1966         }
1968         return $context;
1969     }
1971     /**
1972      * Returns an array of editoroptions for this event: Called by __get
1973      * Please use $blah = $event->editoroptions;
1974      *
1975      * @return array event editor options
1976      */
1977     protected function get_editoroptions() {
1978         return $this->editoroptions;
1979     }
1981     /**
1982      * Returns an event description: Called by __get
1983      * Please use $blah = $event->description;
1984      *
1985      * @return string event description
1986      */
1987     protected function get_description() {
1988         global $CFG;
1990         require_once($CFG->libdir . '/filelib.php');
1992         if ($this->_description === null) {
1993             // Check if we have already resolved the context for this event
1994             if ($this->editorcontext === null) {
1995                 // Switch on the event type to decide upon the appropriate context
1996                 // to use for this event
1997                 $this->editorcontext = $this->properties->context;
1998                 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
1999                         && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
2000                     return clean_text($this->properties->description, $this->properties->format);
2001                 }
2002             }
2004             // Work out the item id for the editor, if this is a repeated event then the files will
2005             // be associated with the original
2006             if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
2007                 $itemid = $this->properties->repeatid;
2008             } else {
2009                 $itemid = $this->properties->id;
2010             }
2012             // Convert file paths in the description so that things display correctly
2013             $this->_description = file_rewrite_pluginfile_urls($this->properties->description, 'pluginfile.php', $this->editorcontext->id, 'calendar', 'event_description', $itemid);
2014             // Clean the text so no nasties get through
2015             $this->_description = clean_text($this->_description, $this->properties->format);
2016         }
2017         // Finally return the description
2018         return $this->_description;
2019     }
2021     /**
2022      * Return the number of repeat events there are in this events series
2023      *
2024      * @return int number of event repeated
2025      */
2026     public function count_repeats() {
2027         global $DB;
2028         if (!empty($this->properties->repeatid)) {
2029             $this->properties->eventrepeats = $DB->count_records('event', array('repeatid'=>$this->properties->repeatid));
2030             // We don't want to count ourselves
2031             $this->properties->eventrepeats--;
2032         }
2033         return $this->properties->eventrepeats;
2034     }
2036     /**
2037      * Update or create an event within the database
2038      *
2039      * Pass in a object containing the event properties and this function will
2040      * insert it into the database and deal with any associated files
2041      *
2042      * @see add_event()
2043      * @see update_event()
2044      *
2045      * @param stdClass $data object of event
2046      * @param bool $checkcapability if moodle should check calendar managing capability or not
2047      * @return bool event updated
2048      */
2049     public function update($data, $checkcapability=true) {
2050         global $CFG, $DB, $USER;
2052         foreach ($data as $key=>$value) {
2053             $this->properties->$key = $value;
2054         }
2056         $this->properties->timemodified = time();
2057         $usingeditor = (!empty($this->properties->description) && is_array($this->properties->description));
2059         if (empty($this->properties->id) || $this->properties->id < 1) {
2061             if ($checkcapability) {
2062                 if (!calendar_add_event_allowed($this->properties)) {
2063                     print_error('nopermissiontoupdatecalendar');
2064                 }
2065             }
2067             if ($usingeditor) {
2068                 switch ($this->properties->eventtype) {
2069                     case 'user':
2070                         $this->editorcontext = $this->properties->context;
2071                         $this->properties->courseid = 0;
2072                         $this->properties->groupid = 0;
2073                         $this->properties->userid = $USER->id;
2074                         break;
2075                     case 'site':
2076                         $this->editorcontext = $this->properties->context;
2077                         $this->properties->courseid = SITEID;
2078                         $this->properties->groupid = 0;
2079                         $this->properties->userid = $USER->id;
2080                         break;
2081                     case 'course':
2082                         $this->editorcontext = $this->properties->context;
2083                         $this->properties->groupid = 0;
2084                         $this->properties->userid = $USER->id;
2085                         break;
2086                     case 'group':
2087                         $this->editorcontext = $this->properties->context;
2088                         $this->properties->userid = $USER->id;
2089                         break;
2090                     default:
2091                         // Ewww we should NEVER get here, but just incase we do lets
2092                         // fail gracefully
2093                         $usingeditor = false;
2094                         break;
2095                 }
2097                 $editor = $this->properties->description;
2098                 $this->properties->format = $this->properties->description['format'];
2099                 $this->properties->description = $this->properties->description['text'];
2100             }
2102             // Insert the event into the database
2103             $this->properties->id = $DB->insert_record('event', $this->properties);
2105             if ($usingeditor) {
2106                 $this->properties->description = file_save_draft_area_files(
2107                                                 $editor['itemid'],
2108                                                 $this->editorcontext->id,
2109                                                 'calendar',
2110                                                 'event_description',
2111                                                 $this->properties->id,
2112                                                 $this->editoroptions,
2113                                                 $editor['text'],
2114                                                 $this->editoroptions['forcehttps']);
2116                 $DB->set_field('event', 'description', $this->properties->description, array('id'=>$this->properties->id));
2117             }
2119             // Log the event entry.
2120             add_to_log($this->properties->courseid, 'calendar', 'add', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2122             $repeatedids = array();
2124             if (!empty($this->properties->repeat)) {
2125                 $this->properties->repeatid = $this->properties->id;
2126                 $DB->set_field('event', 'repeatid', $this->properties->repeatid, array('id'=>$this->properties->id));
2128                 $eventcopy = clone($this->properties);
2129                 unset($eventcopy->id);
2131                 for($i = 1; $i < $eventcopy->repeats; $i++) {
2133                     $eventcopy->timestart = ($eventcopy->timestart+WEEKSECS) + dst_offset_on($eventcopy->timestart) - dst_offset_on($eventcopy->timestart+WEEKSECS);
2135                     // Get the event id for the log record.
2136                     $eventcopyid = $DB->insert_record('event', $eventcopy);
2138                     // If the context has been set delete all associated files
2139                     if ($usingeditor) {
2140                         $fs = get_file_storage();
2141                         $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
2142                         foreach ($files as $file) {
2143                             $fs->create_file_from_storedfile(array('itemid'=>$eventcopyid), $file);
2144                         }
2145                     }
2147                     $repeatedids[] = $eventcopyid;
2148                     // Log the event entry.
2149                     add_to_log($eventcopy->courseid, 'calendar', 'add', 'event.php?action=edit&amp;id='.$eventcopyid, $eventcopy->name);
2150                 }
2151             }
2153             // Hook for tracking added events
2154             self::calendar_event_hook('add_event', array($this->properties, $repeatedids));
2155             return true;
2156         } else {
2158             if ($checkcapability) {
2159                 if(!calendar_edit_event_allowed($this->properties)) {
2160                     print_error('nopermissiontoupdatecalendar');
2161                 }
2162             }
2164             if ($usingeditor) {
2165                 if ($this->editorcontext !== null) {
2166                     $this->properties->description = file_save_draft_area_files(
2167                                                     $this->properties->description['itemid'],
2168                                                     $this->editorcontext->id,
2169                                                     'calendar',
2170                                                     'event_description',
2171                                                     $this->properties->id,
2172                                                     $this->editoroptions,
2173                                                     $this->properties->description['text'],
2174                                                     $this->editoroptions['forcehttps']);
2175                 } else {
2176                     $this->properties->format = $this->properties->description['format'];
2177                     $this->properties->description = $this->properties->description['text'];
2178                 }
2179             }
2181             $event = $DB->get_record('event', array('id'=>$this->properties->id));
2183             $updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
2185             if ($updaterepeated) {
2186                 // Update all
2187                 if ($this->properties->timestart != $event->timestart) {
2188                     $timestartoffset = $this->properties->timestart - $event->timestart;
2189                     $sql = "UPDATE {event}
2190                                SET name = ?,
2191                                    description = ?,
2192                                    timestart = timestart + ?,
2193                                    timeduration = ?,
2194                                    timemodified = ?
2195                              WHERE repeatid = ?";
2196                     $params = array($this->properties->name, $this->properties->description, $timestartoffset, $this->properties->timeduration, time(), $event->repeatid);
2197                 } else {
2198                     $sql = "UPDATE {event} SET name = ?, description = ?, timeduration = ?, timemodified = ? WHERE repeatid = ?";
2199                     $params = array($this->properties->name, $this->properties->description, $this->properties->timeduration, time(), $event->repeatid);
2200                 }
2201                 $DB->execute($sql, $params);
2203                 // Log the event update.
2204                 add_to_log($this->properties->courseid, 'calendar', 'edit all', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2205             } else {
2206                 $DB->update_record('event', $this->properties);
2207                 $event = calendar_event::load($this->properties->id);
2208                 $this->properties = $event->properties();
2209                 add_to_log($this->properties->courseid, 'calendar', 'edit', 'event.php?action=edit&amp;id='.$this->properties->id, $this->properties->name);
2210             }
2212             // Hook for tracking event updates
2213             self::calendar_event_hook('update_event', array($this->properties, $updaterepeated));
2214             return true;
2215         }
2216     }
2218     /**
2219      * Deletes an event and if selected an repeated events in the same series
2220      *
2221      * This function deletes an event, any associated events if $deleterepeated=true,
2222      * and cleans up any files associated with the events.
2223      *
2224      * @see delete_event()
2225      *
2226      * @param bool $deleterepeated  delete event repeatedly
2227      * @return bool succession of deleting event
2228      */
2229     public function delete($deleterepeated=false) {
2230         global $DB;
2232         // If $this->properties->id is not set then something is wrong
2233         if (empty($this->properties->id)) {
2234             debugging('Attempting to delete an event before it has been loaded', DEBUG_DEVELOPER);
2235             return false;
2236         }
2238         // Delete the event
2239         $DB->delete_records('event', array('id'=>$this->properties->id));
2241         // If we are deleting parent of a repeated event series, promote the next event in the series as parent
2242         if (($this->properties->id == $this->properties->repeatid) && !$deleterepeated) {
2243             $newparent = $DB->get_field_sql("SELECT id from {event} where repeatid = ? order by id ASC", array($this->properties->id), IGNORE_MULTIPLE);
2244             if (!empty($newparent)) {
2245                 $DB->execute("UPDATE {event} SET repeatid = ? WHERE repeatid = ?", array($newparent, $this->properties->id));
2246                 // Get all records where the repeatid is the same as the event being removed
2247                 $events = $DB->get_records('event', array('repeatid' => $newparent));
2248                 // For each of the returned events trigger the event_update hook.
2249                 foreach ($events as $event) {
2250                     self::calendar_event_hook('update_event', array($event, false));
2251                 }
2252             }
2253         }
2255         // If the editor context hasn't already been set then set it now
2256         if ($this->editorcontext === null) {
2257             $this->editorcontext = $this->properties->context;
2258         }
2260         // If the context has been set delete all associated files
2261         if ($this->editorcontext !== null) {
2262             $fs = get_file_storage();
2263             $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
2264             foreach ($files as $file) {
2265                 $file->delete();
2266             }
2267         }
2269         // Fire the event deleted hook
2270         self::calendar_event_hook('delete_event', array($this->properties->id, $deleterepeated));
2272         // If we need to delete repeated events then we will fetch them all and delete one by one
2273         if ($deleterepeated && !empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
2274             // Get all records where the repeatid is the same as the event being removed
2275             $events = $DB->get_records('event', array('repeatid'=>$this->properties->repeatid));
2276             // For each of the returned events populate a calendar_event object and call delete
2277             // make sure the arg passed is false as we are already deleting all repeats
2278             foreach ($events as $event) {
2279                 $event = new calendar_event($event);
2280                 $event->delete(false);
2281             }
2282         }
2284         return true;
2285     }
2287     /**
2288      * Fetch all event properties
2289      *
2290      * This function returns all of the events properties as an object and optionally
2291      * can prepare an editor for the description field at the same time. This is
2292      * designed to work when the properties are going to be used to set the default
2293      * values of a moodle forms form.
2294      *
2295      * @param bool $prepareeditor If set to true a editor is prepared for use with
2296      *              the mforms editor element. (for description)
2297      * @return stdClass Object containing event properties
2298      */
2299     public function properties($prepareeditor=false) {
2300         global $USER, $CFG, $DB;
2302         // First take a copy of the properties. We don't want to actually change the
2303         // properties or we'd forever be converting back and forwards between an
2304         // editor formatted description and not
2305         $properties = clone($this->properties);
2306         // Clean the description here
2307         $properties->description = clean_text($properties->description, $properties->format);
2309         // If set to true we need to prepare the properties for use with an editor
2310         // and prepare the file area
2311         if ($prepareeditor) {
2313             // We may or may not have a property id. If we do then we need to work
2314             // out the context so we can copy the existing files to the draft area
2315             if (!empty($properties->id)) {
2317                 if ($properties->eventtype === 'site') {
2318                     // Site context
2319                     $this->editorcontext = $this->properties->context;
2320                 } else if ($properties->eventtype === 'user') {
2321                     // User context
2322                     $this->editorcontext = $this->properties->context;
2323                 } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
2324                     // First check the course is valid
2325                     $course = $DB->get_record('course', array('id'=>$properties->courseid));
2326                     if (!$course) {
2327                         print_error('invalidcourse');
2328                     }
2329                     // Course context
2330                     $this->editorcontext = $this->properties->context;
2331                     // We have a course and are within the course context so we had
2332                     // better use the courses max bytes value
2333                     $this->editoroptions['maxbytes'] = $course->maxbytes;
2334                 } else {
2335                     // If we get here we have a custom event type as used by some
2336                     // modules. In this case the event will have been added by
2337                     // code and we won't need the editor
2338                     $this->editoroptions['maxbytes'] = 0;
2339                     $this->editoroptions['maxfiles'] = 0;
2340                 }
2342                 if (empty($this->editorcontext) || empty($this->editorcontext->id)) {
2343                     $contextid = false;
2344                 } else {
2345                     // Get the context id that is what we really want
2346                     $contextid = $this->editorcontext->id;
2347                 }
2348             } else {
2350                 // If we get here then this is a new event in which case we don't need a
2351                 // context as there is no existing files to copy to the draft area.
2352                 $contextid = null;
2353             }
2355             // If the contextid === false we don't support files so no preparing
2356             // a draft area
2357             if ($contextid !== false) {
2358                 // Just encase it has already been submitted
2359                 $draftiddescription = file_get_submitted_draft_itemid('description');
2360                 // Prepare the draft area, this copies existing files to the draft area as well
2361                 $properties->description = file_prepare_draft_area($draftiddescription, $contextid, 'calendar', 'event_description', $properties->id, $this->editoroptions, $properties->description);
2362             } else {
2363                 $draftiddescription = 0;
2364             }
2366             // Structure the description field as the editor requires
2367             $properties->description = array('text'=>$properties->description, 'format'=>$properties->format, 'itemid'=>$draftiddescription);
2368         }
2370         // Finally return the properties
2371         return $properties;
2372     }
2374     /**
2375      * Toggles the visibility of an event
2376      *
2377      * @param null|bool $force If it is left null the events visibility is flipped,
2378      *                   If it is false the event is made hidden, if it is true it
2379      *                   is made visible.
2380      * @return bool if event is successfully updated, toggle will be visible
2381      */
2382     public function toggle_visibility($force=null) {
2383         global $CFG, $DB;
2385         // Set visible to the default if it is not already set
2386         if (empty($this->properties->visible)) {
2387             $this->properties->visible = 1;
2388         }
2390         if ($force === true || ($force !== false && $this->properties->visible == 0)) {
2391             // Make this event visible
2392             $this->properties->visible = 1;
2393             // Fire the hook
2394             self::calendar_event_hook('show_event', array($this->properties));
2395         } else {
2396             // Make this event hidden
2397             $this->properties->visible = 0;
2398             // Fire the hook
2399             self::calendar_event_hook('hide_event', array($this->properties));
2400         }
2402         // Update the database to reflect this change
2403         return $DB->set_field('event', 'visible', $this->properties->visible, array('id'=>$this->properties->id));
2404     }
2406     /**
2407      * Attempts to call the hook for the specified action should a calendar type
2408      * by set $CFG->calendar, and the appopriate function defined
2409      *
2410      * @param string $action One of `update_event`, `add_event`, `delete_event`, `show_event`, `hide_event`
2411      * @param array $args The args to pass to the hook, usually the event is the first element
2412      * @return bool attempts to call event hook
2413      */
2414     public static function calendar_event_hook($action, array $args) {
2415         global $CFG;
2416         static $extcalendarinc;
2417         if ($extcalendarinc === null) {
2418             if (!empty($CFG->calendar) && file_exists($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) {
2419                 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php');
2420                 $extcalendarinc = true;
2421             } else {
2422                 $extcalendarinc = false;
2423             }
2424         }
2425         if($extcalendarinc === false) {
2426             return false;
2427         }
2428         $hook = $CFG->calendar .'_'.$action;
2429         if (function_exists($hook)) {
2430             call_user_func_array($hook, $args);
2431             return true;
2432         }
2433         return false;
2434     }
2436     /**
2437      * Returns a calendar_event object when provided with an event id
2438      *
2439      * This function makes use of MUST_EXIST, if the event id passed in is invalid
2440      * it will result in an exception being thrown
2441      *
2442      * @param int|object $param event object or event id
2443      * @return calendar_event|false status for loading calendar_event
2444      */
2445     public static function load($param) {
2446         global $DB;
2447         if (is_object($param)) {
2448             $event = new calendar_event($param);
2449         } else {
2450             $event = $DB->get_record('event', array('id'=>(int)$param), '*', MUST_EXIST);
2451             $event = new calendar_event($event);
2452         }
2453         return $event;
2454     }
2456     /**
2457      * Creates a new event and returns a calendar_event object
2458      *
2459      * @param object|array $properties An object containing event properties
2460      * @return calendar_event|false The event object or false if it failed
2461      */
2462     public static function create($properties) {
2463         if (is_array($properties)) {
2464             $properties = (object)$properties;
2465         }
2466         if (!is_object($properties)) {
2467             throw new coding_exception('When creating an event properties should be either an object or an assoc array');
2468         }
2469         $event = new calendar_event($properties);
2470         if ($event->update($properties)) {
2471             return $event;
2472         } else {
2473             return false;
2474         }
2475     }
2478 /**
2479  * Calendar information class
2480  *
2481  * This class is used simply to organise the information pertaining to a calendar
2482  * and is used primarily to make information easily available.
2483  *
2484  * @package core_calendar
2485  * @category calendar
2486  * @copyright 2010 Sam Hemelryk
2487  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2488  */
2489 class calendar_information {
2490     /** @var int The day */
2491     public $day;
2493     /** @var int The month */
2494     public $month;
2496     /** @var int The year */
2497     public $year;
2499     /** @var int A course id */
2500     public $courseid = null;
2502     /** @var array An array of courses */
2503     public $courses = array();
2505     /** @var array An array of groups */
2506     public $groups = array();
2508     /** @var array An array of users */
2509     public $users = array();
2511     /**
2512      * Creates a new instance
2513      *
2514      * @param int $day the number of the day
2515      * @param int $month the number of the month
2516      * @param int $year the number of the year
2517      */
2518     public function __construct($day=0, $month=0, $year=0) {
2520         $date = usergetdate(time());
2522         if (empty($day)) {
2523             $day = $date['mday'];
2524         }
2526         if (empty($month)) {
2527             $month = $date['mon'];
2528         }
2530         if (empty($year)) {
2531             $year =  $date['year'];
2532         }
2534         $this->day = $day;
2535         $this->month = $month;
2536         $this->year = $year;
2537     }
2539     /**
2540      * Initialize calendar information
2541      *
2542      * @param stdClass $course object
2543      * @param array $coursestoload An array of courses [$course->id => $course]
2544      * @param bool $ignorefilters options to use filter
2545      */
2546     public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) {
2547         $this->courseid = $course->id;
2548         $this->course = $course;
2549         list($courses, $group, $user) = calendar_set_filters($coursestoload, $ignorefilters);
2550         $this->courses = $courses;
2551         $this->groups = $group;
2552         $this->users = $user;
2553     }
2555     /**
2556      * Ensures the date for the calendar is correct and either sets it to now
2557      * or throws a moodle_exception if not
2558      *
2559      * @param bool $defaultonow use current time
2560      * @throws moodle_exception
2561      * @return bool validation of checkdate
2562      */
2563     public function checkdate($defaultonow = true) {
2564         if (!checkdate($this->month, $this->day, $this->year)) {
2565             if ($defaultonow) {
2566                 $now = usergetdate(time());
2567                 $this->day = intval($now['mday']);
2568                 $this->month = intval($now['mon']);
2569                 $this->year = intval($now['year']);
2570                 return true;
2571             } else {
2572                 throw new moodle_exception('invaliddate');
2573             }
2574         }
2575         return true;
2576     }
2577     /**
2578      * Gets todays timestamp for the calendar
2579      *
2580      * @return int today timestamp
2581      */
2582     public function timestamp_today() {
2583         return make_timestamp($this->year, $this->month, $this->day);
2584     }
2585     /**
2586      * Gets tomorrows timestamp for the calendar
2587      *
2588      * @return int tomorrow timestamp
2589      */
2590     public function timestamp_tomorrow() {
2591         return make_timestamp($this->year, $this->month, $this->day+1);
2592     }
2593     /**
2594      * Adds the pretend blocks for teh calendar
2595      *
2596      * @param core_calendar_renderer $renderer
2597      * @param bool $showfilters display filters, false is set as default
2598      * @param string|null $view preference view options (eg: day, month, upcoming)
2599      */
2600     public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
2601         if ($showfilters) {
2602             $filters = new block_contents();
2603             $filters->content = $renderer->fake_block_filters($this->courseid, $this->day, $this->month, $this->year, $view, $this->courses);
2604             $filters->footer = '';
2605             $filters->title = get_string('eventskey', 'calendar');
2606             $renderer->add_pretend_calendar_block($filters, BLOCK_POS_RIGHT);
2607         }
2608         $block = new block_contents;
2609         $block->content = $renderer->fake_block_threemonths($this);
2610         $block->footer = '';
2611         $block->title = get_string('monthlyview', 'calendar');
2612         $renderer->add_pretend_calendar_block($block, BLOCK_POS_RIGHT);
2613     }