MDL-58348 block_myoverview: keep paging bar under courses
[moodle.git] / calendar / lib.php
CommitLineData
93c91ee4 1<?php
08b4a4e1
RW
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
7423f116 16
08b4a4e1
RW
17/**
18 * Calendar extension
19 *
20 * @package core_calendar
21 * @copyright 2004 Greek School Network (http://www.sch.gr), Jon Papaioannou,
22 * Avgoustos Tsinakos
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
e30390a0
SH
26if (!defined('MOODLE_INTERNAL')) {
27 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
28}
b5a52acd 29
08b4a4e1
RW
30/**
31 * These are read by the administration component to provide default values
32 */
33
34/**
35 * CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD - default value of upcoming event preference
36 */
bb4a2e85 37define('CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD', 21);
08b4a4e1
RW
38
39/**
40 * CALENDAR_DEFAULT_UPCOMING_MAXEVENTS - default value to display the maximum number of upcoming event
41 */
bb4a2e85 42define('CALENDAR_DEFAULT_UPCOMING_MAXEVENTS', 10);
08b4a4e1
RW
43
44/**
45 * CALENDAR_DEFAULT_STARTING_WEEKDAY - default value to display the starting weekday
46 */
47define('CALENDAR_DEFAULT_STARTING_WEEKDAY', 1);
48
bb4a2e85 49// This is a packed bitfield: day X is "weekend" if $field & (1 << X) is true
50// Default value = 65 = 64 + 1 = 2^6 + 2^0 = Saturday & Sunday
08b4a4e1
RW
51
52/**
53 * CALENDAR_DEFAULT_WEEKEND - default value for weekend (Saturday & Sunday)
54 */
55define('CALENDAR_DEFAULT_WEEKEND', 65);
56
57/**
58 * CALENDAR_URL - path to calendar's folder
59 */
76d9df3f 60define('CALENDAR_URL', $CFG->wwwroot.'/calendar/');
08b4a4e1
RW
61
62/**
63 * CALENDAR_TF_24 - Calendar time in 24 hours format
64 */
76d9df3f 65define('CALENDAR_TF_24', '%H:%M');
08b4a4e1
RW
66
67/**
68 * CALENDAR_TF_12 - Calendar time in 12 hours format
69 */
76d9df3f 70define('CALENDAR_TF_12', '%I:%M %p');
bb4a2e85 71
08b4a4e1
RW
72/**
73 * CALENDAR_EVENT_GLOBAL - Global calendar event types
74 */
797cedc7 75define('CALENDAR_EVENT_GLOBAL', 1);
08b4a4e1
RW
76
77/**
78 * CALENDAR_EVENT_COURSE - Course calendar event types
79 */
797cedc7 80define('CALENDAR_EVENT_COURSE', 2);
08b4a4e1
RW
81
82/**
83 * CALENDAR_EVENT_GROUP - group calendar event types
84 */
797cedc7 85define('CALENDAR_EVENT_GROUP', 4);
08b4a4e1
RW
86
87/**
88 * CALENDAR_EVENT_USER - user calendar event types
89 */
797cedc7
SH
90define('CALENDAR_EVENT_USER', 8);
91
7423f116 92
b5a52acd
JH
93/**
94 * CALENDAR_IMPORT_FROM_FILE - import the calendar from a file
95 */
96define('CALENDAR_IMPORT_FROM_FILE', 0);
97
98/**
99 * CALENDAR_IMPORT_FROM_URL - import the calendar from a URL
100 */
101define('CALENDAR_IMPORT_FROM_URL', 1);
102
103/**
104 * CALENDAR_IMPORT_EVENT_UPDATED - imported event was updated
105 */
106define('CALENDAR_IMPORT_EVENT_UPDATED', 1);
107
108/**
109 * CALENDAR_IMPORT_EVENT_INSERTED - imported event was added by insert
110 */
111define('CALENDAR_IMPORT_EVENT_INSERTED', 2);
112
ee74a2a1
AA
113/**
114 * CALENDAR_SUBSCRIPTION_UPDATE - Used to represent update action for subscriptions in various forms.
115 */
116define('CALENDAR_SUBSCRIPTION_UPDATE', 1);
117
118/**
119 * CALENDAR_SUBSCRIPTION_REMOVE - Used to represent remove action for subscriptions in various forms.
120 */
121define('CALENDAR_SUBSCRIPTION_REMOVE', 2);
122
ca75ec4f
JP
123/**
124 * CALENDAR_EVENT_USER_OVERRIDE_PRIORITY - Constant for the user override priority.
125 */
126define('CALENDAR_EVENT_USER_OVERRIDE_PRIORITY', 9999999);
127
5ca71c2d
CB
128/**
129 * CALENDAR_EVENT_TYPE_STANDARD - Standard events.
130 */
131define('CALENDAR_EVENT_TYPE_STANDARD', 0);
132
133/**
134 * CALENDAR_EVENT_TYPE_ACTION - Action events.
135 */
136define('CALENDAR_EVENT_TYPE_ACTION', 1);
137
e1cd93ce
MN
138/**
139 * Manage calendar events.
140 *
141 * This class provides the required functionality in order to manage calendar events.
142 * It was introduced as part of Moodle 2.0 and was created in order to provide a
143 * better framework for dealing with calendar events in particular regard to file
144 * handling through the new file API.
145 *
146 * @package core_calendar
147 * @category calendar
148 * @copyright 2009 Sam Hemelryk
149 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
150 *
151 * @property int $id The id within the event table
152 * @property string $name The name of the event
153 * @property string $description The description of the event
154 * @property int $format The format of the description FORMAT_?
155 * @property int $courseid The course the event is associated with (0 if none)
156 * @property int $groupid The group the event is associated with (0 if none)
157 * @property int $userid The user the event is associated with (0 if none)
158 * @property int $repeatid If this is a repeated event this will be set to the
159 * id of the original
160 * @property string $modulename If added by a module this will be the module name
161 * @property int $instance If added by a module this will be the module instance
162 * @property string $eventtype The event type
163 * @property int $timestart The start time as a timestamp
164 * @property int $timeduration The duration of the event in seconds
165 * @property int $visible 1 if the event is visible
166 * @property int $uuid ?
167 * @property int $sequence ?
168 * @property int $timemodified The time last modified as a timestamp
169 */
170class calendar_event {
171
172 /** @var array An object containing the event properties can be accessed via the magic __get/set methods */
173 protected $properties = null;
174
175 /** @var string The converted event discription with file paths resolved.
176 * This gets populated when someone requests description for the first time */
177 protected $_description = null;
178
179 /** @var array The options to use with this description editor */
180 protected $editoroptions = array(
181 'subdirs' => false,
182 'forcehttps' => false,
183 'maxfiles' => -1,
184 'maxbytes' => null,
185 'trusttext' => false);
186
187 /** @var object The context to use with the description editor */
188 protected $editorcontext = null;
189
190 /**
191 * Instantiates a new event and optionally populates its properties with the data provided.
192 *
193 * @param \stdClass $data Optional. An object containing the properties to for
194 * an event
195 */
196 public function __construct($data = null) {
197 global $CFG, $USER;
198
199 // First convert to object if it is not already (should either be object or assoc array).
200 if (!is_object($data)) {
201 $data = (object) $data;
202 }
203
204 $this->editoroptions['maxbytes'] = $CFG->maxbytes;
205
206 $data->eventrepeats = 0;
207
208 if (empty($data->id)) {
209 $data->id = null;
210 }
211
212 if (!empty($data->subscriptionid)) {
213 $data->subscription = \core_calendar\api::get_subscription($data->subscriptionid);
214 }
215
216 // Default to a user event.
217 if (empty($data->eventtype)) {
218 $data->eventtype = 'user';
219 }
220
221 // Default to the current user.
222 if (empty($data->userid)) {
223 $data->userid = $USER->id;
224 }
225
226 if (!empty($data->timeduration) && is_array($data->timeduration)) {
227 $data->timeduration = make_timestamp(
228 $data->timeduration['year'], $data->timeduration['month'], $data->timeduration['day'],
229 $data->timeduration['hour'], $data->timeduration['minute']) - $data->timestart;
230 }
231
232 if (!empty($data->description) && is_array($data->description)) {
233 $data->format = $data->description['format'];
234 $data->description = $data->description['text'];
235 } else if (empty($data->description)) {
236 $data->description = '';
237 $data->format = editors_get_preferred_format();
238 }
239
240 // Ensure form is defaulted correctly.
241 if (empty($data->format)) {
242 $data->format = editors_get_preferred_format();
243 }
244
245 $this->properties = $data;
246
247 if (empty($data->context)) {
248 $this->properties->context = $this->calculate_context();
249 }
250 }
251
252 /**
253 * Magic set method.
254 *
255 * Attempts to call a set_$key method if one exists otherwise falls back
256 * to simply set the property.
257 *
258 * @param string $key property name
259 * @param mixed $value value of the property
260 */
261 public function __set($key, $value) {
262 if (method_exists($this, 'set_'.$key)) {
263 $this->{'set_'.$key}($value);
264 }
265 $this->properties->{$key} = $value;
266 }
267
268 /**
269 * Magic get method.
270 *
271 * Attempts to call a get_$key method to return the property and ralls over
272 * to return the raw property.
273 *
274 * @param string $key property name
275 * @return mixed property value
276 * @throws \coding_exception
277 */
278 public function __get($key) {
279 if (method_exists($this, 'get_'.$key)) {
280 return $this->{'get_'.$key}();
281 }
2a8e41b9 282 if (!property_exists($this->properties, $key)) {
e1cd93ce
MN
283 throw new \coding_exception('Undefined property requested');
284 }
285 return $this->properties->{$key};
286 }
287
288 /**
289 * Magic isset method.
290 *
291 * PHP needs an isset magic method if you use the get magic method and
292 * still want empty calls to work.
293 *
294 * @param string $key $key property name
295 * @return bool|mixed property value, false if property is not exist
296 */
297 public function __isset($key) {
298 return !empty($this->properties->{$key});
299 }
300
301 /**
302 * Calculate the context value needed for an event.
303 *
304 * Event's type can be determine by the available value store in $data
305 * It is important to check for the existence of course/courseid to determine
306 * the course event.
307 * Default value is set to CONTEXT_USER
308 *
309 * @return \stdClass The context object.
310 */
311 protected function calculate_context() {
312 global $USER, $DB;
313
314 $context = null;
315 if (isset($this->properties->courseid) && $this->properties->courseid > 0) {
316 $context = \context_course::instance($this->properties->courseid);
317 } else if (isset($this->properties->course) && $this->properties->course > 0) {
318 $context = \context_course::instance($this->properties->course);
319 } else if (isset($this->properties->groupid) && $this->properties->groupid > 0) {
320 $group = $DB->get_record('groups', array('id' => $this->properties->groupid));
321 $context = \context_course::instance($group->courseid);
322 } else if (isset($this->properties->userid) && $this->properties->userid > 0
323 && $this->properties->userid == $USER->id) {
324 $context = \context_user::instance($this->properties->userid);
325 } else if (isset($this->properties->userid) && $this->properties->userid > 0
326 && $this->properties->userid != $USER->id &&
327 isset($this->properties->instance) && $this->properties->instance > 0) {
328 $cm = get_coursemodule_from_instance($this->properties->modulename, $this->properties->instance, 0,
329 false, MUST_EXIST);
330 $context = \context_course::instance($cm->course);
331 } else {
332 $context = \context_user::instance($this->properties->userid);
333 }
334
335 return $context;
336 }
337
338 /**
339 * Returns an array of editoroptions for this event.
340 *
341 * @return array event editor options
342 */
343 protected function get_editoroptions() {
344 return $this->editoroptions;
345 }
346
347 /**
348 * Returns an event description: Called by __get
349 * Please use $blah = $event->description;
350 *
351 * @return string event description
352 */
353 protected function get_description() {
354 global $CFG;
355
356 require_once($CFG->libdir . '/filelib.php');
357
358 if ($this->_description === null) {
359 // Check if we have already resolved the context for this event.
360 if ($this->editorcontext === null) {
361 // Switch on the event type to decide upon the appropriate context to use for this event.
362 $this->editorcontext = $this->properties->context;
363 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
364 && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
365 return clean_text($this->properties->description, $this->properties->format);
366 }
367 }
368
369 // Work out the item id for the editor, if this is a repeated event
370 // then the files will be associated with the original.
371 if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
372 $itemid = $this->properties->repeatid;
373 } else {
374 $itemid = $this->properties->id;
375 }
376
377 // Convert file paths in the description so that things display correctly.
378 $this->_description = file_rewrite_pluginfile_urls($this->properties->description, 'pluginfile.php',
379 $this->editorcontext->id, 'calendar', 'event_description', $itemid);
380 // Clean the text so no nasties get through.
381 $this->_description = clean_text($this->_description, $this->properties->format);
382 }
383
384 // Finally return the description.
385 return $this->_description;
386 }
387
388 /**
389 * Return the number of repeat events there are in this events series.
390 *
391 * @return int number of event repeated
392 */
393 public function count_repeats() {
394 global $DB;
395 if (!empty($this->properties->repeatid)) {
396 $this->properties->eventrepeats = $DB->count_records('event',
397 array('repeatid' => $this->properties->repeatid));
398 // We don't want to count ourselves.
399 $this->properties->eventrepeats--;
400 }
401 return $this->properties->eventrepeats;
402 }
403
404 /**
405 * Update or create an event within the database
406 *
407 * Pass in a object containing the event properties and this function will
408 * insert it into the database and deal with any associated files
409 *
410 * @see self::create()
411 * @see self::update()
412 *
413 * @param \stdClass $data object of event
414 * @param bool $checkcapability if moodle should check calendar managing capability or not
415 * @return bool event updated
416 */
417 public function update($data, $checkcapability=true) {
418 global $DB, $USER;
419
420 foreach ($data as $key => $value) {
421 $this->properties->$key = $value;
422 }
423
424 $this->properties->timemodified = time();
425 $usingeditor = (!empty($this->properties->description) && is_array($this->properties->description));
426
427 // Prepare event data.
428 $eventargs = array(
429 'context' => $this->properties->context,
430 'objectid' => $this->properties->id,
431 'other' => array(
432 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
433 'timestart' => $this->properties->timestart,
434 'name' => $this->properties->name
435 )
436 );
437
438 if (empty($this->properties->id) || $this->properties->id < 1) {
439
440 if ($checkcapability) {
441 if (!\core_calendar\api::can_add_event($this->properties)) {
442 print_error('nopermissiontoupdatecalendar');
443 }
444 }
445
446 if ($usingeditor) {
447 switch ($this->properties->eventtype) {
448 case 'user':
449 $this->properties->courseid = 0;
450 $this->properties->course = 0;
451 $this->properties->groupid = 0;
452 $this->properties->userid = $USER->id;
453 break;
454 case 'site':
455 $this->properties->courseid = SITEID;
456 $this->properties->course = SITEID;
457 $this->properties->groupid = 0;
458 $this->properties->userid = $USER->id;
459 break;
460 case 'course':
461 $this->properties->groupid = 0;
462 $this->properties->userid = $USER->id;
463 break;
464 case 'group':
465 $this->properties->userid = $USER->id;
466 break;
467 default:
468 // We should NEVER get here, but just incase we do lets fail gracefully.
469 $usingeditor = false;
470 break;
471 }
472
473 // If we are actually using the editor, we recalculate the context because some default values
474 // were set when calculate_context() was called from the constructor.
475 if ($usingeditor) {
476 $this->properties->context = $this->calculate_context();
477 $this->editorcontext = $this->properties->context;
478 }
479
480 $editor = $this->properties->description;
481 $this->properties->format = $this->properties->description['format'];
482 $this->properties->description = $this->properties->description['text'];
483 }
484
485 // Insert the event into the database.
486 $this->properties->id = $DB->insert_record('event', $this->properties);
487
488 if ($usingeditor) {
489 $this->properties->description = file_save_draft_area_files(
490 $editor['itemid'],
491 $this->editorcontext->id,
492 'calendar',
493 'event_description',
494 $this->properties->id,
495 $this->editoroptions,
496 $editor['text'],
497 $this->editoroptions['forcehttps']);
498 $DB->set_field('event', 'description', $this->properties->description,
499 array('id' => $this->properties->id));
500 }
501
502 // Log the event entry.
503 $eventargs['objectid'] = $this->properties->id;
504 $eventargs['context'] = $this->properties->context;
505 $event = \core\event\calendar_event_created::create($eventargs);
506 $event->trigger();
507
508 $repeatedids = array();
509
510 if (!empty($this->properties->repeat)) {
511 $this->properties->repeatid = $this->properties->id;
512 $DB->set_field('event', 'repeatid', $this->properties->repeatid, array('id' => $this->properties->id));
513
514 $eventcopy = clone($this->properties);
515 unset($eventcopy->id);
516
517 $timestart = new \DateTime('@' . $eventcopy->timestart);
518 $timestart->setTimezone(\core_date::get_user_timezone_object());
519
520 for ($i = 1; $i < $eventcopy->repeats; $i++) {
521
522 $timestart->add(new \DateInterval('P7D'));
523 $eventcopy->timestart = $timestart->getTimestamp();
524
525 // Get the event id for the log record.
526 $eventcopyid = $DB->insert_record('event', $eventcopy);
527
528 // If the context has been set delete all associated files.
529 if ($usingeditor) {
530 $fs = get_file_storage();
531 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description',
532 $this->properties->id);
533 foreach ($files as $file) {
534 $fs->create_file_from_storedfile(array('itemid' => $eventcopyid), $file);
535 }
536 }
537
538 $repeatedids[] = $eventcopyid;
539
540 // Trigger an event.
541 $eventargs['objectid'] = $eventcopyid;
542 $eventargs['other']['timestart'] = $eventcopy->timestart;
543 $event = \core\event\calendar_event_created::create($eventargs);
544 $event->trigger();
545 }
546 }
547
548 return true;
549 } else {
550
551 if ($checkcapability) {
552 if (!\core_calendar\api::can_edit_event($this->properties)) {
553 print_error('nopermissiontoupdatecalendar');
554 }
555 }
556
557 if ($usingeditor) {
558 if ($this->editorcontext !== null) {
559 $this->properties->description = file_save_draft_area_files(
560 $this->properties->description['itemid'],
561 $this->editorcontext->id,
562 'calendar',
563 'event_description',
564 $this->properties->id,
565 $this->editoroptions,
566 $this->properties->description['text'],
567 $this->editoroptions['forcehttps']);
568 } else {
569 $this->properties->format = $this->properties->description['format'];
570 $this->properties->description = $this->properties->description['text'];
571 }
572 }
573
574 $event = $DB->get_record('event', array('id' => $this->properties->id));
575
576 $updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
577
578 if ($updaterepeated) {
579 // Update all.
580 if ($this->properties->timestart != $event->timestart) {
581 $timestartoffset = $this->properties->timestart - $event->timestart;
582 $sql = "UPDATE {event}
583 SET name = ?,
584 description = ?,
585 timestart = timestart + ?,
586 timeduration = ?,
587 timemodified = ?
588 WHERE repeatid = ?";
589 $params = array($this->properties->name, $this->properties->description, $timestartoffset,
590 $this->properties->timeduration, time(), $event->repeatid);
591 } else {
592 $sql = "UPDATE {event} SET name = ?, description = ?, timeduration = ?, timemodified = ? WHERE repeatid = ?";
593 $params = array($this->properties->name, $this->properties->description,
594 $this->properties->timeduration, time(), $event->repeatid);
595 }
596 $DB->execute($sql, $params);
597
598 // Trigger an update event for each of the calendar event.
599 $events = $DB->get_records('event', array('repeatid' => $event->repeatid), '', '*');
600 foreach ($events as $calendarevent) {
601 $eventargs['objectid'] = $calendarevent->id;
602 $eventargs['other']['timestart'] = $calendarevent->timestart;
603 $event = \core\event\calendar_event_updated::create($eventargs);
604 $event->add_record_snapshot('event', $calendarevent);
605 $event->trigger();
606 }
607 } else {
608 $DB->update_record('event', $this->properties);
609 $event = self::load($this->properties->id);
610 $this->properties = $event->properties();
611
612 // Trigger an update event.
613 $event = \core\event\calendar_event_updated::create($eventargs);
614 $event->add_record_snapshot('event', $this->properties);
615 $event->trigger();
616 }
617
618 return true;
619 }
620 }
621
622 /**
623 * Deletes an event and if selected an repeated events in the same series
624 *
625 * This function deletes an event, any associated events if $deleterepeated=true,
626 * and cleans up any files associated with the events.
627 *
628 * @see self::delete()
629 *
630 * @param bool $deleterepeated delete event repeatedly
631 * @return bool succession of deleting event
632 */
633 public function delete($deleterepeated = false) {
634 global $DB;
635
636 // If $this->properties->id is not set then something is wrong.
637 if (empty($this->properties->id)) {
638 debugging('Attempting to delete an event before it has been loaded', DEBUG_DEVELOPER);
639 return false;
640 }
641 $calevent = $DB->get_record('event', array('id' => $this->properties->id), '*', MUST_EXIST);
642 // Delete the event.
643 $DB->delete_records('event', array('id' => $this->properties->id));
644
645 // Trigger an event for the delete action.
646 $eventargs = array(
647 'context' => $this->properties->context,
648 'objectid' => $this->properties->id,
649 'other' => array(
650 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
651 'timestart' => $this->properties->timestart,
652 'name' => $this->properties->name
653 ));
654 $event = \core\event\calendar_event_deleted::create($eventargs);
655 $event->add_record_snapshot('event', $calevent);
656 $event->trigger();
657
658 // If we are deleting parent of a repeated event series, promote the next event in the series as parent.
659 if (($this->properties->id == $this->properties->repeatid) && !$deleterepeated) {
660 $newparent = $DB->get_field_sql("SELECT id from {event} where repeatid = ? order by id ASC",
661 array($this->properties->id), IGNORE_MULTIPLE);
662 if (!empty($newparent)) {
663 $DB->execute("UPDATE {event} SET repeatid = ? WHERE repeatid = ?",
664 array($newparent, $this->properties->id));
665 // Get all records where the repeatid is the same as the event being removed.
666 $events = $DB->get_records('event', array('repeatid' => $newparent));
667 // For each of the returned events trigger an update event.
668 foreach ($events as $calendarevent) {
669 // Trigger an event for the update.
670 $eventargs['objectid'] = $calendarevent->id;
671 $eventargs['other']['timestart'] = $calendarevent->timestart;
672 $event = \core\event\calendar_event_updated::create($eventargs);
673 $event->add_record_snapshot('event', $calendarevent);
674 $event->trigger();
675 }
676 }
677 }
678
679 // If the editor context hasn't already been set then set it now.
680 if ($this->editorcontext === null) {
681 $this->editorcontext = $this->properties->context;
682 }
683
684 // If the context has been set delete all associated files.
685 if ($this->editorcontext !== null) {
686 $fs = get_file_storage();
687 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id);
688 foreach ($files as $file) {
689 $file->delete();
690 }
691 }
692
693 // If we need to delete repeated events then we will fetch them all and delete one by one.
694 if ($deleterepeated && !empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
695 // Get all records where the repeatid is the same as the event being removed.
696 $events = $DB->get_records('event', array('repeatid' => $this->properties->repeatid));
697 // For each of the returned events populate an event object and call delete.
698 // make sure the arg passed is false as we are already deleting all repeats.
699 foreach ($events as $event) {
700 $event = new calendar_event($event);
701 $event->delete(false);
702 }
703 }
704
705 return true;
706 }
707
708 /**
709 * Fetch all event properties.
710 *
711 * This function returns all of the events properties as an object and optionally
712 * can prepare an editor for the description field at the same time. This is
713 * designed to work when the properties are going to be used to set the default
714 * values of a moodle forms form.
715 *
716 * @param bool $prepareeditor If set to true a editor is prepared for use with
717 * the mforms editor element. (for description)
718 * @return \stdClass Object containing event properties
719 */
720 public function properties($prepareeditor = false) {
721 global $DB;
722
723 // First take a copy of the properties. We don't want to actually change the
724 // properties or we'd forever be converting back and forwards between an
725 // editor formatted description and not.
726 $properties = clone($this->properties);
727 // Clean the description here.
728 $properties->description = clean_text($properties->description, $properties->format);
729
730 // If set to true we need to prepare the properties for use with an editor
731 // and prepare the file area.
732 if ($prepareeditor) {
733
734 // We may or may not have a property id. If we do then we need to work
735 // out the context so we can copy the existing files to the draft area.
736 if (!empty($properties->id)) {
737
738 if ($properties->eventtype === 'site') {
739 // Site context.
740 $this->editorcontext = $this->properties->context;
741 } else if ($properties->eventtype === 'user') {
742 // User context.
743 $this->editorcontext = $this->properties->context;
744 } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
745 // First check the course is valid.
746 $course = $DB->get_record('course', array('id' => $properties->courseid));
747 if (!$course) {
748 print_error('invalidcourse');
749 }
750 // Course context.
751 $this->editorcontext = $this->properties->context;
752 // We have a course and are within the course context so we had
753 // better use the courses max bytes value.
754 $this->editoroptions['maxbytes'] = $course->maxbytes;
755 } else {
756 // If we get here we have a custom event type as used by some
757 // modules. In this case the event will have been added by
758 // code and we won't need the editor.
759 $this->editoroptions['maxbytes'] = 0;
760 $this->editoroptions['maxfiles'] = 0;
761 }
762
763 if (empty($this->editorcontext) || empty($this->editorcontext->id)) {
764 $contextid = false;
765 } else {
766 // Get the context id that is what we really want.
767 $contextid = $this->editorcontext->id;
768 }
769 } else {
770
771 // If we get here then this is a new event in which case we don't need a
772 // context as there is no existing files to copy to the draft area.
773 $contextid = null;
774 }
775
776 // If the contextid === false we don't support files so no preparing
777 // a draft area.
778 if ($contextid !== false) {
779 // Just encase it has already been submitted.
780 $draftiddescription = file_get_submitted_draft_itemid('description');
781 // Prepare the draft area, this copies existing files to the draft area as well.
782 $properties->description = file_prepare_draft_area($draftiddescription, $contextid, 'calendar',
783 'event_description', $properties->id, $this->editoroptions, $properties->description);
784 } else {
785 $draftiddescription = 0;
786 }
787
788 // Structure the description field as the editor requires.
789 $properties->description = array('text' => $properties->description, 'format' => $properties->format,
790 'itemid' => $draftiddescription);
791 }
792
793 // Finally return the properties.
794 return $properties;
795 }
796
797 /**
798 * Toggles the visibility of an event
799 *
800 * @param null|bool $force If it is left null the events visibility is flipped,
801 * If it is false the event is made hidden, if it is true it
802 * is made visible.
803 * @return bool if event is successfully updated, toggle will be visible
804 */
805 public function toggle_visibility($force = null) {
806 global $DB;
807
808 // Set visible to the default if it is not already set.
809 if (empty($this->properties->visible)) {
810 $this->properties->visible = 1;
811 }
812
813 if ($force === true || ($force !== false && $this->properties->visible == 0)) {
814 // Make this event visible.
815 $this->properties->visible = 1;
816 } else {
817 // Make this event hidden.
818 $this->properties->visible = 0;
819 }
820
821 // Update the database to reflect this change.
822 $success = $DB->set_field('event', 'visible', $this->properties->visible, array('id' => $this->properties->id));
823 $calendarevent = $DB->get_record('event', array('id' => $this->properties->id), '*', MUST_EXIST);
824
825 // Prepare event data.
826 $eventargs = array(
827 'context' => $this->properties->context,
828 'objectid' => $this->properties->id,
829 'other' => array(
830 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
831 'timestart' => $this->properties->timestart,
832 'name' => $this->properties->name
833 )
834 );
835 $event = \core\event\calendar_event_updated::create($eventargs);
836 $event->add_record_snapshot('event', $calendarevent);
837 $event->trigger();
838
839 return $success;
840 }
841
842 /**
843 * Returns an event object when provided with an event id.
844 *
845 * This function makes use of MUST_EXIST, if the event id passed in is invalid
846 * it will result in an exception being thrown.
847 *
848 * @param int|object $param event object or event id
849 * @return calendar_event
850 */
851 public static function load($param) {
852 global $DB;
853 if (is_object($param)) {
854 $event = new calendar_event($param);
855 } else {
856 $event = $DB->get_record('event', array('id' => (int)$param), '*', MUST_EXIST);
857 $event = new calendar_event($event);
858 }
859 return $event;
860 }
861
862 /**
863 * Creates a new event and returns an event object
864 *
865 * @param \stdClass|array $properties An object containing event properties
866 * @param bool $checkcapability Check caps or not
867 * @throws \coding_exception
868 *
869 * @return calendar_event|bool The event object or false if it failed
870 */
871 public static function create($properties, $checkcapability = true) {
872 if (is_array($properties)) {
873 $properties = (object)$properties;
874 }
875 if (!is_object($properties)) {
876 throw new \coding_exception('When creating an event properties should be either an object or an assoc array');
877 }
878 $event = new calendar_event($properties);
879 if ($event->update($properties, $checkcapability)) {
880 return $event;
881 } else {
882 return false;
883 }
884 }
885
886 /**
887 * Format the text using the external API.
888 *
889 * This function should we used when text formatting is required in external functions.
890 *
891 * @return array an array containing the text formatted and the text format
892 */
893 public function format_external_text() {
894
895 if ($this->editorcontext === null) {
896 // Switch on the event type to decide upon the appropriate context to use for this event.
897 $this->editorcontext = $this->properties->context;
898
899 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
900 && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
901 // We don't have a context here, do a normal format_text.
902 return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id);
903 }
904 }
905
906 // Work out the item id for the editor, if this is a repeated event then the files will be associated with the original.
907 if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) {
908 $itemid = $this->properties->repeatid;
909 } else {
910 $itemid = $this->properties->id;
911 }
912
913 return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id,
914 'calendar', 'event_description', $itemid);
915 }
916}
917
36dc3b71
SH
918/**
919 * Calendar information class
920 *
921 * This class is used simply to organise the information pertaining to a calendar
922 * and is used primarily to make information easily available.
08b4a4e1
RW
923 *
924 * @package core_calendar
925 * @category calendar
926 * @copyright 2010 Sam Hemelryk
927 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36dc3b71
SH
928 */
929class calendar_information {
08b4a4e1 930
da304137
MN
931 /**
932 * @var int The timestamp
933 *
934 * Rather than setting the day, month and year we will set a timestamp which will be able
935 * to be used by multiple calendars.
936 */
937 public $time;
36dc3b71 938
08b4a4e1 939 /** @var int A course id */
36dc3b71 940 public $courseid = null;
08b4a4e1
RW
941
942 /** @var array An array of courses */
36dc3b71 943 public $courses = array();
08b4a4e1
RW
944
945 /** @var array An array of groups */
36dc3b71 946 public $groups = array();
08b4a4e1
RW
947
948 /** @var array An array of users */
36dc3b71
SH
949 public $users = array();
950
951 /**
952 * Creates a new instance
953 *
08b4a4e1
RW
954 * @param int $day the number of the day
955 * @param int $month the number of the month
956 * @param int $year the number of the year
da304137
MN
957 * @param int $time the unixtimestamp representing the date we want to view, this is used instead of $calmonth
958 * and $calyear to support multiple calendars
36dc3b71 959 */
da304137
MN
960 public function __construct($day = 0, $month = 0, $year = 0, $time = 0) {
961 // If a day, month and year were passed then convert it to a timestamp. If these were passed
962 // then we can assume the day, month and year are passed as Gregorian, as no where in core
963 // should we be passing these values rather than the time. This is done for BC.
964 if (!empty($day) || !empty($month) || !empty($year)) {
965 $date = usergetdate(time());
966 if (empty($day)) {
967 $day = $date['mday'];
968 }
969 if (empty($month)) {
970 $month = $date['mon'];
971 }
972 if (empty($year)) {
973 $year = $date['year'];
974 }
975 if (checkdate($month, $day, $year)) {
976 $this->time = make_timestamp($year, $month, $day);
977 } else {
978 $this->time = time();
979 }
980 } else if (!empty($time)) {
981 $this->time = $time;
982 } else {
983 $this->time = time();
d8d8bdd9 984 }
36dc3b71
SH
985 }
986
797cedc7 987 /**
08b4a4e1 988 * Initialize calendar information
797cedc7 989 *
08b4a4e1 990 * @param stdClass $course object
797cedc7 991 * @param array $coursestoload An array of courses [$course->id => $course]
08b4a4e1 992 * @param bool $ignorefilters options to use filter
797cedc7
SH
993 */
994 public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) {
995 $this->courseid = $course->id;
996 $this->course = $course;
12cbce0a 997 list($courses, $group, $user) = \core_calendar\api::set_filters($coursestoload, $ignorefilters);
797cedc7
SH
998 $this->courses = $courses;
999 $this->groups = $group;
1000 $this->users = $user;
1001 }
1002
36dc3b71
SH
1003 /**
1004 * Ensures the date for the calendar is correct and either sets it to now
1005 * or throws a moodle_exception if not
1006 *
08b4a4e1
RW
1007 * @param bool $defaultonow use current time
1008 * @throws moodle_exception
1009 * @return bool validation of checkdate
36dc3b71
SH
1010 */
1011 public function checkdate($defaultonow = true) {
1012 if (!checkdate($this->month, $this->day, $this->year)) {
1013 if ($defaultonow) {
1014 $now = usergetdate(time());
1015 $this->day = intval($now['mday']);
1016 $this->month = intval($now['mon']);
1017 $this->year = intval($now['year']);
1018 return true;
1019 } else {
1020 throw new moodle_exception('invaliddate');
1021 }
1022 }
1023 return true;
1024 }
da304137 1025
36dc3b71
SH
1026 /**
1027 * Gets todays timestamp for the calendar
08b4a4e1
RW
1028 *
1029 * @return int today timestamp
36dc3b71
SH
1030 */
1031 public function timestamp_today() {
da304137 1032 return $this->time;
36dc3b71
SH
1033 }
1034 /**
1035 * Gets tomorrows timestamp for the calendar
08b4a4e1
RW
1036 *
1037 * @return int tomorrow timestamp
36dc3b71
SH
1038 */
1039 public function timestamp_tomorrow() {
a0ef87de 1040 return strtotime('+1 day', $this->time);
36dc3b71
SH
1041 }
1042 /**
e30390a0 1043 * Adds the pretend blocks for the calendar
36dc3b71
SH
1044 *
1045 * @param core_calendar_renderer $renderer
08b4a4e1
RW
1046 * @param bool $showfilters display filters, false is set as default
1047 * @param string|null $view preference view options (eg: day, month, upcoming)
36dc3b71
SH
1048 */
1049 public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
1050 if ($showfilters) {
1051 $filters = new block_contents();
da304137 1052 $filters->content = $renderer->fake_block_filters($this->courseid, 0, 0, 0, $view, $this->courses);
36dc3b71
SH
1053 $filters->footer = '';
1054 $filters->title = get_string('eventskey', 'calendar');
1055 $renderer->add_pretend_calendar_block($filters, BLOCK_POS_RIGHT);
1056 }
1057 $block = new block_contents;
1058 $block->content = $renderer->fake_block_threemonths($this);
1059 $block->footer = '';
1060 $block->title = get_string('monthlyview', 'calendar');
1061 $renderer->add_pretend_calendar_block($block, BLOCK_POS_RIGHT);
1062 }
1d5bd3d2 1063}
b5a52acd 1064
6e65554e
MG
1065/**
1066 * Implements callback user_preferences, whitelists preferences that users are allowed to update directly
1067 *
1068 * Used in {@see core_user::fill_preferences_cache()}, see also {@see useredit_update_user_preference()}
1069 *
1070 * @return array
1071 */
1072function core_calendar_user_preferences() {
1073 $preferences = [];
1074 $preferences['calendar_timeformat'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED, 'default' => '0',
1075 'choices' => array('0', CALENDAR_TF_12, CALENDAR_TF_24)
1076 );
1077 $preferences['calendar_startwday'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
1078 'choices' => array(0, 1, 2, 3, 4, 5, 6));
1079 $preferences['calendar_maxevents'] = array('type' => PARAM_INT, 'choices' => range(1, 20));
1080 $preferences['calendar_lookahead'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 365,
1081 'choices' => array(365, 270, 180, 150, 120, 90, 60, 30, 21, 14, 7, 6, 5, 4, 3, 2, 1));
1082 $preferences['calendar_persistflt'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0,
1083 'choices' => array(0, 1));
1084 return $preferences;
663640f5 1085}