aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/dav/lib/CalDAV
diff options
context:
space:
mode:
authorredmatrix <git@macgirvin.com>2016-05-10 17:26:44 -0700
committerredmatrix <git@macgirvin.com>2016-05-10 17:26:44 -0700
commit0b02a6d123b2014705998c94ddf3d460948d3eac (patch)
tree78ff2cab9944a4f5ab3f80ec93cbe1120de90bb2 /vendor/sabre/dav/lib/CalDAV
parent40b5b6e9d2da7ab65c8b4d38cdceac83a4d78deb (diff)
downloadvolse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.tar.gz
volse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.tar.bz2
volse-hubzilla-0b02a6d123b2014705998c94ddf3d460948d3eac.zip
initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import)
Diffstat (limited to 'vendor/sabre/dav/lib/CalDAV')
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php226
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php268
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php46
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/PDO.php1210
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php65
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php243
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php89
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php81
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Calendar.php527
-rw-r--r--vendor/sabre/dav/lib/CalDAV/CalendarHome.php430
-rw-r--r--vendor/sabre/dav/lib/CalDAV/CalendarObject.php290
-rw-r--r--vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php375
-rw-r--r--vendor/sabre/dav/lib/CalDAV/CalendarRoot.php80
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php35
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php366
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ICalendar.php18
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ICalendarObject.php21
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php39
-rw-r--r--vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php48
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php36
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php173
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php23
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/INode.php38
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/Node.php193
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php180
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Plugin.php1025
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Principal/Collection.php33
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php19
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php19
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php181
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php181
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Principal/User.php135
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php15
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php190
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php15
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php13
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php261
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php184
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php994
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php165
-rw-r--r--vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php72
-rw-r--r--vendor/sabre/dav/lib/CalDAV/SharedCalendar.php148
-rw-r--r--vendor/sabre/dav/lib/CalDAV/SharingPlugin.php426
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php40
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php120
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php274
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php84
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php97
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php82
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php98
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php307
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php212
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php45
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php182
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php87
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php80
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php252
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php140
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php129
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php60
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php57
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php124
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php139
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php91
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php149
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php79
-rw-r--r--vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php116
67 files changed, 12220 insertions, 0 deletions
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php
new file mode 100644
index 000000000..d58b4a46e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+use Sabre\VObject;
+use Sabre\CalDAV;
+
+/**
+ * Abstract Calendaring backend. Extend this class to create your own backends.
+ *
+ * Checkout the BackendInterface for all the methods that must be implemented.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+abstract class AbstractBackend implements BackendInterface {
+
+ /**
+ * Updates properties for a calendar.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documenation for more info and examples.
+ *
+ * @param string $path
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
+
+ }
+
+ /**
+ * Returns a list of calendar objects.
+ *
+ * This method should work identical to getCalendarObject, but instead
+ * return all the calendar objects in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $calendarId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCalendarObjects($calendarId, array $uris) {
+
+ return array_map(function($uri) use ($calendarId) {
+ return $this->getCalendarObject($calendarId, $uri);
+ }, $uris);
+
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on either VEVENT or VTODO.
+ *
+ * ..and combinations of these requests. It may not be worth it to try to
+ * handle every possible situation and just rely on the (relatively
+ * easy to use) CalendarQueryValidator to handle the rest.
+ *
+ * Note that especially time-range-filters may be difficult to parse. A
+ * time-range filter specified on a VEVENT must for instance also handle
+ * recurrence rules correctly.
+ * A good example of how to interprete all these filters can also simply
+ * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
+ * as possible, so it gives you a good idea on what type of stuff you need
+ * to think of.
+ *
+ * @param mixed $calendarId
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery($calendarId, array $filters) {
+
+ $result = [];
+ $objects = $this->getCalendarObjects($calendarId);
+
+ foreach ($objects as $object) {
+
+ if ($this->validateFilterForObject($object, $filters)) {
+ $result[] = $object['uri'];
+ }
+
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * This method validates if a filter (as passed to calendarQuery) matches
+ * the given object.
+ *
+ * @param array $object
+ * @param array $filters
+ * @return bool
+ */
+ protected function validateFilterForObject(array $object, array $filters) {
+
+ // Unfortunately, setting the 'calendardata' here is optional. If
+ // it was excluded, we actually need another call to get this as
+ // well.
+ if (!isset($object['calendardata'])) {
+ $object = $this->getCalendarObject($object['calendarid'], $object['uri']);
+ }
+
+ $vObject = VObject\Reader::read($object['calendardata']);
+
+ $validator = new CalDAV\CalendarQueryValidator();
+ $result = $validator->validate($vObject, $filters);
+
+ // Destroy circular references so PHP will GC the object.
+ $vObject->destroy();
+
+ return $result;
+
+ }
+
+ /**
+ * Searches through all of a users calendars and calendar objects to find
+ * an object with a specific UID.
+ *
+ * This method should return the path to this object, relative to the
+ * calendar home, so this path usually only contains two parts:
+ *
+ * calendarpath/objectpath.ics
+ *
+ * If the uid is not found, return null.
+ *
+ * This method should only consider * objects that the principal owns, so
+ * any calendars owned by other principals that also appear in this
+ * collection should be ignored.
+ *
+ * @param string $principalUri
+ * @param string $uid
+ * @return string|null
+ */
+ function getCalendarObjectByUID($principalUri, $uid) {
+
+ // Note: this is a super slow naive implementation of this method. You
+ // are highly recommended to optimize it, if your backend allows it.
+ foreach ($this->getCalendarsForUser($principalUri) as $calendar) {
+
+ // We must ignore calendars owned by other principals.
+ if ($calendar['principaluri'] !== $principalUri) {
+ continue;
+ }
+
+ // Ignore calendars that are shared.
+ if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) {
+ continue;
+ }
+
+ $results = $this->calendarQuery(
+ $calendar['id'],
+ [
+ 'name' => 'VCALENDAR',
+ 'prop-filters' => [],
+ 'comp-filters' => [
+ [
+ 'name' => 'VEVENT',
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ 'comp-filters' => [],
+ 'prop-filters' => [
+ [
+ 'name' => 'UID',
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ 'text-match' => [
+ 'value' => $uid,
+ 'negate-condition' => false,
+ 'collation' => 'i;octet',
+ ],
+ 'param-filters' => [],
+ ],
+ ]
+ ]
+ ],
+ ]
+ );
+ if ($results) {
+ // We have a match
+ return $calendar['uri'] . '/' . $results[0];
+ }
+
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
new file mode 100644
index 000000000..7513fb60d
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php
@@ -0,0 +1,268 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+/**
+ * Every CalDAV backend must at least implement this interface.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface BackendInterface {
+
+ /**
+ * Returns a list of calendars for a principal.
+ *
+ * Every project is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * calendar. This can be the same as the uri or a database key.
+ * * uri, which is the basename of the uri with which the calendar is
+ * accessed.
+ * * principaluri. The owner of the calendar. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore it can contain webdav properties in clark notation. A very
+ * common one is '{DAV:}displayname'.
+ *
+ * Many clients also require:
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
+ * For this property, you can just return an instance of
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
+ *
+ * If you return {http://sabredav.org/ns}read-only and set the value to 1,
+ * ACL will automatically be put in read-only mode.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getCalendarsForUser($principalUri);
+
+ /**
+ * Creates a new calendar for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to
+ * reference this calendar in other methods, such as updateCalendar.
+ *
+ * @param string $principalUri
+ * @param string $calendarUri
+ * @param array $properties
+ * @return void
+ */
+ function createCalendar($principalUri, $calendarUri, array $properties);
+
+ /**
+ * Updates properties for a calendar.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documentation for more info and examples.
+ *
+ * @param string $path
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);
+
+ /**
+ * Delete a calendar and all its objects
+ *
+ * @param mixed $calendarId
+ * @return void
+ */
+ function deleteCalendar($calendarId);
+
+ /**
+ * Returns all calendar objects within a calendar.
+ *
+ * Every item contains an array with the following keys:
+ * * calendardata - The iCalendar-compatible calendar data
+ * * uri - a unique key which will be used to construct the uri. This can
+ * be any arbitrary string, but making sure it ends with '.ics' is a
+ * good idea. This is only the basename, or filename, not the full
+ * path.
+ * * lastmodified - a timestamp of the last modification time
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+ * '"abcdef"')
+ * * size - The size of the calendar objects, in bytes.
+ * * component - optional, a string containing the type of object, such
+ * as 'vevent' or 'vtodo'. If specified, this will be used to populate
+ * the Content-Type header.
+ *
+ * Note that the etag is optional, but it's highly encouraged to return for
+ * speed reasons.
+ *
+ * The calendardata is also optional. If it's not returned
+ * 'getCalendarObject' will be called later, which *is* expected to return
+ * calendardata.
+ *
+ * If neither etag or size are specified, the calendardata will be
+ * used/fetched to determine these numbers. If both are specified the
+ * amount of times this is needed is reduced by a great degree.
+ *
+ * @param mixed $calendarId
+ * @return array
+ */
+ function getCalendarObjects($calendarId);
+
+ /**
+ * Returns information from a single calendar object, based on it's object
+ * uri.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * The returned array must have the same keys as getCalendarObjects. The
+ * 'calendardata' object is required here though, while it's not required
+ * for getCalendarObjects.
+ *
+ * This method must return null if the object did not exist.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @return array|null
+ */
+ function getCalendarObject($calendarId, $objectUri);
+
+ /**
+ * Returns a list of calendar objects.
+ *
+ * This method should work identical to getCalendarObject, but instead
+ * return all the calendar objects in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $calendarId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCalendarObjects($calendarId, array $uris);
+
+ /**
+ * Creates a new calendar object.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * It is possible to return an etag from this function, which will be used
+ * in the response to this PUT request. Note that the ETag must be
+ * surrounded by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function createCalendarObject($calendarId, $objectUri, $calendarData);
+
+ /**
+ * Updates an existing calendarobject, based on it's uri.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function updateCalendarObject($calendarId, $objectUri, $calendarData);
+
+ /**
+ * Deletes an existing calendar object.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @return void
+ */
+ function deleteCalendarObject($calendarId, $objectUri);
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by Sabre\CalDAV\CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on either VEVENT or VTODO.
+ *
+ * ..and combinations of these requests. It may not be worth it to try to
+ * handle every possible situation and just rely on the (relatively
+ * easy to use) CalendarQueryValidator to handle the rest.
+ *
+ * Note that especially time-range-filters may be difficult to parse. A
+ * time-range filter specified on a VEVENT must for instance also handle
+ * recurrence rules correctly.
+ * A good example of how to interprete all these filters can also simply
+ * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
+ * as possible, so it gives you a good idea on what type of stuff you need
+ * to think of.
+ *
+ * @param mixed $calendarId
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery($calendarId, array $filters);
+
+ /**
+ * Searches through all of a users calendars and calendar objects to find
+ * an object with a specific UID.
+ *
+ * This method should return the path to this object, relative to the
+ * calendar home, so this path usually only contains two parts:
+ *
+ * calendarpath/objectpath.ics
+ *
+ * If the uid is not found, return null.
+ *
+ * This method should only consider * objects that the principal owns, so
+ * any calendars owned by other principals that also appear in this
+ * collection should be ignored.
+ *
+ * @param string $principalUri
+ * @param string $uid
+ * @return string|null
+ */
+ function getCalendarObjectByUID($principalUri, $uid);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
new file mode 100644
index 000000000..19b9b22a7
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+use Sabre\CalDAV\Xml\Notification\NotificationInterface;
+
+/**
+ * Adds caldav notification support to a backend.
+ *
+ * Note: This feature is experimental, and may change in between different
+ * SabreDAV versions.
+ *
+ * Notifications are defined at:
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
+ *
+ * These notifications are basically a list of server-generated notifications
+ * displayed to the user. Users can dismiss notifications by deleting them.
+ *
+ * The primary usecase is to allow for calendar-sharing.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface NotificationSupport extends BackendInterface {
+
+ /**
+ * Returns a list of notifications for a given principal url.
+ *
+ * @param string $principalUri
+ * @return NotificationInterface[]
+ */
+ function getNotificationsForPrincipal($principalUri);
+
+ /**
+ * This deletes a specific notifcation.
+ *
+ * This may be called by a client once it deems a notification handled.
+ *
+ * @param string $principalUri
+ * @param NotificationInterface $notification
+ * @return void
+ */
+ function deleteNotification($principalUri, NotificationInterface $notification);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php b/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
new file mode 100644
index 000000000..76b69dc6e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php
@@ -0,0 +1,1210 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+use Sabre\VObject;
+use Sabre\CalDAV;
+use Sabre\DAV;
+use Sabre\DAV\Exception\Forbidden;
+
+/**
+ * PDO CalDAV backend
+ *
+ * This backend is used to store calendar-data in a PDO database, such as
+ * sqlite or MySQL
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
+
+ /**
+ * We need to specify a max date, because we need to stop *somewhere*
+ *
+ * On 32 bit system the maximum for a signed integer is 2147483647, so
+ * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
+ * in 2038-01-19 to avoid problems when the date is converted
+ * to a unix timestamp.
+ */
+ const MAX_DATE = '2038-01-01';
+
+ /**
+ * pdo
+ *
+ * @var \PDO
+ */
+ protected $pdo;
+
+ /**
+ * The table name that will be used for calendars
+ *
+ * @var string
+ */
+ public $calendarTableName = 'calendars';
+
+ /**
+ * The table name that will be used for calendar objects
+ *
+ * @var string
+ */
+ public $calendarObjectTableName = 'calendarobjects';
+
+ /**
+ * The table name that will be used for tracking changes in calendars.
+ *
+ * @var string
+ */
+ public $calendarChangesTableName = 'calendarchanges';
+
+ /**
+ * The table name that will be used inbox items.
+ *
+ * @var string
+ */
+ public $schedulingObjectTableName = 'schedulingobjects';
+
+ /**
+ * The table name that will be used for calendar subscriptions.
+ *
+ * @var string
+ */
+ public $calendarSubscriptionsTableName = 'calendarsubscriptions';
+
+ /**
+ * List of CalDAV properties, and how they map to database fieldnames
+ * Add your own properties by simply adding on to this array.
+ *
+ * Note that only string-based properties are supported here.
+ *
+ * @var array
+ */
+ public $propertyMap = [
+ '{DAV:}displayname' => 'displayname',
+ '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
+ '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ ];
+
+ /**
+ * List of subscription properties, and how they map to database fieldnames.
+ *
+ * @var array
+ */
+ public $subscriptionPropertyMap = [
+ '{DAV:}displayname' => 'displayname',
+ '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
+ '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
+ '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
+ '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
+ '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
+ ];
+
+ /**
+ * Creates the backend
+ *
+ * @param \PDO $pdo
+ */
+ function __construct(\PDO $pdo) {
+
+ $this->pdo = $pdo;
+
+ }
+
+ /**
+ * Returns a list of calendars for a principal.
+ *
+ * Every project is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * calendar. This can be the same as the uri or a database key.
+ * * uri. This is just the 'base uri' or 'filename' of the calendar.
+ * * principaluri. The owner of the calendar. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore it can contain webdav properties in clark notation. A very
+ * common one is '{DAV:}displayname'.
+ *
+ * Many clients also require:
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
+ * For this property, you can just return an instance of
+ * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
+ *
+ * If you return {http://sabredav.org/ns}read-only and set the value to 1,
+ * ACL will automatically be put in read-only mode.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getCalendarsForUser($principalUri) {
+
+ $fields = array_values($this->propertyMap);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'synctoken';
+ $fields[] = 'components';
+ $fields[] = 'principaluri';
+ $fields[] = 'transparent';
+
+ // Making fields a comma-delimited list
+ $fields = implode(', ', $fields);
+ $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
+ $stmt->execute([$principalUri]);
+
+ $calendars = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $components = [];
+ if ($row['components']) {
+ $components = explode(',', $row['components']);
+ }
+
+ $calendar = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
+ '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
+ '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
+ ];
+
+
+ foreach ($this->propertyMap as $xmlName => $dbName) {
+ $calendar[$xmlName] = $row[$dbName];
+ }
+
+ $calendars[] = $calendar;
+
+ }
+
+ return $calendars;
+
+ }
+
+ /**
+ * Creates a new calendar for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used
+ * to reference this calendar in other methods, such as updateCalendar.
+ *
+ * @param string $principalUri
+ * @param string $calendarUri
+ * @param array $properties
+ * @return string
+ */
+ function createCalendar($principalUri, $calendarUri, array $properties) {
+
+ $fieldNames = [
+ 'principaluri',
+ 'uri',
+ 'synctoken',
+ 'transparent',
+ ];
+ $values = [
+ ':principaluri' => $principalUri,
+ ':uri' => $calendarUri,
+ ':synctoken' => 1,
+ ':transparent' => 0,
+ ];
+
+ // Default value
+ $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+ $fieldNames[] = 'components';
+ if (!isset($properties[$sccs])) {
+ $values[':components'] = 'VEVENT,VTODO';
+ } else {
+ if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
+ throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
+ }
+ $values[':components'] = implode(',', $properties[$sccs]->getValue());
+ }
+ $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
+ if (isset($properties[$transp])) {
+ $values[':transparent'] = $properties[$transp]->getValue() === 'transparent';
+ }
+
+ foreach ($this->propertyMap as $xmlName => $dbName) {
+ if (isset($properties[$xmlName])) {
+
+ $values[':' . $dbName] = $properties[$xmlName];
+ $fieldNames[] = $dbName;
+ }
+ }
+
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
+ $stmt->execute($values);
+
+ return $this->pdo->lastInsertId();
+
+ }
+
+ /**
+ * Updates properties for a calendar.
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documenation for more info and examples.
+ *
+ * @param string $calendarId
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
+
+ $supportedProperties = array_keys($this->propertyMap);
+ $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
+
+ $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
+ $newValues = [];
+ foreach ($mutations as $propertyName => $propertyValue) {
+
+ switch ($propertyName) {
+ case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
+ $fieldName = 'transparent';
+ $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
+ break;
+ default :
+ $fieldName = $this->propertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+ break;
+ }
+
+ }
+ $valuesSql = [];
+ foreach ($newValues as $fieldName => $value) {
+ $valuesSql[] = $fieldName . ' = ?';
+ }
+
+ $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
+ $newValues['id'] = $calendarId;
+ $stmt->execute(array_values($newValues));
+
+ $this->addChange($calendarId, "", 2);
+
+ return true;
+
+ });
+
+ }
+
+ /**
+ * Delete a calendar and all it's objects
+ *
+ * @param string $calendarId
+ * @return void
+ */
+ function deleteCalendar($calendarId) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
+ $stmt->execute([$calendarId]);
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ }
+
+ /**
+ * Returns all calendar objects within a calendar.
+ *
+ * Every item contains an array with the following keys:
+ * * calendardata - The iCalendar-compatible calendar data
+ * * uri - a unique key which will be used to construct the uri. This can
+ * be any arbitrary string, but making sure it ends with '.ics' is a
+ * good idea. This is only the basename, or filename, not the full
+ * path.
+ * * lastmodified - a timestamp of the last modification time
+ * * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
+ * ' "abcdef"')
+ * * size - The size of the calendar objects, in bytes.
+ * * component - optional, a string containing the type of object, such
+ * as 'vevent' or 'vtodo'. If specified, this will be used to populate
+ * the Content-Type header.
+ *
+ * Note that the etag is optional, but it's highly encouraged to return for
+ * speed reasons.
+ *
+ * The calendardata is also optional. If it's not returned
+ * 'getCalendarObject' will be called later, which *is* expected to return
+ * calendardata.
+ *
+ * If neither etag or size are specified, the calendardata will be
+ * used/fetched to determine these numbers. If both are specified the
+ * amount of times this is needed is reduced by a great degree.
+ *
+ * @param string $calendarId
+ * @return array
+ */
+ function getCalendarObjects($calendarId) {
+
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
+ $stmt->execute([$calendarId]);
+
+ $result = [];
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * Returns information from a single calendar object, based on it's object
+ * uri.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * The returned array must have the same keys as getCalendarObjects. The
+ * 'calendardata' object is required here though, while it's not required
+ * for getCalendarObjects.
+ *
+ * This method must return null if the object did not exist.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @return array|null
+ */
+ function getCalendarObject($calendarId, $objectUri) {
+
+ $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarId, $objectUri]);
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$row) return null;
+
+ return [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'calendardata' => $row['calendardata'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+
+ }
+
+ /**
+ * Returns a list of calendar objects.
+ *
+ * This method should work identical to getCalendarObject, but instead
+ * return all the calendar objects in the list as an array.
+ *
+ * If the backend supports this, it may allow for some speed-ups.
+ *
+ * @param mixed $calendarId
+ * @param array $uris
+ * @return array
+ */
+ function getMultipleCalendarObjects($calendarId, array $uris) {
+
+ $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
+ // Inserting a whole bunch of question marks
+ $query .= implode(',', array_fill(0, count($uris), '?'));
+ $query .= ')';
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute(array_merge([$calendarId], $uris));
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'calendardata' => $row['calendardata'],
+ 'component' => strtolower($row['componenttype']),
+ ];
+
+ }
+ return $result;
+
+ }
+
+
+ /**
+ * Creates a new calendar object.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function createCalendarObject($calendarId, $objectUri, $calendarData) {
+
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
+ $stmt->execute([
+ $calendarId,
+ $objectUri,
+ $calendarData,
+ time(),
+ $extraData['etag'],
+ $extraData['size'],
+ $extraData['componentType'],
+ $extraData['firstOccurence'],
+ $extraData['lastOccurence'],
+ $extraData['uid'],
+ ]);
+ $this->addChange($calendarId, $objectUri, 1);
+
+ return '"' . $extraData['etag'] . '"';
+
+ }
+
+ /**
+ * Updates an existing calendarobject, based on it's uri.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * It is possible return an etag from this function, which will be used in
+ * the response to this PUT request. Note that the ETag must be surrounded
+ * by double-quotes.
+ *
+ * However, you should only really return this ETag if you don't mangle the
+ * calendar-data. If the result of a subsequent GET to this object is not
+ * the exact same as this request body, you should omit the ETag.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param string $calendarData
+ * @return string|null
+ */
+ function updateCalendarObject($calendarId, $objectUri, $calendarData) {
+
+ $extraData = $this->getDenormalizedData($calendarData);
+
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);
+
+ $this->addChange($calendarId, $objectUri, 2);
+
+ return '"' . $extraData['etag'] . '"';
+
+ }
+
+ /**
+ * Parses some information from calendar objects, used for optimized
+ * calendar-queries.
+ *
+ * Returns an array with the following keys:
+ * * etag - An md5 checksum of the object without the quotes.
+ * * size - Size of the object in bytes
+ * * componentType - VEVENT, VTODO or VJOURNAL
+ * * firstOccurence
+ * * lastOccurence
+ * * uid - value of the UID property
+ *
+ * @param string $calendarData
+ * @return array
+ */
+ protected function getDenormalizedData($calendarData) {
+
+ $vObject = VObject\Reader::read($calendarData);
+ $componentType = null;
+ $component = null;
+ $firstOccurence = null;
+ $lastOccurence = null;
+ $uid = null;
+ foreach ($vObject->getComponents() as $component) {
+ if ($component->name !== 'VTIMEZONE') {
+ $componentType = $component->name;
+ $uid = (string)$component->UID;
+ break;
+ }
+ }
+ if (!$componentType) {
+ throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+ }
+ if ($componentType === 'VEVENT') {
+ $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
+ // Finding the last occurence is a bit harder
+ if (!isset($component->RRULE)) {
+ if (isset($component->DTEND)) {
+ $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
+ } elseif (isset($component->DURATION)) {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
+ $lastOccurence = $endDate->getTimeStamp();
+ } elseif (!$component->DTSTART->hasTime()) {
+ $endDate = clone $component->DTSTART->getDateTime();
+ $endDate = $endDate->modify('+1 day');
+ $lastOccurence = $endDate->getTimeStamp();
+ } else {
+ $lastOccurence = $firstOccurence;
+ }
+ } else {
+ $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
+ $maxDate = new \DateTime(self::MAX_DATE);
+ if ($it->isInfinite()) {
+ $lastOccurence = $maxDate->getTimeStamp();
+ } else {
+ $end = $it->getDtEnd();
+ while ($it->valid() && $end < $maxDate) {
+ $end = $it->getDtEnd();
+ $it->next();
+
+ }
+ $lastOccurence = $end->getTimeStamp();
+ }
+
+ }
+ }
+
+ // Destroy circular references to PHP will GC the object.
+ $vObject->destroy();
+
+ return [
+ 'etag' => md5($calendarData),
+ 'size' => strlen($calendarData),
+ 'componentType' => $componentType,
+ 'firstOccurence' => $firstOccurence,
+ 'lastOccurence' => $lastOccurence,
+ 'uid' => $uid,
+ ];
+
+ }
+
+ /**
+ * Deletes an existing calendar object.
+ *
+ * The object uri is only the basename, or filename and not a full path.
+ *
+ * @param string $calendarId
+ * @param string $objectUri
+ * @return void
+ */
+ function deleteCalendarObject($calendarId, $objectUri) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
+ $stmt->execute([$calendarId, $objectUri]);
+
+ $this->addChange($calendarId, $objectUri, 3);
+
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
+ *
+ * Note that it is extremely likely that getCalendarObject for every path
+ * returned from this method will be called almost immediately after. You
+ * may want to anticipate this to speed up these requests.
+ *
+ * This method provides a default implementation, which parses *all* the
+ * iCalendar objects in the specified calendar.
+ *
+ * This default may well be good enough for personal use, and calendars
+ * that aren't very large. But if you anticipate high usage, big calendars
+ * or high loads, you are strongly adviced to optimize certain paths.
+ *
+ * The best way to do so is override this method and to optimize
+ * specifically for 'common filters'.
+ *
+ * Requests that are extremely common are:
+ * * requests for just VEVENTS
+ * * requests for just VTODO
+ * * requests with a time-range-filter on a VEVENT.
+ *
+ * ..and combinations of these requests. It may not be worth it to try to
+ * handle every possible situation and just rely on the (relatively
+ * easy to use) CalendarQueryValidator to handle the rest.
+ *
+ * Note that especially time-range-filters may be difficult to parse. A
+ * time-range filter specified on a VEVENT must for instance also handle
+ * recurrence rules correctly.
+ * A good example of how to interprete all these filters can also simply
+ * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
+ * as possible, so it gives you a good idea on what type of stuff you need
+ * to think of.
+ *
+ * This specific implementation (for the PDO) backend optimizes filters on
+ * specific components, and VEVENT time-ranges.
+ *
+ * @param string $calendarId
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery($calendarId, array $filters) {
+
+ $componentType = null;
+ $requirePostFilter = true;
+ $timeRange = null;
+
+ // if no filters were specified, we don't need to filter after a query
+ if (!$filters['prop-filters'] && !$filters['comp-filters']) {
+ $requirePostFilter = false;
+ }
+
+ // Figuring out if there's a component filter
+ if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
+ $componentType = $filters['comp-filters'][0]['name'];
+
+ // Checking if we need post-filters
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
+ $requirePostFilter = false;
+ }
+ // There was a time-range filter
+ if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
+ $timeRange = $filters['comp-filters'][0]['time-range'];
+
+ // If start time OR the end time is not specified, we can do a
+ // 100% accurate mysql query.
+ if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
+ $requirePostFilter = false;
+ }
+ }
+
+ }
+
+ if ($requirePostFilter) {
+ $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
+ } else {
+ $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
+ }
+
+ $values = [
+ 'calendarid' => $calendarId,
+ ];
+
+ if ($componentType) {
+ $query .= " AND componenttype = :componenttype";
+ $values['componenttype'] = $componentType;
+ }
+
+ if ($timeRange && $timeRange['start']) {
+ $query .= " AND lastoccurence > :startdate";
+ $values['startdate'] = $timeRange['start']->getTimeStamp();
+ }
+ if ($timeRange && $timeRange['end']) {
+ $query .= " AND firstoccurence < :enddate";
+ $values['enddate'] = $timeRange['end']->getTimeStamp();
+ }
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute($values);
+
+ $result = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ if ($requirePostFilter) {
+ if (!$this->validateFilterForObject($row, $filters)) {
+ continue;
+ }
+ }
+ $result[] = $row['uri'];
+
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * Searches through all of a users calendars and calendar objects to find
+ * an object with a specific UID.
+ *
+ * This method should return the path to this object, relative to the
+ * calendar home, so this path usually only contains two parts:
+ *
+ * calendarpath/objectpath.ics
+ *
+ * If the uid is not found, return null.
+ *
+ * This method should only consider * objects that the principal owns, so
+ * any calendars owned by other principals that also appear in this
+ * collection should be ignored.
+ *
+ * @param string $principalUri
+ * @param string $uid
+ * @return string|null
+ */
+ function getCalendarObjectByUID($principalUri, $uid) {
+
+ $query = <<<SQL
+SELECT
+ calendars.uri AS calendaruri, calendarobjects.uri as objecturi
+FROM
+ $this->calendarObjectTableName AS calendarobjects
+LEFT JOIN
+ $this->calendarTableName AS calendars
+ ON calendarobjects.calendarid = calendars.id
+WHERE
+ calendars.principaluri = ?
+ AND
+ calendarobjects.uid = ?
+SQL;
+
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$principalUri, $uid]);
+
+ if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ return $row['calendaruri'] . '/' . $row['objecturi'];
+ }
+
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken in the specified calendar.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The returned syncToken property should reflect the *current* syncToken
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
+ * property this is needed here too, to ensure the operation is atomic.
+ *
+ * If the $syncToken argument is specified as null, this is an initial
+ * sync, and all members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
+ * 1, you only have to report changes that happened only directly in
+ * immediate descendants. If it's 2, it should also include changes from
+ * the nodes below the child collections. (grandchildren)
+ *
+ * The $limit argument allows a client to specify how many results should
+ * be returned at most. If the limit is not specified, it should be treated
+ * as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $calendarId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
+
+ // Current synctoken
+ $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
+ $stmt->execute([ $calendarId ]);
+ $currentToken = $stmt->fetchColumn(0);
+
+ if (is_null($currentToken)) return null;
+
+ $result = [
+ 'syncToken' => $currentToken,
+ 'added' => [],
+ 'modified' => [],
+ 'deleted' => [],
+ ];
+
+ if ($syncToken) {
+
+ $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
+ if ($limit > 0) $query .= " LIMIT " . (int)$limit;
+
+ // Fetching all changes
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$syncToken, $currentToken, $calendarId]);
+
+ $changes = [];
+
+ // This loop ensures that any duplicates are overwritten, only the
+ // last change on a node is relevant.
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $changes[$row['uri']] = $row['operation'];
+
+ }
+
+ foreach ($changes as $uri => $operation) {
+
+ switch ($operation) {
+ case 1 :
+ $result['added'][] = $uri;
+ break;
+ case 2 :
+ $result['modified'][] = $uri;
+ break;
+ case 3 :
+ $result['deleted'][] = $uri;
+ break;
+ }
+
+ }
+ } else {
+ // No synctoken supplied, this is the initial sync.
+ $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?";
+ $stmt = $this->pdo->prepare($query);
+ $stmt->execute([$calendarId]);
+
+ $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
+ }
+ return $result;
+
+ }
+
+ /**
+ * Adds a change record to the calendarchanges table.
+ *
+ * @param mixed $calendarId
+ * @param string $objectUri
+ * @param int $operation 1 = add, 2 = modify, 3 = delete.
+ * @return void
+ */
+ protected function addChange($calendarId, $objectUri, $operation) {
+
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
+ $stmt->execute([
+ $objectUri,
+ $calendarId,
+ $operation,
+ $calendarId
+ ]);
+ $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
+ $stmt->execute([
+ $calendarId
+ ]);
+
+ }
+
+ /**
+ * Returns a list of subscriptions for a principal.
+ *
+ * Every subscription is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * subscription. This can be the same as the uri or a database key.
+ * * uri. This is just the 'base uri' or 'filename' of the subscription.
+ * * principaluri. The owner of the subscription. Almost always the same as
+ * principalUri passed to this method.
+ * * source. Url to the actual feed
+ *
+ * Furthermore, all the subscription info must be returned too:
+ *
+ * 1. {DAV:}displayname
+ * 2. {http://apple.com/ns/ical/}refreshrate
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
+ * should not be stripped).
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
+ * should not be stripped).
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
+ * attachments should not be stripped).
+ * 7. {http://apple.com/ns/ical/}calendar-color
+ * 8. {http://apple.com/ns/ical/}calendar-order
+ * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
+ * (should just be an instance of
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
+ * default components).
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getSubscriptionsForUser($principalUri) {
+
+ $fields = array_values($this->subscriptionPropertyMap);
+ $fields[] = 'id';
+ $fields[] = 'uri';
+ $fields[] = 'source';
+ $fields[] = 'principaluri';
+ $fields[] = 'lastmodified';
+
+ // Making fields a comma-delimited list
+ $fields = implode(', ', $fields);
+ $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
+ $stmt->execute([$principalUri]);
+
+ $subscriptions = [];
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+
+ $subscription = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'principaluri' => $row['principaluri'],
+ 'source' => $row['source'],
+ 'lastmodified' => $row['lastmodified'],
+
+ '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
+ ];
+
+ foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
+ if (!is_null($row[$dbName])) {
+ $subscription[$xmlName] = $row[$dbName];
+ }
+ }
+
+ $subscriptions[] = $subscription;
+
+ }
+
+ return $subscriptions;
+
+ }
+
+ /**
+ * Creates a new subscription for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this subscription in other methods, such as updateSubscription.
+ *
+ * @param string $principalUri
+ * @param string $uri
+ * @param array $properties
+ * @return mixed
+ */
+ function createSubscription($principalUri, $uri, array $properties) {
+
+ $fieldNames = [
+ 'principaluri',
+ 'uri',
+ 'source',
+ 'lastmodified',
+ ];
+
+ if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
+ throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
+ }
+
+ $values = [
+ ':principaluri' => $principalUri,
+ ':uri' => $uri,
+ ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
+ ':lastmodified' => time(),
+ ];
+
+ foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
+ if (isset($properties[$xmlName])) {
+
+ $values[':' . $dbName] = $properties[$xmlName];
+ $fieldNames[] = $dbName;
+ }
+ }
+
+ $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
+ $stmt->execute($values);
+
+ return $this->pdo->lastInsertId();
+
+ }
+
+ /**
+ * Updates a subscription
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documenation for more info and examples.
+ *
+ * @param mixed $subscriptionId
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {
+
+ $supportedProperties = array_keys($this->subscriptionPropertyMap);
+ $supportedProperties[] = '{http://calendarserver.org/ns/}source';
+
+ $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
+
+ $newValues = [];
+
+ foreach ($mutations as $propertyName => $propertyValue) {
+
+ if ($propertyName === '{http://calendarserver.org/ns/}source') {
+ $newValues['source'] = $propertyValue->getHref();
+ } else {
+ $fieldName = $this->subscriptionPropertyMap[$propertyName];
+ $newValues[$fieldName] = $propertyValue;
+ }
+
+ }
+
+ // Now we're generating the sql query.
+ $valuesSql = [];
+ foreach ($newValues as $fieldName => $value) {
+ $valuesSql[] = $fieldName . ' = ?';
+ }
+
+ $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?");
+ $newValues['lastmodified'] = time();
+ $newValues['id'] = $subscriptionId;
+ $stmt->execute(array_values($newValues));
+
+ return true;
+
+ });
+
+ }
+
+ /**
+ * Deletes a subscription
+ *
+ * @param mixed $subscriptionId
+ * @return void
+ */
+ function deleteSubscription($subscriptionId) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?');
+ $stmt->execute([$subscriptionId]);
+
+ }
+
+ /**
+ * Returns a single scheduling object.
+ *
+ * The returned array should contain the following elements:
+ * * uri - A unique basename for the object. This will be used to
+ * construct a full uri.
+ * * calendardata - The iCalendar object
+ * * lastmodified - The last modification date. Can be an int for a unix
+ * timestamp, or a PHP DateTime object.
+ * * etag - A unique token that must change if the object changed.
+ * * size - The size of the object, in bytes.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @return array
+ */
+ function getSchedulingObject($principalUri, $objectUri) {
+
+ $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
+ $stmt->execute([$principalUri, $objectUri]);
+ $row = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+ if (!$row) return null;
+
+ return [
+ 'uri' => $row['uri'],
+ 'calendardata' => $row['calendardata'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'size' => (int)$row['size'],
+ ];
+
+ }
+
+ /**
+ * Returns all scheduling objects for the inbox collection.
+ *
+ * These objects should be returned as an array. Every item in the array
+ * should follow the same structure as returned from getSchedulingObject.
+ *
+ * The main difference is that 'calendardata' is optional.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getSchedulingObjects($principalUri) {
+
+ $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?');
+ $stmt->execute([$principalUri]);
+
+ $result = [];
+ foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
+ $result[] = [
+ 'calendardata' => $row['calendardata'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'size' => (int)$row['size'],
+ ];
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * Deletes a scheduling object
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @return void
+ */
+ function deleteSchedulingObject($principalUri, $objectUri) {
+
+ $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
+ $stmt->execute([$principalUri, $objectUri]);
+
+ }
+
+ /**
+ * Creates a new scheduling object. This should land in a users' inbox.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @param string $objectData
+ * @return void
+ */
+ function createSchedulingObject($principalUri, $objectUri, $objectData) {
+
+ $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
+ $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData) ]);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php
new file mode 100644
index 000000000..6ec0bf06b
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+/**
+ * Implementing this interface adds CalDAV Scheduling support to your caldav
+ * server, as defined in rfc6638.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface SchedulingSupport extends BackendInterface {
+
+ /**
+ * Returns a single scheduling object for the inbox collection.
+ *
+ * The returned array should contain the following elements:
+ * * uri - A unique basename for the object. This will be used to
+ * construct a full uri.
+ * * calendardata - The iCalendar object
+ * * lastmodified - The last modification date. Can be an int for a unix
+ * timestamp, or a PHP DateTime object.
+ * * etag - A unique token that must change if the object changed.
+ * * size - The size of the object, in bytes.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @return array
+ */
+ function getSchedulingObject($principalUri, $objectUri);
+
+ /**
+ * Returns all scheduling objects for the inbox collection.
+ *
+ * These objects should be returned as an array. Every item in the array
+ * should follow the same structure as returned from getSchedulingObject.
+ *
+ * The main difference is that 'calendardata' is optional.
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getSchedulingObjects($principalUri);
+
+ /**
+ * Deletes a scheduling object from the inbox collection.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @return void
+ */
+ function deleteSchedulingObject($principalUri, $objectUri);
+
+ /**
+ * Creates a new scheduling object. This should land in a users' inbox.
+ *
+ * @param string $principalUri
+ * @param string $objectUri
+ * @param string $objectData
+ * @return void
+ */
+ function createSchedulingObject($principalUri, $objectUri, $objectData);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
new file mode 100644
index 000000000..6a11b0ab1
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php
@@ -0,0 +1,243 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+/**
+ * Adds support for sharing features to a CalDAV server.
+ *
+ * Note: This feature is experimental, and may change in between different
+ * SabreDAV versions.
+ *
+ * Early warning: Currently SabreDAV provides no implementation for this. This
+ * is, because in it's current state there is no elegant way to do this.
+ * The problem lies in the fact that a real CalDAV server with sharing support
+ * would first need email support (with invite notifications), and really also
+ * a browser-frontend that allows people to accept or reject these shares.
+ *
+ * In addition, the CalDAV backends are currently kept as independent as
+ * possible, and should not be aware of principals, email addresses or
+ * accounts.
+ *
+ * Adding an implementation for Sharing to standard-sabredav would contradict
+ * these goals, so for this reason this is currently not implemented, although
+ * it may very well in the future; but probably not before SabreDAV 2.0.
+ *
+ * The interface works however, so if you implement all this, and do it
+ * correctly sharing _will_ work. It's not particularly easy, and I _urge you_
+ * to make yourself acquainted with the following document first:
+ *
+ * https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
+ *
+ * An overview
+ * ===========
+ *
+ * Implementing this interface will allow a user to share his or her calendars
+ * to other users. Effectively, when a calendar is shared the calendar will
+ * show up in both the Sharer's and Sharee's calendar-home root.
+ * This interface adds a few methods that ensure that this happens, and there
+ * are also a number of new requirements in the base-class you must now follow.
+ *
+ *
+ * How it works
+ * ============
+ *
+ * When a user shares a calendar, the updateShares() method will be called with
+ * a list of sharees that are now added, and a list of sharees that have been
+ * removed.
+ * Removal is instant, but when a sharee is added the sharee first gets a
+ * chance to accept or reject the invitation for a share.
+ *
+ * After a share is accepted, the calendar will be returned from
+ * getUserCalendars for both the sharer, and the sharee.
+ *
+ * If the sharee deletes the calendar, only their share gets deleted. When the
+ * owner deletes a calendar, it will be removed for everybody.
+ *
+ *
+ * Notifications
+ * =============
+ *
+ * During all these sharing operations, a lot of notifications are sent back
+ * and forward.
+ *
+ * Whenever the list of sharees for a calendar has been changed (they have been
+ * added, removed or modified) all sharees should get a notification for this
+ * change.
+ * This notification is always represented by:
+ *
+ * Sabre\CalDAV\Notifications\Notification\Invite
+ *
+ * In the case of an invite, the sharee may reply with an 'accept' or
+ * 'decline'. These are always represented by:
+ *
+ * Sabre\CalDAV\Notifications\Notification\InviteReply
+ *
+ *
+ * Calendar access by sharees
+ * ==========================
+ *
+ * As mentioned earlier, shared calendars must now also be returned for
+ * getCalendarsForUser for sharees. A few things change though.
+ *
+ * The following properties must be specified:
+ *
+ * 1. {http://calendarserver.org/ns/}shared-url
+ *
+ * This property MUST contain the url to the original calendar, that is.. the
+ * path to the calendar from the owner.
+ *
+ * 2. {http://sabredav.org/ns}owner-principal
+ *
+ * This is a url to to the principal who is sharing the calendar.
+ *
+ * 3. {http://sabredav.org/ns}read-only
+ *
+ * This should be either 0 or 1, depending on if the user has read-only or
+ * read-write access to the calendar.
+ *
+ * Only when this is done, the calendar will correctly be marked as a calendar
+ * that's shared to him, thus allowing clients to display the correct interface
+ * and ACL enforcement.
+ *
+ * If a sharee deletes their calendar, only their instance of the calendar
+ * should be deleted, the original should still exists.
+ * Pretty much any 'dead' WebDAV properties on these shared calendars should be
+ * specific to a user. This means that if the displayname is changed by a
+ * sharee, the original is not affected. This is also true for:
+ * * The description
+ * * The color
+ * * The order
+ * * And any other dead properties.
+ *
+ * Properties like a ctag should not be different for multiple instances of the
+ * calendar.
+ *
+ * Lastly, objects *within* calendars should also have user-specific data. The
+ * two things that are user-specific are:
+ * * VALARM objects
+ * * The TRANSP property
+ *
+ * This _also_ implies that if a VALARM is deleted by a sharee for some event,
+ * this has no effect on the original VALARM.
+ *
+ * Understandably, the this last requirement is one of the hardest.
+ * Realisticly, I can see people ignoring this part of the spec, but that could
+ * cause a different set of issues.
+ *
+ *
+ * Publishing
+ * ==========
+ *
+ * When a user publishes a url, the server should generate a 'publish url'.
+ * This is a read-only url, anybody can use to consume the calendar feed.
+ *
+ * Calendars are in one of two states:
+ * * published
+ * * unpublished
+ *
+ * If a calendar is published, the following property should be returned
+ * for each calendar in getCalendarsForUser.
+ *
+ * {http://calendarserver.org/ns/}publish-url
+ *
+ * This element should contain a {DAV:}href element, which points to the
+ * public url that does not require authentication. Unlike every other href,
+ * this url must be absolute.
+ *
+ * Ideally, the following property is always returned
+ *
+ * {http://calendarserver.org/ns/}pre-publish-url
+ *
+ * This property should contain the url that the calendar _would_ have, if it
+ * were to be published. iCal uses this to display the url, before the user
+ * will actually publish it.
+ *
+ *
+ * Selectively disabling publish or share feature
+ * ==============================================
+ *
+ * If Sabre\CalDAV\Property\AllowedSharingModes is returned from
+ * getCalendarsForUser, this allows the server to specify whether either sharing,
+ * or publishing is supported.
+ *
+ * This allows a client to determine in advance which features are available,
+ * and update the interface appropriately. If this property is not returned by
+ * the backend, the SharingPlugin automatically injects it and assumes both
+ * features are available.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface SharingSupport extends NotificationSupport {
+
+ /**
+ * Updates the list of shares.
+ *
+ * The first array is a list of people that are to be added to the
+ * calendar.
+ *
+ * Every element in the add array has the following properties:
+ * * href - A url. Usually a mailto: address
+ * * commonName - Usually a first and last name, or false
+ * * summary - A description of the share, can also be false
+ * * readOnly - A boolean value
+ *
+ * Every element in the remove array is just the address string.
+ *
+ * Note that if the calendar is currently marked as 'not shared' by and
+ * this method is called, the calendar should be 'upgraded' to a shared
+ * calendar.
+ *
+ * @param mixed $calendarId
+ * @param array $add
+ * @param array $remove
+ * @return void
+ */
+ function updateShares($calendarId, array $add, array $remove);
+
+ /**
+ * Returns the list of people whom this calendar is shared with.
+ *
+ * Every element in this array should have the following properties:
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first + last name
+ * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
+ * * readOnly - boolean
+ * * summary - Optional, a description for the share
+ *
+ * This method may be called by either the original instance of the
+ * calendar, as well as the shared instances. In the case of the shared
+ * instances, it is perfectly acceptable to return an empty array in case
+ * there are privacy concerns.
+ *
+ * @param mixed $calendarId
+ * @return array
+ */
+ function getShares($calendarId);
+
+ /**
+ * This method is called when a user replied to a request to share.
+ *
+ * If the user chose to accept the share, this method should return the
+ * newly created calendar url.
+ *
+ * @param string href The sharee who is replying (often a mailto: address)
+ * @param int status One of the SharingPlugin::STATUS_* constants
+ * @param string $calendarUri The url to the calendar thats being shared
+ * @param string $inReplyTo The unique id this message is a response to
+ * @param string $summary A description of the reply
+ * @return null|string
+ */
+ function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
+
+ /**
+ * Publishes a calendar
+ *
+ * @param mixed $calendarId
+ * @param bool $value
+ * @return void
+ */
+ function setPublishStatus($calendarId, $value);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php
new file mode 100644
index 000000000..a39289f5e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+use Sabre\DAV;
+
+/**
+ * Every CalDAV backend must at least implement this interface.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface SubscriptionSupport extends BackendInterface {
+
+ /**
+ * Returns a list of subscriptions for a principal.
+ *
+ * Every subscription is an array with the following keys:
+ * * id, a unique id that will be used by other functions to modify the
+ * subscription. This can be the same as the uri or a database key.
+ * * uri. This is just the 'base uri' or 'filename' of the subscription.
+ * * principaluri. The owner of the subscription. Almost always the same as
+ * principalUri passed to this method.
+ *
+ * Furthermore, all the subscription info must be returned too:
+ *
+ * 1. {DAV:}displayname
+ * 2. {http://apple.com/ns/ical/}refreshrate
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
+ * should not be stripped).
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
+ * should not be stripped).
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
+ * attachments should not be stripped).
+ * 6. {http://calendarserver.org/ns/}source (Must be a
+ * Sabre\DAV\Property\Href).
+ * 7. {http://apple.com/ns/ical/}calendar-color
+ * 8. {http://apple.com/ns/ical/}calendar-order
+ * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
+ * (should just be an instance of
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
+ * default components).
+ *
+ * @param string $principalUri
+ * @return array
+ */
+ function getSubscriptionsForUser($principalUri);
+
+ /**
+ * Creates a new subscription for a principal.
+ *
+ * If the creation was a success, an id must be returned that can be used to reference
+ * this subscription in other methods, such as updateSubscription.
+ *
+ * @param string $principalUri
+ * @param string $uri
+ * @param array $properties
+ * @return mixed
+ */
+ function createSubscription($principalUri, $uri, array $properties);
+
+ /**
+ * Updates a subscription
+ *
+ * The list of mutations is stored in a Sabre\DAV\PropPatch object.
+ * To do the actual updates, you must tell this object which properties
+ * you're going to process with the handle() method.
+ *
+ * Calling the handle method is like telling the PropPatch object "I
+ * promise I can handle updating this property".
+ *
+ * Read the PropPatch documenation for more info and examples.
+ *
+ * @param mixed $subscriptionId
+ * @param \Sabre\DAV\PropPatch $propPatch
+ * @return void
+ */
+ function updateSubscription($subscriptionId, DAV\PropPatch $propPatch);
+
+ /**
+ * Deletes a subscription.
+ *
+ * @param mixed $subscriptionId
+ * @return void
+ */
+ function deleteSubscription($subscriptionId);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php
new file mode 100644
index 000000000..a934505f9
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Sabre\CalDAV\Backend;
+
+/**
+ * WebDAV-sync support for CalDAV backends.
+ *
+ * In order for backends to advertise support for WebDAV-sync, this interface
+ * must be implemented.
+ *
+ * Implementing this can result in a significant reduction of bandwidth and CPU
+ * time.
+ *
+ * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
+ * property from getCalendarsFromUser.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface SyncSupport extends BackendInterface {
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken in the specified calendar.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * );
+ *
+ * The returned syncToken property should reflect the *current* syncToken
+ * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
+ * property This is * needed here too, to ensure the operation is atomic.
+ *
+ * If the $syncToken argument is specified as null, this is an initial
+ * sync, and all members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The $syncLevel argument is basically the 'depth' of the report. If it's
+ * 1, you only have to report changes that happened only directly in
+ * immediate descendants. If it's 2, it should also include changes from
+ * the nodes below the child collections. (grandchildren)
+ *
+ * The $limit argument allows a client to specify how many results should
+ * be returned at most. If the limit is not specified, it should be treated
+ * as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $calendarId
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Calendar.php b/vendor/sabre/dav/lib/CalDAV/Calendar.php
new file mode 100644
index 000000000..ff8e19b15
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Calendar.php
@@ -0,0 +1,527 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAV;
+use Sabre\DAVACL;
+use Sabre\DAV\PropPatch;
+
+/**
+ * This object represents a CalDAV calendar.
+ *
+ * A calendar can contain multiple TODO and or Events. These are represented
+ * as \Sabre\CalDAV\CalendarObject objects.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet {
+
+ /**
+ * This is an array with calendar information
+ *
+ * @var array
+ */
+ protected $calendarInfo;
+
+ /**
+ * CalDAV backend
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $caldavBackend;
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $caldavBackend
+ * @param array $calendarInfo
+ */
+ function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->calendarInfo = $calendarInfo;
+
+ }
+
+ /**
+ * Returns the name of the calendar
+ *
+ * @return string
+ */
+ function getName() {
+
+ return $this->calendarInfo['uri'];
+
+ }
+
+ /**
+ * Updates properties on this node.
+ *
+ * This method received a PropPatch object, which contains all the
+ * information about the update.
+ *
+ * To update specific properties, call the 'handle' method on this object.
+ * Read the PropPatch documentation for more information.
+ *
+ * @param PropPatch $propPatch
+ * @return void
+ */
+ function propPatch(PropPatch $propPatch) {
+
+ return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch);
+
+ }
+
+ /**
+ * Returns the list of properties
+ *
+ * @param array $requestedProperties
+ * @return array
+ */
+ function getProperties($requestedProperties) {
+
+ $response = [];
+
+ foreach ($this->calendarInfo as $propName => $propValue) {
+
+ if ($propName[0] === '{')
+ $response[$propName] = $this->calendarInfo[$propName];
+
+ }
+ return $response;
+
+ }
+
+ /**
+ * Returns a calendar object
+ *
+ * The contained calendar objects are for example Events or Todo's.
+ *
+ * @param string $name
+ * @return \Sabre\CalDAV\ICalendarObject
+ */
+ function getChild($name) {
+
+ $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
+
+ if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found');
+
+ $obj['acl'] = $this->getChildACL();
+
+ return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
+
+ }
+
+ /**
+ * Returns the full list of calendar objects
+ *
+ * @return array
+ */
+ function getChildren() {
+
+ $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
+ $children = [];
+ foreach ($objs as $obj) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
+ }
+ return $children;
+
+ }
+
+ /**
+ * This method receives a list of paths in it's first argument.
+ * It must return an array with Node objects.
+ *
+ * If any children are not found, you do not have to return them.
+ *
+ * @param string[] $paths
+ * @return array
+ */
+ function getMultipleChildren(array $paths) {
+
+ $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
+ $children = [];
+ foreach ($objs as $obj) {
+ $obj['acl'] = $this->getChildACL();
+ $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
+ }
+ return $children;
+
+ }
+
+ /**
+ * Checks if a child-node exists.
+ *
+ * @param string $name
+ * @return bool
+ */
+ function childExists($name) {
+
+ $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
+ if (!$obj)
+ return false;
+ else
+ return true;
+
+ }
+
+ /**
+ * Creates a new directory
+ *
+ * We actually block this, as subdirectories are not allowed in calendars.
+ *
+ * @param string $name
+ * @return void
+ */
+ function createDirectory($name) {
+
+ throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');
+
+ }
+
+ /**
+ * Creates a new file
+ *
+ * The contents of the new file must be a valid ICalendar string.
+ *
+ * @param string $name
+ * @param resource $calendarData
+ * @return string|null
+ */
+ function createFile($name, $calendarData = null) {
+
+ if (is_resource($calendarData)) {
+ $calendarData = stream_get_contents($calendarData);
+ }
+ return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData);
+
+ }
+
+ /**
+ * Deletes the calendar.
+ *
+ * @return void
+ */
+ function delete() {
+
+ $this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
+
+ }
+
+ /**
+ * Renames the calendar. Note that most calendars use the
+ * {DAV:}displayname to display a name to display a name.
+ *
+ * @param string $newName
+ * @return void
+ */
+ function setName($newName) {
+
+ throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
+
+ }
+
+ /**
+ * Returns the last modification date as a unix timestamp.
+ *
+ * @return void
+ */
+ function getLastModified() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->calendarInfo['principaluri'];
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ $acl = [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ ],
+
+ ];
+ if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
+ $acl[] = [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ];
+ $acl[] = [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ];
+ }
+
+ return $acl;
+
+ }
+
+ /**
+ * This method returns the ACL's for calendar objects in this calendar.
+ * The result of this method automatically gets passed to the
+ * calendar-object nodes in the calendar.
+ *
+ * @return array
+ */
+ function getChildACL() {
+
+ $acl = [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
+ 'protected' => true,
+ ],
+
+ ];
+ if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
+ $acl[] = [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ];
+ $acl[] = [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ];
+
+ }
+ return $acl;
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
+
+ // We need to inject 'read-free-busy' in the tree, aggregated under
+ // {DAV:}read.
+ foreach ($default['aggregates'] as &$agg) {
+
+ if ($agg['privilege'] !== '{DAV:}read') continue;
+
+ $agg['aggregates'][] = [
+ 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
+ ];
+
+ }
+ return $default;
+
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by Sabre\CalDAV\CalendarQueryParser.
+ *
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery(array $filters) {
+
+ return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
+
+ }
+
+ /**
+ * This method returns the current sync-token for this collection.
+ * This can be any string.
+ *
+ * If null is returned from this function, the plugin assumes there's no
+ * sync information available.
+ *
+ * @return string|null
+ */
+ function getSyncToken() {
+
+ if (
+ $this->caldavBackend instanceof Backend\SyncSupport &&
+ isset($this->calendarInfo['{DAV:}sync-token'])
+ ) {
+ return $this->calendarInfo['{DAV:}sync-token'];
+ }
+ if (
+ $this->caldavBackend instanceof Backend\SyncSupport &&
+ isset($this->calendarInfo['{http://sabredav.org/ns}sync-token'])
+ ) {
+ return $this->calendarInfo['{http://sabredav.org/ns}sync-token'];
+ }
+
+ }
+
+ /**
+ * The getChanges method returns all the changes that have happened, since
+ * the specified syncToken and the current collection.
+ *
+ * This function should return an array, such as the following:
+ *
+ * [
+ * 'syncToken' => 'The current synctoken',
+ * 'added' => [
+ * 'new.txt',
+ * ],
+ * 'modified' => [
+ * 'modified.txt',
+ * ],
+ * 'deleted' => [
+ * 'foo.php.bak',
+ * 'old.txt'
+ * ]
+ * ];
+ *
+ * The syncToken property should reflect the *current* syncToken of the
+ * collection, as reported getSyncToken(). This is needed here too, to
+ * ensure the operation is atomic.
+ *
+ * If the syncToken is specified as null, this is an initial sync, and all
+ * members should be reported.
+ *
+ * The modified property is an array of nodenames that have changed since
+ * the last token.
+ *
+ * The deleted property is an array with nodenames, that have been deleted
+ * from collection.
+ *
+ * The second argument is basically the 'depth' of the report. If it's 1,
+ * you only have to report changes that happened only directly in immediate
+ * descendants. If it's 2, it should also include changes from the nodes
+ * below the child collections. (grandchildren)
+ *
+ * The third (optional) argument allows a client to specify how many
+ * results should be returned at most. If the limit is not specified, it
+ * should be treated as infinite.
+ *
+ * If the limit (infinite or not) is higher than you're willing to return,
+ * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
+ *
+ * If the syncToken is expired (due to data cleanup) or unknown, you must
+ * return null.
+ *
+ * The limit is 'suggestive'. You are free to ignore it.
+ *
+ * @param string $syncToken
+ * @param int $syncLevel
+ * @param int $limit
+ * @return array
+ */
+ function getChanges($syncToken, $syncLevel, $limit = null) {
+
+ if (!$this->caldavBackend instanceof Backend\SyncSupport) {
+ return null;
+ }
+
+ return $this->caldavBackend->getChangesForCalendar(
+ $this->calendarInfo['id'],
+ $syncToken,
+ $syncLevel,
+ $limit
+ );
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarHome.php b/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
new file mode 100644
index 000000000..a53f829e2
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarHome.php
@@ -0,0 +1,430 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAV;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\MkCol;
+use Sabre\DAVACL;
+use Sabre\HTTP\URLUtil;
+
+/**
+ * The CalendarHome represents a node that is usually in a users'
+ * calendar-homeset.
+ *
+ * It contains all the users' calendars, and can optionally contain a
+ * notifications collection, calendar subscriptions, a users' inbox, and a
+ * users' outbox.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {
+
+ /**
+ * CalDAV backend
+ *
+ * @var Sabre\CalDAV\Backend\BackendInterface
+ */
+ protected $caldavBackend;
+
+ /**
+ * Principal information
+ *
+ * @var array
+ */
+ protected $principalInfo;
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $caldavBackend
+ * @param mixed $userUri
+ */
+ function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->principalInfo = $principalInfo;
+
+ }
+
+ /**
+ * Returns the name of this object
+ *
+ * @return string
+ */
+ function getName() {
+
+ list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
+ return $name;
+
+ }
+
+ /**
+ * Updates the name of this object
+ *
+ * @param string $name
+ * @return void
+ */
+ function setName($name) {
+
+ throw new DAV\Exception\Forbidden();
+
+ }
+
+ /**
+ * Deletes this object
+ *
+ * @return void
+ */
+ function delete() {
+
+ throw new DAV\Exception\Forbidden();
+
+ }
+
+ /**
+ * Returns the last modification date
+ *
+ * @return int
+ */
+ function getLastModified() {
+
+ return null;
+
+ }
+
+ /**
+ * Creates a new file under this object.
+ *
+ * This is currently not allowed
+ *
+ * @param string $filename
+ * @param resource $data
+ * @return void
+ */
+ function createFile($filename, $data = null) {
+
+ throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');
+
+ }
+
+ /**
+ * Creates a new directory under this object.
+ *
+ * This is currently not allowed.
+ *
+ * @param string $filename
+ * @return void
+ */
+ function createDirectory($filename) {
+
+ throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');
+
+ }
+
+ /**
+ * Returns a single calendar, by name
+ *
+ * @param string $name
+ * @return Calendar
+ */
+ function getChild($name) {
+
+ // Special nodes
+ if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
+ return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
+ }
+ if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
+ return new Schedule\Outbox($this->principalInfo['uri']);
+ }
+ if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
+ return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
+ }
+
+ // Calendars
+ foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
+ if ($calendar['uri'] === $name) {
+ if ($this->caldavBackend instanceof Backend\SharingSupport) {
+ if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
+ return new SharedCalendar($this->caldavBackend, $calendar);
+ } else {
+ return new ShareableCalendar($this->caldavBackend, $calendar);
+ }
+ } else {
+ return new Calendar($this->caldavBackend, $calendar);
+ }
+ }
+ }
+
+ if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
+ foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
+ if ($subscription['uri'] === $name) {
+ return new Subscriptions\Subscription($this->caldavBackend, $subscription);
+ }
+ }
+
+ }
+
+ throw new NotFound('Node with name \'' . $name . '\' could not be found');
+
+ }
+
+ /**
+ * Checks if a calendar exists.
+ *
+ * @param string $name
+ * @return bool
+ */
+ function childExists($name) {
+
+ try {
+ return !!$this->getChild($name);
+ } catch (NotFound $e) {
+ return false;
+ }
+
+ }
+
+ /**
+ * Returns a list of calendars
+ *
+ * @return array
+ */
+ function getChildren() {
+
+ $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
+ $objs = [];
+ foreach ($calendars as $calendar) {
+ if ($this->caldavBackend instanceof Backend\SharingSupport) {
+ if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
+ $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
+ } else {
+ $objs[] = new ShareableCalendar($this->caldavBackend, $calendar);
+ }
+ } else {
+ $objs[] = new Calendar($this->caldavBackend, $calendar);
+ }
+ }
+
+ if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
+ $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
+ $objs[] = new Schedule\Outbox($this->principalInfo['uri']);
+ }
+
+ // We're adding a notifications node, if it's supported by the backend.
+ if ($this->caldavBackend instanceof Backend\NotificationSupport) {
+ $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
+ }
+
+ // If the backend supports subscriptions, we'll add those as well,
+ if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
+ foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
+ $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
+ }
+ }
+
+ return $objs;
+
+ }
+
+ /**
+ * Creates a new calendar or subscription.
+ *
+ * @param string $name
+ * @param MkCol $mkCol
+ * @throws DAV\Exception\InvalidResourceType
+ * @return void
+ */
+ function createExtendedCollection($name, MkCol $mkCol) {
+
+ $isCalendar = false;
+ $isSubscription = false;
+ foreach ($mkCol->getResourceType() as $rt) {
+ switch ($rt) {
+ case '{DAV:}collection' :
+ case '{http://calendarserver.org/ns/}shared-owner' :
+ // ignore
+ break;
+ case '{urn:ietf:params:xml:ns:caldav}calendar' :
+ $isCalendar = true;
+ break;
+ case '{http://calendarserver.org/ns/}subscribed' :
+ $isSubscription = true;
+ break;
+ default :
+ throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
+ }
+ }
+
+ $properties = $mkCol->getRemainingValues();
+ $mkCol->setRemainingResultCode(201);
+
+ if ($isSubscription) {
+ if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
+ throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
+ }
+ $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);
+
+ } elseif ($isCalendar) {
+ $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
+
+ } else {
+ throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');
+
+ }
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->principalInfo['uri'];
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->principalInfo['uri'],
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->principalInfo['uri'],
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
+ 'protected' => true,
+ ],
+
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+ /**
+ * This method is called when a user replied to a request to share.
+ *
+ * This method should return the url of the newly created calendar if the
+ * share was accepted.
+ *
+ * @param string href The sharee who is replying (often a mailto: address)
+ * @param int status One of the SharingPlugin::STATUS_* constants
+ * @param string $calendarUri The url to the calendar thats being shared
+ * @param string $inReplyTo The unique id this message is a response to
+ * @param string $summary A description of the reply
+ * @return null|string
+ */
+ function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {
+
+ if (!$this->caldavBackend instanceof Backend\SharingSupport) {
+ throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
+ }
+
+ return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
+
+ }
+
+ /**
+ * Searches through all of a users calendars and calendar objects to find
+ * an object with a specific UID.
+ *
+ * This method should return the path to this object, relative to the
+ * calendar home, so this path usually only contains two parts:
+ *
+ * calendarpath/objectpath.ics
+ *
+ * If the uid is not found, return null.
+ *
+ * This method should only consider * objects that the principal owns, so
+ * any calendars owned by other principals that also appear in this
+ * collection should be ignored.
+ *
+ * @param string $uid
+ * @return string|null
+ */
+ function getCalendarObjectByUID($uid) {
+
+ return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarObject.php b/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
new file mode 100644
index 000000000..393ca4cbd
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarObject.php
@@ -0,0 +1,290 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+/**
+ * The CalendarObject represents a single VEVENT or VTODO within a Calendar.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL {
+
+ /**
+ * Sabre\CalDAV\Backend\BackendInterface
+ *
+ * @var Sabre\CalDAV\Backend\AbstractBackend
+ */
+ protected $caldavBackend;
+
+ /**
+ * Array with information about this CalendarObject
+ *
+ * @var array
+ */
+ protected $objectData;
+
+ /**
+ * Array with information about the containing calendar
+ *
+ * @var array
+ */
+ protected $calendarInfo;
+
+ /**
+ * Constructor
+ *
+ * The following properties may be passed within $objectData:
+ *
+ * * calendarid - This must refer to a calendarid from a caldavBackend
+ * * uri - A unique uri. Only the 'basename' must be passed.
+ * * calendardata (optional) - The iCalendar data
+ * * etag - (optional) The etag for this object, MUST be encloded with
+ * double-quotes.
+ * * size - (optional) The size of the data in bytes.
+ * * lastmodified - (optional) format as a unix timestamp.
+ * * acl - (optional) Use this to override the default ACL for the node.
+ *
+ * @param Backend\BackendInterface $caldavBackend
+ * @param array $calendarInfo
+ * @param array $objectData
+ */
+ function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData) {
+
+ $this->caldavBackend = $caldavBackend;
+
+ if (!isset($objectData['uri'])) {
+ throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
+ }
+
+ $this->calendarInfo = $calendarInfo;
+ $this->objectData = $objectData;
+
+ }
+
+ /**
+ * Returns the uri for this object
+ *
+ * @return string
+ */
+ function getName() {
+
+ return $this->objectData['uri'];
+
+ }
+
+ /**
+ * Returns the ICalendar-formatted object
+ *
+ * @return string
+ */
+ function get() {
+
+ // Pre-populating the 'calendardata' is optional, if we don't have it
+ // already we fetch it from the backend.
+ if (!isset($this->objectData['calendardata'])) {
+ $this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
+ }
+ return $this->objectData['calendardata'];
+
+ }
+
+ /**
+ * Updates the ICalendar-formatted object
+ *
+ * @param string|resource $calendarData
+ * @return string
+ */
+ function put($calendarData) {
+
+ if (is_resource($calendarData)) {
+ $calendarData = stream_get_contents($calendarData);
+ }
+ $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData);
+ $this->objectData['calendardata'] = $calendarData;
+ $this->objectData['etag'] = $etag;
+
+ return $etag;
+
+ }
+
+ /**
+ * Deletes the calendar object
+ *
+ * @return void
+ */
+ function delete() {
+
+ $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
+
+ }
+
+ /**
+ * Returns the mime content-type
+ *
+ * @return string
+ */
+ function getContentType() {
+
+ $mime = 'text/calendar; charset=utf-8';
+ if (isset($this->objectData['component']) && $this->objectData['component']) {
+ $mime .= '; component=' . $this->objectData['component'];
+ }
+ return $mime;
+
+ }
+
+ /**
+ * Returns an ETag for this object.
+ *
+ * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
+ *
+ * @return string
+ */
+ function getETag() {
+
+ if (isset($this->objectData['etag'])) {
+ return $this->objectData['etag'];
+ } else {
+ return '"' . md5($this->get()) . '"';
+ }
+
+ }
+
+ /**
+ * Returns the last modification date as a unix timestamp
+ *
+ * @return int
+ */
+ function getLastModified() {
+
+ return $this->objectData['lastmodified'];
+
+ }
+
+ /**
+ * Returns the size of this object in bytes
+ *
+ * @return int
+ */
+ function getSize() {
+
+ if (array_key_exists('size', $this->objectData)) {
+ return $this->objectData['size'];
+ } else {
+ return strlen($this->get());
+ }
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->calendarInfo['principaluri'];
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ // An alternative acl may be specified in the object data.
+ if (isset($this->objectData['acl'])) {
+ return $this->objectData['acl'];
+ }
+
+ // The default ACL
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
+ 'protected' => true,
+ ],
+
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new \Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php b/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
new file mode 100644
index 000000000..f3c7524d2
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php
@@ -0,0 +1,375 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\VObject;
+use DateTime;
+
+/**
+ * CalendarQuery Validator
+ *
+ * This class is responsible for checking if an iCalendar object matches a set
+ * of filters. The main function to do this is 'validate'.
+ *
+ * This is used to determine which icalendar objects should be returned for a
+ * calendar-query REPORT request.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarQueryValidator {
+
+ /**
+ * Verify if a list of filters applies to the calendar data object
+ *
+ * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
+ *
+ * @param VObject\Component $vObject
+ * @param array $filters
+ * @return bool
+ */
+ function validate(VObject\Component\VCalendar $vObject, array $filters) {
+
+ // The top level object is always a component filter.
+ // We'll parse it manually, as it's pretty simple.
+ if ($vObject->name !== $filters['name']) {
+ return false;
+ }
+
+ return
+ $this->validateCompFilters($vObject, $filters['comp-filters']) &&
+ $this->validatePropFilters($vObject, $filters['prop-filters']);
+
+
+ }
+
+ /**
+ * This method checks the validity of comp-filters.
+ *
+ * A list of comp-filters needs to be specified. Also the parent of the
+ * component we're checking should be specified, not the component to check
+ * itself.
+ *
+ * @param VObject\Component $parent
+ * @param array $filters
+ * @return bool
+ */
+ protected function validateCompFilters(VObject\Component $parent, array $filters) {
+
+ foreach ($filters as $filter) {
+
+ $isDefined = isset($parent->{$filter['name']});
+
+ if ($filter['is-not-defined']) {
+
+ if ($isDefined) {
+ return false;
+ } else {
+ continue;
+ }
+
+ }
+ if (!$isDefined) {
+ return false;
+ }
+
+ if ($filter['time-range']) {
+ foreach ($parent->{$filter['name']} as $subComponent) {
+ if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
+ continue 2;
+ }
+ }
+ return false;
+ }
+
+ if (!$filter['comp-filters'] && !$filter['prop-filters']) {
+ continue;
+ }
+
+ // If there are sub-filters, we need to find at least one component
+ // for which the subfilters hold true.
+ foreach ($parent->{$filter['name']} as $subComponent) {
+
+ if (
+ $this->validateCompFilters($subComponent, $filter['comp-filters']) &&
+ $this->validatePropFilters($subComponent, $filter['prop-filters'])) {
+ // We had a match, so this comp-filter succeeds
+ continue 2;
+ }
+
+ }
+
+ // If we got here it means there were sub-comp-filters or
+ // sub-prop-filters and there was no match. This means this filter
+ // needs to return false.
+ return false;
+
+ }
+
+ // If we got here it means we got through all comp-filters alive so the
+ // filters were all true.
+ return true;
+
+ }
+
+ /**
+ * This method checks the validity of prop-filters.
+ *
+ * A list of prop-filters needs to be specified. Also the parent of the
+ * property we're checking should be specified, not the property to check
+ * itself.
+ *
+ * @param VObject\Component $parent
+ * @param array $filters
+ * @return bool
+ */
+ protected function validatePropFilters(VObject\Component $parent, array $filters) {
+
+ foreach ($filters as $filter) {
+
+ $isDefined = isset($parent->{$filter['name']});
+
+ if ($filter['is-not-defined']) {
+
+ if ($isDefined) {
+ return false;
+ } else {
+ continue;
+ }
+
+ }
+ if (!$isDefined) {
+ return false;
+ }
+
+ if ($filter['time-range']) {
+ foreach ($parent->{$filter['name']} as $subComponent) {
+ if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
+ continue 2;
+ }
+ }
+ return false;
+ }
+
+ if (!$filter['param-filters'] && !$filter['text-match']) {
+ continue;
+ }
+
+ // If there are sub-filters, we need to find at least one property
+ // for which the subfilters hold true.
+ foreach ($parent->{$filter['name']} as $subComponent) {
+
+ if (
+ $this->validateParamFilters($subComponent, $filter['param-filters']) &&
+ (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
+ ) {
+ // We had a match, so this prop-filter succeeds
+ continue 2;
+ }
+
+ }
+
+ // If we got here it means there were sub-param-filters or
+ // text-match filters and there was no match. This means the
+ // filter needs to return false.
+ return false;
+
+ }
+
+ // If we got here it means we got through all prop-filters alive so the
+ // filters were all true.
+ return true;
+
+ }
+
+ /**
+ * This method checks the validity of param-filters.
+ *
+ * A list of param-filters needs to be specified. Also the parent of the
+ * parameter we're checking should be specified, not the parameter to check
+ * itself.
+ *
+ * @param VObject\Property $parent
+ * @param array $filters
+ * @return bool
+ */
+ protected function validateParamFilters(VObject\Property $parent, array $filters) {
+
+ foreach ($filters as $filter) {
+
+ $isDefined = isset($parent[$filter['name']]);
+
+ if ($filter['is-not-defined']) {
+
+ if ($isDefined) {
+ return false;
+ } else {
+ continue;
+ }
+
+ }
+ if (!$isDefined) {
+ return false;
+ }
+
+ if (!$filter['text-match']) {
+ continue;
+ }
+
+ // If there are sub-filters, we need to find at least one parameter
+ // for which the subfilters hold true.
+ foreach ($parent[$filter['name']]->getParts() as $paramPart) {
+
+ if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
+ // We had a match, so this param-filter succeeds
+ continue 2;
+ }
+
+ }
+
+ // If we got here it means there was a text-match filter and there
+ // were no matches. This means the filter needs to return false.
+ return false;
+
+ }
+
+ // If we got here it means we got through all param-filters alive so the
+ // filters were all true.
+ return true;
+
+ }
+
+ /**
+ * This method checks the validity of a text-match.
+ *
+ * A single text-match should be specified as well as the specific property
+ * or parameter we need to validate.
+ *
+ * @param VObject\Node|string $check Value to check against.
+ * @param array $textMatch
+ * @return bool
+ */
+ protected function validateTextMatch($check, array $textMatch) {
+
+ if ($check instanceof VObject\Node) {
+ $check = $check->getValue();
+ }
+
+ $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
+
+ return ($textMatch['negate-condition'] xor $isMatching);
+
+ }
+
+ /**
+ * Validates if a component matches the given time range.
+ *
+ * This is all based on the rules specified in rfc4791, which are quite
+ * complex.
+ *
+ * @param VObject\Node $component
+ * @param DateTime $start
+ * @param DateTime $end
+ * @return bool
+ */
+ protected function validateTimeRange(VObject\Node $component, $start, $end) {
+
+ if (is_null($start)) {
+ $start = new DateTime('1900-01-01');
+ }
+ if (is_null($end)) {
+ $end = new DateTime('3000-01-01');
+ }
+
+ switch ($component->name) {
+
+ case 'VEVENT' :
+ case 'VTODO' :
+ case 'VJOURNAL' :
+
+ return $component->isInTimeRange($start, $end);
+
+ case 'VALARM' :
+
+ // If the valarm is wrapped in a recurring event, we need to
+ // expand the recursions, and validate each.
+ //
+ // Our datamodel doesn't easily allow us to do this straight
+ // in the VALARM component code, so this is a hack, and an
+ // expensive one too.
+ if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
+
+ // Fire up the iterator!
+ $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
+ while ($it->valid()) {
+ $expandedEvent = $it->getEventObject();
+
+ // We need to check from these expanded alarms, which
+ // one is the first to trigger. Based on this, we can
+ // determine if we can 'give up' expanding events.
+ $firstAlarm = null;
+ if ($expandedEvent->VALARM !== null) {
+ foreach ($expandedEvent->VALARM as $expandedAlarm) {
+
+ $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
+ if ($expandedAlarm->isInTimeRange($start, $end)) {
+ return true;
+ }
+
+ if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
+ // This is an alarm with a non-relative trigger
+ // time, likely created by a buggy client. The
+ // implication is that every alarm in this
+ // recurring event trigger at the exact same
+ // time. It doesn't make sense to traverse
+ // further.
+ } else {
+ // We store the first alarm as a means to
+ // figure out when we can stop traversing.
+ if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
+ $firstAlarm = $effectiveTrigger;
+ }
+ }
+ }
+ }
+ if (is_null($firstAlarm)) {
+ // No alarm was found.
+ //
+ // Or technically: No alarm that will change for
+ // every instance of the recurrence was found,
+ // which means we can assume there was no match.
+ return false;
+ }
+ if ($firstAlarm > $end) {
+ return false;
+ }
+ $it->next();
+ }
+ return false;
+ } else {
+ return $component->isInTimeRange($start, $end);
+ }
+
+ case 'VFREEBUSY' :
+ throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
+
+ case 'COMPLETED' :
+ case 'CREATED' :
+ case 'DTEND' :
+ case 'DTSTAMP' :
+ case 'DTSTART' :
+ case 'DUE' :
+ case 'LAST-MODIFIED' :
+ return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
+
+
+
+ default :
+ throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
+
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php b/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php
new file mode 100644
index 000000000..0ac50e41d
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAVACL\PrincipalBackend;
+
+/**
+ * Calendars collection
+ *
+ * This object is responsible for generating a list of calendar-homes for each
+ * user.
+ *
+ * This is the top-most node for the calendars tree. In most servers this class
+ * represents the "/calendars" path.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection {
+
+ /**
+ * CalDAV backend
+ *
+ * @var Sabre\CalDAV\Backend\BackendInterface
+ */
+ protected $caldavBackend;
+
+ /**
+ * Constructor
+ *
+ * This constructor needs both an authentication and a caldav backend.
+ *
+ * By default this class will show a list of calendar collections for
+ * principals in the 'principals' collection. If your main principals are
+ * actually located in a different path, use the $principalPrefix argument
+ * to override this.
+ *
+ * @param PrincipalBackend\BackendInterface $principalBackend
+ * @param Backend\BackendInterface $caldavBackend
+ * @param string $principalPrefix
+ */
+ function __construct(PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') {
+
+ parent::__construct($principalBackend, $principalPrefix);
+ $this->caldavBackend = $caldavBackend;
+
+ }
+
+ /**
+ * Returns the nodename
+ *
+ * We're overriding this, because the default will be the 'principalPrefix',
+ * and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT
+ *
+ * @return string
+ */
+ function getName() {
+
+ return Plugin::CALENDAR_ROOT;
+
+ }
+
+ /**
+ * This method returns a node for a principal.
+ *
+ * The passed array contains principal information, and is guaranteed to
+ * at least contain a uri item. Other properties may or may not be
+ * supplied by the authentication backend.
+ *
+ * @param array $principal
+ * @return \Sabre\DAV\INode
+ */
+ function getChildForPrincipal(array $principal) {
+
+ return new CalendarHome($this->caldavBackend, $principal);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php b/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php
new file mode 100644
index 000000000..5ce8a93f5
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Sabre\CalDAV\Exception;
+
+use Sabre\DAV;
+use Sabre\CalDAV;
+
+/**
+ * InvalidComponentType
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class InvalidComponentType extends DAV\Exception\Forbidden {
+
+ /**
+ * Adds in extra information in the xml response.
+ *
+ * This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
+ *
+ * @param DAV\Server $server
+ * @param \DOMElement $errorNode
+ * @return void
+ */
+ function serialize(DAV\Server $server, \DOMElement $errorNode) {
+
+ $doc = $errorNode->ownerDocument;
+
+ $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component');
+ $errorNode->appendChild($np);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php b/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
new file mode 100644
index 000000000..8c296d50f
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php
@@ -0,0 +1,366 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use DateTimeZone;
+use Sabre\DAV;
+use Sabre\VObject;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\DAV\Exception\BadRequest;
+use DateTime;
+
+/**
+ * ICS Exporter
+ *
+ * This plugin adds the ability to export entire calendars as .ics files.
+ * This is useful for clients that don't support CalDAV yet. They often do
+ * support ics files.
+ *
+ * To use this, point a http client to a caldav calendar, and add ?expand to
+ * the url.
+ *
+ * Further options that can be added to the url:
+ * start=123456789 - Only return events after the given unix timestamp
+ * end=123245679 - Only return events from before the given unix timestamp
+ * expand=1 - Strip timezone information and expand recurring events.
+ * If you'd like to expand, you _must_ also specify start
+ * and end.
+ *
+ * By default this plugin returns data in the text/calendar format (iCalendar
+ * 2.0). If you'd like to receive jCal data instead, you can use an Accept
+ * header:
+ *
+ * Accept: application/calendar+json
+ *
+ * Alternatively, you can also specify this in the url using
+ * accept=application/calendar+json, or accept=jcal for short. If the url
+ * parameter and Accept header is specified, the url parameter wins.
+ *
+ * Note that specifying a start or end data implies that only events will be
+ * returned. VTODO and VJOURNAL will be stripped.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ICSExportPlugin extends DAV\ServerPlugin {
+
+ /**
+ * Reference to Server class
+ *
+ * @var \Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * Initializes the plugin and registers event handlers
+ *
+ * @param \Sabre\DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ $this->server = $server;
+ $server->on('method:GET', [$this, 'httpGet'], 90);
+ $server->on('browserButtonActions', function($path, $node, &$actions) {
+ if ($node instanceof ICalendar) {
+ $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
+ }
+ });
+
+ }
+
+ /**
+ * Intercepts GET requests on calendar urls ending with ?export.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool
+ */
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
+
+ $queryParams = $request->getQueryParameters();
+ if (!array_key_exists('export', $queryParams)) return;
+
+ $path = $request->getPath();
+
+ $node = $this->server->getProperties($path, [
+ '{DAV:}resourcetype',
+ '{DAV:}displayname',
+ '{http://sabredav.org/ns}sync-token',
+ '{DAV:}sync-token',
+ '{http://apple.com/ns/ical/}calendar-color',
+ ]);
+
+ if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
+ return;
+ }
+ // Marking the transactionType, for logging purposes.
+ $this->server->transactionType = 'get-calendar-export';
+
+ $properties = $node;
+
+ $start = null;
+ $end = null;
+ $expand = false;
+ $componentType = false;
+ if (isset($queryParams['start'])) {
+ if (!ctype_digit($queryParams['start'])) {
+ throw new BadRequest('The start= parameter must contain a unix timestamp');
+ }
+ $start = DateTime::createFromFormat('U', $queryParams['start']);
+ }
+ if (isset($queryParams['end'])) {
+ if (!ctype_digit($queryParams['end'])) {
+ throw new BadRequest('The end= parameter must contain a unix timestamp');
+ }
+ $end = DateTime::createFromFormat('U', $queryParams['end']);
+ }
+ if (isset($queryParams['expand']) && !!$queryParams['expand']) {
+ if (!$start || !$end) {
+ throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
+ }
+ $expand = true;
+ $componentType = 'VEVENT';
+ }
+ if (isset($queryParams['componentType'])) {
+ if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
+ throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
+ }
+ $componentType = $queryParams['componentType'];
+ }
+
+ $format = \Sabre\HTTP\Util::Negotiate(
+ $request->getHeader('Accept'),
+ [
+ 'text/calendar',
+ 'application/calendar+json',
+ ]
+ );
+
+ if (isset($queryParams['accept'])) {
+ if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
+ $format = 'application/calendar+json';
+ }
+ }
+ if (!$format) {
+ $format = 'text/calendar';
+ }
+
+ $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
+
+ // Returning false to break the event chain
+ return false;
+
+ }
+
+ /**
+ * This method is responsible for generating the actual, full response.
+ *
+ * @param string $path
+ * @param DateTime|null $start
+ * @param DateTime|null $end
+ * @param bool $expand
+ * @param string $componentType
+ * @param string $format
+ * @param array $properties
+ * @param ResponseInterface $response
+ */
+ protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
+
+ $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
+
+ $blobs = [];
+ if ($start || $end || $componentType) {
+
+ // If there was a start or end filter, we need to enlist
+ // calendarQuery for speed.
+ $calendarNode = $this->server->tree->getNodeForPath($path);
+ $queryResult = $calendarNode->calendarQuery([
+ 'name' => 'VCALENDAR',
+ 'comp-filters' => [
+ [
+ 'name' => $componentType,
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => [
+ 'start' => $start,
+ 'end' => $end,
+ ],
+ ],
+ ],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ ]);
+
+ // queryResult is just a list of base urls. We need to prefix the
+ // calendar path.
+ $queryResult = array_map(
+ function($item) use ($path) {
+ return $path . '/' . $item;
+ },
+ $queryResult
+ );
+ $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
+ unset($queryResult);
+
+ } else {
+ $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
+ }
+
+ // Flattening the arrays
+ foreach ($nodes as $node) {
+ if (isset($node[200][$calDataProp])) {
+ $blobs[$node['href']] = $node[200][$calDataProp];
+ }
+ }
+ unset($nodes);
+
+ $mergedCalendar = $this->mergeObjects(
+ $properties,
+ $blobs
+ );
+
+ if ($expand) {
+ $calendarTimeZone = null;
+ // We're expanding, and for that we need to figure out the
+ // calendar's timezone.
+ $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
+ $tzResult = $this->server->getProperties($path, [$tzProp]);
+ if (isset($tzResult[$tzProp])) {
+ // This property contains a VCALENDAR with a single
+ // VTIMEZONE.
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+ // Destroy circular references to PHP will GC the object.
+ $vtimezoneObj->destroy();
+ unset($vtimezoneObj);
+ } else {
+ // Defaulting to UTC.
+ $calendarTimeZone = new DateTimeZone('UTC');
+ }
+
+ $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
+ }
+
+ $response->setHeader('Content-Type', $format);
+
+ switch ($format) {
+ case 'text/calendar' :
+ $mergedCalendar = $mergedCalendar->serialize();
+ break;
+ case 'application/calendar+json' :
+ $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
+ break;
+ }
+
+ $response->setStatus(200);
+ $response->setBody($mergedCalendar);
+
+ }
+
+ /**
+ * Merges all calendar objects, and builds one big iCalendar blob.
+ *
+ * @param array $properties Some CalDAV properties
+ * @param array $inputObjects
+ * @return VObject\Component\VCalendar
+ */
+ function mergeObjects(array $properties, array $inputObjects) {
+
+ $calendar = new VObject\Component\VCalendar();
+ $calendar->version = '2.0';
+ if (DAV\Server::$exposeVersion) {
+ $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
+ } else {
+ $calendar->prodid = '-//SabreDAV//SabreDAV//EN';
+ }
+ if (isset($properties['{DAV:}displayname'])) {
+ $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
+ }
+ if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
+ $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
+ }
+
+ $collectedTimezones = [];
+
+ $timezones = [];
+ $objects = [];
+
+ foreach ($inputObjects as $href => $inputObject) {
+
+ $nodeComp = VObject\Reader::read($inputObject);
+
+ foreach ($nodeComp->children() as $child) {
+
+ switch ($child->name) {
+ case 'VEVENT' :
+ case 'VTODO' :
+ case 'VJOURNAL' :
+ $objects[] = clone $child;
+ break;
+
+ // VTIMEZONE is special, because we need to filter out the duplicates
+ case 'VTIMEZONE' :
+ // Naively just checking tzid.
+ if (in_array((string)$child->TZID, $collectedTimezones)) continue;
+
+ $timezones[] = clone $child;
+ $collectedTimezones[] = $child->TZID;
+ break;
+
+ }
+
+ }
+ // Destroy circular references to PHP will GC the object.
+ $nodeComp->destroy();
+ unset($nodeComp);
+
+ }
+
+ foreach ($timezones as $tz) $calendar->add($tz);
+ foreach ($objects as $obj) $calendar->add($obj);
+
+ return $calendar;
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using \Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'ics-export';
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
+ 'link' => 'http://sabre.io/dav/ics-export-plugin/',
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/ICalendar.php b/vendor/sabre/dav/lib/CalDAV/ICalendar.php
new file mode 100644
index 000000000..7cf4b1256
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/ICalendar.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAVACL;
+
+/**
+ * Calendar interface
+ *
+ * Implement this interface to allow a node to be recognized as an calendar.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ICalendar extends ICalendarObjectContainer, DAVACL\IACL {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/ICalendarObject.php b/vendor/sabre/dav/lib/CalDAV/ICalendarObject.php
new file mode 100644
index 000000000..b3a767b74
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/ICalendarObject.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAV;
+
+/**
+ * CalendarObject interface
+ *
+ * Extend the ICalendarObject interface to allow your custom nodes to be picked up as
+ * CalendarObjects.
+ *
+ * Calendar objects are resources such as Events, Todo's or Journals.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ICalendarObject extends DAV\IFile {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php b/vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php
new file mode 100644
index 000000000..0308b8a55
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+/**
+ * This interface represents a node that may contain calendar objects.
+ *
+ * This is the shared parent for both the Inbox collection and calendars
+ * resources.
+ *
+ * In most cases you will likely want to look at ICalendar instead of this
+ * interface.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ICalendarObjectContainer extends \Sabre\DAV\ICollection {
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
+ *
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery(array $filters);
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php b/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php
new file mode 100644
index 000000000..cfda91a55
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+/**
+ * This interface represents a Calendar that can be shared with other users.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IShareableCalendar extends ICalendar {
+
+ /**
+ * Updates the list of shares.
+ *
+ * The first array is a list of people that are to be added to the
+ * calendar.
+ *
+ * Every element in the add array has the following properties:
+ * * href - A url. Usually a mailto: address
+ * * commonName - Usually a first and last name, or false
+ * * summary - A description of the share, can also be false
+ * * readOnly - A boolean value
+ *
+ * Every element in the remove array is just the address string.
+ *
+ * @param array $add
+ * @param array $remove
+ * @return void
+ */
+ function updateShares(array $add, array $remove);
+
+ /**
+ * Returns the list of people whom this calendar is shared with.
+ *
+ * Every element in this array should have the following properties:
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first + last name
+ * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
+ * * readOnly - boolean
+ * * summary - Optional, a description for the share
+ *
+ * @return array
+ */
+ function getShares();
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php b/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
new file mode 100644
index 000000000..84442ac21
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+/**
+ * This interface represents a Calendar that is shared by a different user.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ISharedCalendar extends ICalendar {
+
+ /**
+ * This method should return the url of the owners' copy of the shared
+ * calendar.
+ *
+ * @return string
+ */
+ function getSharedUrl();
+
+ /**
+ * Returns the list of people whom this calendar is shared with.
+ *
+ * Every element in this array should have the following properties:
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first + last name
+ * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
+ * * readOnly - boolean
+ * * summary - Optional, a description for the share
+ *
+ * @return array
+ */
+ function getShares();
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php b/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
new file mode 100644
index 000000000..1fcc1171c
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Sabre\CalDAV\Notifications;
+
+use Sabre\DAV;
+use Sabre\CalDAV;
+use Sabre\DAVACL;
+
+/**
+ * This node represents a list of notifications.
+ *
+ * It provides no additional functionality, but you must implement this
+ * interface to allow the Notifications plugin to mark the collection
+ * as a notifications collection.
+ *
+ * This collection should only return Sabre\CalDAV\Notifications\INode nodes as
+ * its children.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {
+
+ /**
+ * The notification backend
+ *
+ * @var Sabre\CalDAV\Backend\NotificationSupport
+ */
+ protected $caldavBackend;
+
+ /**
+ * Principal uri
+ *
+ * @var string
+ */
+ protected $principalUri;
+
+ /**
+ * Constructor
+ *
+ * @param CalDAV\Backend\NotificationSupport $caldavBackend
+ * @param string $principalUri
+ */
+ function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->principalUri = $principalUri;
+
+ }
+
+ /**
+ * Returns all notifications for a principal
+ *
+ * @return array
+ */
+ function getChildren() {
+
+ $children = [];
+ $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
+
+ foreach ($notifications as $notification) {
+
+ $children[] = new Node(
+ $this->caldavBackend,
+ $this->principalUri,
+ $notification
+ );
+ }
+
+ return $children;
+
+ }
+
+ /**
+ * Returns the name of this object
+ *
+ * @return string
+ */
+ function getName() {
+
+ return 'notifications';
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->principalUri;
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'principal' => $this->getOwner(),
+ 'privilege' => '{DAV:}read',
+ 'protected' => true,
+ ],
+ [
+ 'principal' => $this->getOwner(),
+ 'privilege' => '{DAV:}write',
+ 'protected' => true,
+ ]
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's as an array argument.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php b/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php
new file mode 100644
index 000000000..008e87435
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Sabre\CalDAV\Notifications;
+
+use Sabre\DAV;
+
+/**
+ * This node represents a list of notifications.
+ *
+ * It provides no additional functionality, but you must implement this
+ * interface to allow the Notifications plugin to mark the collection
+ * as a notifications collection.
+ *
+ * This collection should only return Sabre\CalDAV\Notifications\INode nodes as
+ * its children.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ICollection extends DAV\ICollection {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/INode.php b/vendor/sabre/dav/lib/CalDAV/Notifications/INode.php
new file mode 100644
index 000000000..f9986b714
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/INode.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Sabre\CalDAV\Notifications;
+
+/**
+ * This node represents a single notification.
+ *
+ * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
+ * MUST return an xml document that matches the requirements of the
+ * 'caldav-notifications.txt' spec.
+ *
+ * For a complete example, check out the Notification class, which contains
+ * some helper functions.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface INode {
+
+ /**
+ * This method must return an xml element, using the
+ * Sabre\CalDAV\Notifications\INotificationType classes.
+ *
+ * @return INotificationType
+ */
+ function getNotificationType();
+
+ /**
+ * Returns the etag for the notification.
+ *
+ * The etag must be surrounded by litteral double-quotes.
+ *
+ * @return string
+ */
+ function getETag();
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php b/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
new file mode 100644
index 000000000..47e78d5de
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Sabre\CalDAV\Notifications;
+
+use Sabre\DAV;
+use Sabre\CalDAV;
+use Sabre\CalDAV\Xml\Notification\NotificationInterface;
+use Sabre\DAVACL;
+
+/**
+ * This node represents a single notification.
+ *
+ * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
+ * MUST return an xml document that matches the requirements of the
+ * 'caldav-notifications.txt' spec.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Node extends DAV\File implements INode, DAVACL\IACL {
+
+ /**
+ * The notification backend
+ *
+ * @var Sabre\CalDAV\Backend\NotificationSupport
+ */
+ protected $caldavBackend;
+
+ /**
+ * The actual notification
+ *
+ * @var Sabre\CalDAV\Notifications\INotificationType
+ */
+ protected $notification;
+
+ /**
+ * Owner principal of the notification
+ *
+ * @var string
+ */
+ protected $principalUri;
+
+ /**
+ * Constructor
+ *
+ * @param CalDAV\Backend\NotificationSupport $caldavBackend
+ * @param string $principalUri
+ * @param NotificationInterface $notification
+ */
+ function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->principalUri = $principalUri;
+ $this->notification = $notification;
+
+ }
+
+ /**
+ * Returns the path name for this notification
+ *
+ * @return id
+ */
+ function getName() {
+
+ return $this->notification->getId() . '.xml';
+
+ }
+
+ /**
+ * Returns the etag for the notification.
+ *
+ * The etag must be surrounded by litteral double-quotes.
+ *
+ * @return string
+ */
+ function getETag() {
+
+ return $this->notification->getETag();
+
+ }
+
+ /**
+ * This method must return an xml element, using the
+ * Sabre\CalDAV\Notifications\INotificationType classes.
+ *
+ * @return INotificationType
+ */
+ function getNotificationType() {
+
+ return $this->notification;
+
+ }
+
+ /**
+ * Deletes this notification
+ *
+ * @return void
+ */
+ function delete() {
+
+ $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->principalUri;
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'principal' => $this->getOwner(),
+ 'privilege' => '{DAV:}read',
+ 'protected' => true,
+ ],
+ [
+ 'principal' => $this->getOwner(),
+ 'privilege' => '{DAV:}write',
+ 'protected' => true,
+ ]
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's as an array argument.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php
new file mode 100644
index 000000000..546bf927f
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Sabre\CalDAV\Notifications;
+
+use Sabre\DAV;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\INode as BaseINode;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Server;
+use Sabre\DAVACL;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * Notifications plugin
+ *
+ * This plugin implements several features required by the caldav-notification
+ * draft specification.
+ *
+ * Before version 2.1.0 this functionality was part of Sabre\CalDAV\Plugin but
+ * this has since been split up.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends ServerPlugin {
+
+ /**
+ * This is the namespace for the proprietary calendarserver extensions
+ */
+ const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
+
+ /**
+ * Reference to the main server object.
+ *
+ * @var Server
+ */
+ protected $server;
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using \Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'notifications';
+
+ }
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param Server $server
+ * @return void
+ */
+ function initialize(Server $server) {
+
+ $this->server = $server;
+ $server->on('method:GET', [$this, 'httpGet'], 90);
+ $server->on('propFind', [$this, 'propFind']);
+
+ $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
+
+ array_push($server->protectedProperties,
+ '{' . self::NS_CALENDARSERVER . '}notification-URL',
+ '{' . self::NS_CALENDARSERVER . '}notificationtype'
+ );
+
+ }
+
+ /**
+ * PropFind
+ *
+ * @param PropFind $propFind
+ * @param BaseINode $node
+ * @return void
+ */
+ function propFind(PropFind $propFind, BaseINode $node) {
+
+ $caldavPlugin = $this->server->getPlugin('caldav');
+
+ if ($node instanceof DAVACL\IPrincipal) {
+
+ $principalUrl = $node->getPrincipalUrl();
+
+ // notification-URL property
+ $propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) {
+
+ $notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/';
+ return new DAV\Xml\Property\Href($notificationPath);
+
+ });
+
+ }
+
+ if ($node instanceof INode) {
+
+ $propFind->handle(
+ '{' . self::NS_CALENDARSERVER . '}notificationtype',
+ [$node, 'getNotificationType']
+ );
+
+ }
+
+ }
+
+ /**
+ * This event is triggered before the usual GET request handler.
+ *
+ * We use this to intercept GET calls to notification nodes, and return the
+ * proper response.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return void
+ */
+ function httpGet(RequestInterface $request, ResponseInterface $response) {
+
+ $path = $request->getPath();
+
+ try {
+ $node = $this->server->tree->getNodeForPath($path);
+ } catch (DAV\Exception\NotFound $e) {
+ return;
+ }
+
+ if (!$node instanceof INode)
+ return;
+
+ $writer = $this->server->xml->getWriter();
+ $writer->contextUri = $this->server->getBaseUri();
+ $writer->openMemory();
+ $writer->startDocument('1.0', 'UTF-8');
+ $writer->startElement('{http://calendarserver.org/ns/}notification');
+ $node->getNotificationType()->xmlSerializeFull($writer);
+ $writer->endElement();
+
+ $response->setHeader('Content-Type', 'application/xml');
+ $response->setHeader('ETag', $node->getETag());
+ $response->setStatus(200);
+ $response->setBody($writer->outputMemory());
+
+ // Return false to break the event chain.
+ return false;
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
+ 'link' => 'http://sabre.io/dav/caldav-sharing/',
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Plugin.php
new file mode 100644
index 000000000..663490023
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Plugin.php
@@ -0,0 +1,1025 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use DateTimeZone;
+use Sabre\DAV;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\MkCol;
+use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAVACL;
+use Sabre\VObject;
+use Sabre\HTTP;
+use Sabre\Uri;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * CalDAV plugin
+ *
+ * This plugin provides functionality added by CalDAV (RFC 4791)
+ * It implements new reports, and the MKCALENDAR method.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends DAV\ServerPlugin {
+
+ /**
+ * This is the official CalDAV namespace
+ */
+ const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
+
+ /**
+ * This is the namespace for the proprietary calendarserver extensions
+ */
+ const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
+
+ /**
+ * The hardcoded root for calendar objects. It is unfortunate
+ * that we're stuck with it, but it will have to do for now
+ */
+ const CALENDAR_ROOT = 'calendars';
+
+ /**
+ * Reference to server object
+ *
+ * @var DAV\Server
+ */
+ protected $server;
+
+ /**
+ * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
+ * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
+ * capping it to 10M here.
+ */
+ protected $maxResourceSize = 10000000;
+
+ /**
+ * Use this method to tell the server this plugin defines additional
+ * HTTP methods.
+ *
+ * This method is passed a uri. It should only return HTTP methods that are
+ * available for the specified uri.
+ *
+ * @param string $uri
+ * @return array
+ */
+ function getHTTPMethods($uri) {
+
+ // The MKCALENDAR is only available on unmapped uri's, whose
+ // parents extend IExtendedCollection
+ list($parent, $name) = Uri\split($uri);
+
+ $node = $this->server->tree->getNodeForPath($parent);
+
+ if ($node instanceof DAV\IExtendedCollection) {
+ try {
+ $node->getChild($name);
+ } catch (DAV\Exception\NotFound $e) {
+ return ['MKCALENDAR'];
+ }
+ }
+ return [];
+
+ }
+
+ /**
+ * Returns the path to a principal's calendar home.
+ *
+ * The return url must not end with a slash.
+ * This function should return null in case a principal did not have
+ * a calendar home.
+ *
+ * @param string $principalUrl
+ * @return string
+ */
+ function getCalendarHomeForPrincipal($principalUrl) {
+
+ // The default behavior for most sabre/dav servers is that there is a
+ // principals root node, which contains users directly under it.
+ //
+ // This function assumes that there are two components in a principal
+ // path. If there's more, we don't return a calendar home. This
+ // excludes things like the calendar-proxy-read principal (which it
+ // should).
+ $parts = explode('/', trim($principalUrl, '/'));
+ if (count($parts) !== 2) return;
+ if ($parts[0] !== 'principals') return;
+
+ return self::CALENDAR_ROOT . '/' . $parts[1];
+
+ }
+
+ /**
+ * Returns a list of features for the DAV: HTTP header.
+ *
+ * @return array
+ */
+ function getFeatures() {
+
+ return ['calendar-access', 'calendar-proxy'];
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'caldav';
+
+ }
+
+ /**
+ * Returns a list of reports this plugin supports.
+ *
+ * This will be used in the {DAV:}supported-report-set property.
+ * Note that you still need to subscribe to the 'report' event to actually
+ * implement them
+ *
+ * @param string $uri
+ * @return array
+ */
+ function getSupportedReportSet($uri) {
+
+ $node = $this->server->tree->getNodeForPath($uri);
+
+ $reports = [];
+ if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
+ $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
+ $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
+ }
+ if ($node instanceof ICalendar) {
+ $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
+ }
+ // iCal has a bug where it assumes that sync support is enabled, only
+ // if we say we support it on the calendar-home, even though this is
+ // not actually the case.
+ if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
+ $reports[] = '{DAV:}sync-collection';
+ }
+ return $reports;
+
+ }
+
+ /**
+ * Initializes the plugin
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ $this->server = $server;
+
+ $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']);
+ $server->on('report', [$this, 'report']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
+ $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
+ $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
+ $server->on('afterMethod:GET', [$this, 'httpAfterGET']);
+
+ $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
+ $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
+
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
+ $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
+
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
+
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
+
+ array_push($server->protectedProperties,
+
+ '{' . self::NS_CALDAV . '}supported-calendar-component-set',
+ '{' . self::NS_CALDAV . '}supported-calendar-data',
+ '{' . self::NS_CALDAV . '}max-resource-size',
+ '{' . self::NS_CALDAV . '}min-date-time',
+ '{' . self::NS_CALDAV . '}max-date-time',
+ '{' . self::NS_CALDAV . '}max-instances',
+ '{' . self::NS_CALDAV . '}max-attendees-per-instance',
+ '{' . self::NS_CALDAV . '}calendar-home-set',
+ '{' . self::NS_CALDAV . '}supported-collation-set',
+ '{' . self::NS_CALDAV . '}calendar-data',
+
+ // CalendarServer extensions
+ '{' . self::NS_CALENDARSERVER . '}getctag',
+ '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
+ '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
+
+ );
+
+ if ($aclPlugin = $server->getPlugin('acl')) {
+ $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
+ }
+ }
+
+ /**
+ * This functions handles REPORT requests specific to CalDAV
+ *
+ * @param string $reportName
+ * @param mixed $report
+ * @return bool
+ */
+ function report($reportName, $report) {
+
+ switch ($reportName) {
+ case '{' . self::NS_CALDAV . '}calendar-multiget' :
+ $this->server->transactionType = 'report-calendar-multiget';
+ $this->calendarMultiGetReport($report);
+ return false;
+ case '{' . self::NS_CALDAV . '}calendar-query' :
+ $this->server->transactionType = 'report-calendar-query';
+ $this->calendarQueryReport($report);
+ return false;
+ case '{' . self::NS_CALDAV . '}free-busy-query' :
+ $this->server->transactionType = 'report-free-busy-query';
+ $this->freeBusyQueryReport($report);
+ return false;
+
+ }
+
+
+ }
+
+ /**
+ * This function handles the MKCALENDAR HTTP method, which creates
+ * a new calendar.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool
+ */
+ function httpMkCalendar(RequestInterface $request, ResponseInterface $response) {
+
+ $body = $request->getBodyAsString();
+ $path = $request->getPath();
+
+ $properties = [];
+
+ if ($body) {
+
+ try {
+ $mkcalendar = $this->server->xml->expect(
+ '{urn:ietf:params:xml:ns:caldav}mkcalendar',
+ $body
+ );
+ } catch (\Sabre\Xml\ParseException $e) {
+ throw new BadRequest($e->getMessage(), null, $e);
+ }
+ $properties = $mkcalendar->getProperties();
+
+ }
+
+ // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
+ // subscriptions. Before that it used MKCOL which was the correct way
+ // to do this.
+ //
+ // If the body had a {DAV:}resourcetype, it means we stumbled upon this
+ // request, and we simply use it instead of the pre-defined list.
+ if (isset($properties['{DAV:}resourcetype'])) {
+ $resourceType = $properties['{DAV:}resourcetype']->getValue();
+ } else {
+ $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
+ }
+
+ $this->server->createCollection($path, new MkCol($resourceType, $properties));
+
+ $this->server->httpResponse->setStatus(201);
+ $this->server->httpResponse->setHeader('Content-Length', 0);
+
+ // This breaks the method chain.
+ return false;
+ }
+
+ /**
+ * PropFind
+ *
+ * This method handler is invoked before any after properties for a
+ * resource are fetched. This allows us to add in any CalDAV specific
+ * properties.
+ *
+ * @param DAV\PropFind $propFind
+ * @param DAV\INode $node
+ * @return void
+ */
+ function propFind(DAV\PropFind $propFind, DAV\INode $node) {
+
+ $ns = '{' . self::NS_CALDAV . '}';
+
+ if ($node instanceof ICalendarObjectContainer) {
+
+ $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
+ $propFind->handle($ns . 'supported-calendar-data', function() {
+ return new Xml\Property\SupportedCalendarData();
+ });
+ $propFind->handle($ns . 'supported-collation-set', function() {
+ return new Xml\Property\SupportedCollationSet();
+ });
+
+ }
+
+ if ($node instanceof DAVACL\IPrincipal) {
+
+ $principalUrl = $node->getPrincipalUrl();
+
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {
+
+ $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
+ if (is_null($calendarHomePath)) return null;
+ return new Href($calendarHomePath . '/');
+
+ });
+ // The calendar-user-address-set property is basically mapped to
+ // the {DAV:}alternate-URI-set property.
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
+ $addresses = $node->getAlternateUriSet();
+ $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
+ return new Href($addresses, false);
+ });
+ // For some reason somebody thought it was a good idea to add
+ // another one of these properties. We're supporting it too.
+ $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
+ $addresses = $node->getAlternateUriSet();
+ $emails = [];
+ foreach ($addresses as $address) {
+ if (substr($address, 0, 7) === 'mailto:') {
+ $emails[] = substr($address, 7);
+ }
+ }
+ return new Xml\Property\EmailAddressSet($emails);
+ });
+
+ // These two properties are shortcuts for ical to easily find
+ // other principals this principal has access to.
+ $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
+ $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
+
+ if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {
+
+ $aclPlugin = $this->server->getPlugin('acl');
+ $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
+ $readList = [];
+ $writeList = [];
+
+ foreach ($membership as $group) {
+
+ $groupNode = $this->server->tree->getNodeForPath($group);
+
+ $listItem = Uri\split($group)[0] . '/';
+
+ // If the node is either ap proxy-read or proxy-write
+ // group, we grab the parent principal and add it to the
+ // list.
+ if ($groupNode instanceof Principal\IProxyRead) {
+ $readList[] = $listItem;
+ }
+ if ($groupNode instanceof Principal\IProxyWrite) {
+ $writeList[] = $listItem;
+ }
+
+ }
+
+ $propFind->set($propRead, new Href($readList));
+ $propFind->set($propWrite, new Href($writeList));
+
+ }
+
+ } // instanceof IPrincipal
+
+ if ($node instanceof ICalendarObject) {
+
+ // The calendar-data property is not supposed to be a 'real'
+ // property, but in large chunks of the spec it does act as such.
+ // Therefore we simply expose it as a property.
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
+ $val = $node->get();
+ if (is_resource($val))
+ $val = stream_get_contents($val);
+
+ // Taking out \r to not screw up the xml output
+ return str_replace("\r", "", $val);
+
+ });
+
+ }
+
+ }
+
+ /**
+ * This function handles the calendar-multiget REPORT.
+ *
+ * This report is used by the client to fetch the content of a series
+ * of urls. Effectively avoiding a lot of redundant requests.
+ *
+ * @param CalendarMultiGetReport $report
+ * @return void
+ */
+ function calendarMultiGetReport($report) {
+
+ $needsJson = $report->contentType === 'application/calendar+json';
+
+ $timeZones = [];
+ $propertyList = [];
+
+ $paths = array_map(
+ [$this->server, 'calculateUri'],
+ $report->hrefs
+ );
+
+ foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {
+
+ if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
+ $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
+
+ if ($report->expand) {
+ // We're expanding, and for that we need to figure out the
+ // calendar's timezone.
+ list($calendarPath) = Uri\split($uri);
+ if (!isset($timeZones[$calendarPath])) {
+ // Checking the calendar-timezone property.
+ $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
+ $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
+ if (isset($tzResult[$tzProp])) {
+ // This property contains a VCALENDAR with a single
+ // VTIMEZONE.
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
+ $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+ } else {
+ // Defaulting to UTC.
+ $timeZone = new DateTimeZone('UTC');
+ }
+ $timeZones[$calendarPath] = $timeZone;
+ }
+
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
+ }
+ if ($needsJson) {
+ $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
+ } else {
+ $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
+ }
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vObject->destroy();
+ }
+
+ $propertyList[] = $objProps;
+
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
+
+ }
+
+ /**
+ * This function handles the calendar-query REPORT
+ *
+ * This report is used by clients to request calendar objects based on
+ * complex conditions.
+ *
+ * @param Xml\Request\CalendarQueryReport $report
+ * @return void
+ */
+ function calendarQueryReport($report) {
+
+ $path = $this->server->getRequestUri();
+
+ $needsJson = $report->contentType === 'application/calendar+json';
+
+ $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
+ $depth = $this->server->getHTTPDepth(0);
+
+ // The default result is an empty array
+ $result = [];
+
+ $calendarTimeZone = null;
+ if ($report->expand) {
+ // We're expanding, and for that we need to figure out the
+ // calendar's timezone.
+ $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
+ $tzResult = $this->server->getProperties($path, [$tzProp]);
+ if (isset($tzResult[$tzProp])) {
+ // This property contains a VCALENDAR with a single
+ // VTIMEZONE.
+ $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vtimezoneObj->destroy();
+ } else {
+ // Defaulting to UTC.
+ $calendarTimeZone = new DateTimeZone('UTC');
+ }
+ }
+
+ // The calendarobject was requested directly. In this case we handle
+ // this locally.
+ if ($depth == 0 && $node instanceof ICalendarObject) {
+
+ $requestedCalendarData = true;
+ $requestedProperties = $report->properties;
+
+ if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
+
+ // We always retrieve calendar-data, as we need it for filtering.
+ $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
+
+ // If calendar-data wasn't explicitly requested, we need to remove
+ // it after processing.
+ $requestedCalendarData = false;
+ }
+
+ $properties = $this->server->getPropertiesForPath(
+ $path,
+ $requestedProperties,
+ 0
+ );
+
+ // This array should have only 1 element, the first calendar
+ // object.
+ $properties = current($properties);
+
+ // If there wasn't any calendar-data returned somehow, we ignore
+ // this.
+ if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
+
+ $validator = new CalendarQueryValidator();
+
+ $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+ if ($validator->validate($vObject, $report->filters)) {
+
+ // If the client didn't require the calendar-data property,
+ // we won't give it back.
+ if (!$requestedCalendarData) {
+ unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+ } else {
+
+
+ if ($report->expand) {
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
+ }
+ if ($needsJson) {
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
+ } elseif ($report->expand) {
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
+ }
+ }
+
+ $result = [$properties];
+
+ }
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vObject->destroy();
+
+ }
+
+ }
+
+ if ($node instanceof ICalendarObjectContainer && $depth === 0) {
+
+ if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
+ // Microsoft clients incorrectly supplied depth as 0, when it actually
+ // should have set depth to 1. We're implementing a workaround here
+ // to deal with this.
+ //
+ // This targets at least the following clients:
+ // Windows 10
+ // Windows Phone 8, 10
+ $depth = 1;
+ } else {
+ throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
+ }
+
+ }
+
+ // If we're dealing with a calendar, the calendar itself is responsible
+ // for the calendar-query.
+ if ($node instanceof ICalendarObjectContainer && $depth == 1) {
+
+ $nodePaths = $node->calendarQuery($report->filters);
+
+ foreach ($nodePaths as $path) {
+
+ list($properties) =
+ $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);
+
+ if (($needsJson || $report->expand)) {
+ $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);
+
+ if ($report->expand) {
+ $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
+ }
+
+ if ($needsJson) {
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
+ } else {
+ $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
+ }
+
+ // Destroy circular references so PHP will garbage collect the
+ // object.
+ $vObject->destroy();
+ }
+ $result[] = $properties;
+
+ }
+
+ }
+
+ $prefer = $this->server->getHTTPPrefer();
+
+ $this->server->httpResponse->setStatus(207);
+ $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
+ $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
+
+ }
+
+ /**
+ * This method is responsible for parsing the request and generating the
+ * response for the CALDAV:free-busy-query REPORT.
+ *
+ * @param Xml\Request\FreeBusyQueryReport $report
+ * @return void
+ */
+ protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {
+
+ $uri = $this->server->getRequestUri();
+
+ $acl = $this->server->getPlugin('acl');
+ if ($acl) {
+ $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
+ }
+
+ $calendar = $this->server->tree->getNodeForPath($uri);
+ if (!$calendar instanceof ICalendar) {
+ throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
+ }
+
+ $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
+
+ // Figuring out the default timezone for the calendar, for floating
+ // times.
+ $calendarProps = $this->server->getProperties($uri, [$tzProp]);
+
+ if (isset($calendarProps[$tzProp])) {
+ $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+ // Destroy circular references so PHP will garbage collect the object.
+ $vtimezoneObj->destroy();
+ } else {
+ $calendarTimeZone = new DateTimeZone('UTC');
+ }
+
+ // Doing a calendar-query first, to make sure we get the most
+ // performance.
+ $urls = $calendar->calendarQuery([
+ 'name' => 'VCALENDAR',
+ 'comp-filters' => [
+ [
+ 'name' => 'VEVENT',
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => [
+ 'start' => $report->start,
+ 'end' => $report->end,
+ ],
+ ],
+ ],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ ]);
+
+ $objects = array_map(function($url) use ($calendar) {
+ $obj = $calendar->getChild($url)->get();
+ return $obj;
+ }, $urls);
+
+ $generator = new VObject\FreeBusyGenerator();
+ $generator->setObjects($objects);
+ $generator->setTimeRange($report->start, $report->end);
+ $generator->setTimeZone($calendarTimeZone);
+ $result = $generator->getResult();
+ $result = $result->serialize();
+
+ $this->server->httpResponse->setStatus(200);
+ $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
+ $this->server->httpResponse->setHeader('Content-Length', strlen($result));
+ $this->server->httpResponse->setBody($result);
+
+ }
+
+ /**
+ * This method is triggered before a file gets updated with new content.
+ *
+ * This plugin uses this method to ensure that CalDAV objects receive
+ * valid calendar data.
+ *
+ * @param string $path
+ * @param DAV\IFile $node
+ * @param resource $data
+ * @param bool $modified Should be set to true, if this event handler
+ * changed &$data.
+ * @return void
+ */
+ function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
+
+ if (!$node instanceof ICalendarObject)
+ return;
+
+ // We're onyl interested in ICalendarObject nodes that are inside of a
+ // real calendar. This is to avoid triggering validation and scheduling
+ // for non-calendars (such as an inbox).
+ list($parent) = Uri\split($path);
+ $parentNode = $this->server->tree->getNodeForPath($parent);
+
+ if (!$parentNode instanceof ICalendar)
+ return;
+
+ $this->validateICalendar(
+ $data,
+ $path,
+ $modified,
+ $this->server->httpRequest,
+ $this->server->httpResponse,
+ false
+ );
+
+ }
+
+ /**
+ * This method is triggered before a new file is created.
+ *
+ * This plugin uses this method to ensure that newly created calendar
+ * objects contain valid calendar data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param DAV\ICollection $parentNode
+ * @param bool $modified Should be set to true, if this event handler
+ * changed &$data.
+ * @return void
+ */
+ function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
+
+ if (!$parentNode instanceof ICalendar)
+ return;
+
+ $this->validateICalendar(
+ $data,
+ $path,
+ $modified,
+ $this->server->httpRequest,
+ $this->server->httpResponse,
+ true
+ );
+
+ }
+
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @param string $path
+ * @param bool $modified Should be set to true, if this event handler
+ * changed &$data.
+ * @param RequestInterface $request The http request.
+ * @param ResponseInterface $response The http response.
+ * @param bool $isNew Is the item a new one, or an update.
+ * @return void
+ */
+ protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) {
+
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+
+ $before = md5($data);
+ // Converting the data to unicode, if needed.
+ $data = DAV\StringUtil::ensureUTF8($data);
+
+ if ($before !== md5($data)) $modified = true;
+
+ try {
+
+ // If the data starts with a [, we can reasonably assume we're dealing
+ // with a jCal object.
+ if (substr($data, 0, 1) === '[') {
+ $vobj = VObject\Reader::readJson($data);
+
+ // Converting $data back to iCalendar, as that's what we
+ // technically support everywhere.
+ $data = $vobj->serialize();
+ $modified = true;
+ } else {
+ $vobj = VObject\Reader::read($data);
+ }
+
+ } catch (VObject\ParseException $e) {
+
+ throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
+
+ }
+
+ if ($vobj->name !== 'VCALENDAR') {
+ throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
+ }
+
+ $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
+
+ // Get the Supported Components for the target calendar
+ list($parentPath) = Uri\split($path);
+ $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
+
+ if (isset($calendarProperties[$sCCS])) {
+ $supportedComponents = $calendarProperties[$sCCS]->getValue();
+ } else {
+ $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
+ }
+
+ $foundType = null;
+ $foundUID = null;
+ foreach ($vobj->getComponents() as $component) {
+ switch ($component->name) {
+ case 'VTIMEZONE' :
+ continue 2;
+ case 'VEVENT' :
+ case 'VTODO' :
+ case 'VJOURNAL' :
+ if (is_null($foundType)) {
+ $foundType = $component->name;
+ if (!in_array($foundType, $supportedComponents)) {
+ throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
+ }
+ if (!isset($component->UID)) {
+ throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
+ }
+ $foundUID = (string)$component->UID;
+ } else {
+ if ($foundType !== $component->name) {
+ throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
+ }
+ if ($foundUID !== (string)$component->UID) {
+ throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
+ }
+ }
+ break;
+ default :
+ throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
+
+ }
+ }
+ if (!$foundType)
+ throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
+
+ // We use an extra variable to allow event handles to tell us wether
+ // the object was modified or not.
+ //
+ // This helps us determine if we need to re-serialize the object.
+ $subModified = false;
+
+ $this->server->emit(
+ 'calendarObjectChange',
+ [
+ $request,
+ $response,
+ $vobj,
+ $parentPath,
+ &$subModified,
+ $isNew
+ ]
+ );
+
+ if ($subModified) {
+ // An event handler told us that it modified the object.
+ $data = $vobj->serialize();
+
+ // Using md5 to figure out if there was an *actual* change.
+ if (!$modified && $before !== md5($data)) {
+ $modified = true;
+ }
+
+ }
+
+ // Destroy circular references so PHP will garbage collect the object.
+ $vobj->destroy();
+
+ }
+
+
+ /**
+ * This method is used to generate HTML output for the
+ * DAV\Browser\Plugin. This allows us to generate an interface users
+ * can use to create new calendars.
+ *
+ * @param DAV\INode $node
+ * @param string $output
+ * @return bool
+ */
+ function htmlActionsPanel(DAV\INode $node, &$output) {
+
+ if (!$node instanceof CalendarHome)
+ return;
+
+ $output .= '<tr><td colspan="2"><form method="post" action="">
+ <h3>Create new calendar</h3>
+ <input type="hidden" name="sabreAction" value="mkcol" />
+ <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV . '}calendar" />
+ <label>Name (uri):</label> <input type="text" name="name" /><br />
+ <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
+ <input type="submit" value="create" />
+ </form>
+ </td></tr>';
+
+ return false;
+
+ }
+
+ /**
+ * This event is triggered after GET requests.
+ *
+ * This is used to transform data into jCal, if this was requested.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return void
+ */
+ function httpAfterGet(RequestInterface $request, ResponseInterface $response) {
+
+ if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
+ return;
+ }
+
+ $result = HTTP\Util::negotiate(
+ $request->getHeader('Accept'),
+ ['text/calendar', 'application/calendar+json']
+ );
+
+ if ($result !== 'application/calendar+json') {
+ // Do nothing
+ return;
+ }
+
+ // Transforming.
+ $vobj = VObject\Reader::read($response->getBody());
+
+ $jsonBody = json_encode($vobj->jsonSerialize());
+ $response->setBody($jsonBody);
+
+ // Destroy circular references so PHP will garbage collect the object.
+ $vobj->destroy();
+
+ $response->setHeader('Content-Type', 'application/calendar+json');
+ $response->setHeader('Content-Length', strlen($jsonBody));
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds support for CalDAV (rfc4791)',
+ 'link' => 'http://sabre.io/dav/caldav/',
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php b/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php
new file mode 100644
index 000000000..e19719a76
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Sabre\CalDAV\Principal;
+
+use Sabre\DAVACL;
+
+/**
+ * Principal collection
+ *
+ * This is an alternative collection to the standard ACL principal collection.
+ * This collection adds support for the calendar-proxy-read and
+ * calendar-proxy-write sub-principals, as defined by the caldav-proxy
+ * specification.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Collection extends DAVACL\PrincipalCollection {
+
+ /**
+ * Returns a child object based on principal information
+ *
+ * @param array $principalInfo
+ * @return User
+ */
+ function getChildForPrincipal(array $principalInfo) {
+
+ return new User($this->principalBackend, $principalInfo);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php b/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php
new file mode 100644
index 000000000..7dd375932
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Sabre\CalDAV\Principal;
+
+use Sabre\DAVACL;
+
+/**
+ * ProxyRead principal interface
+ *
+ * Any principal node implementing this interface will be picked up as a 'proxy
+ * principal group'.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IProxyRead extends DAVACL\IPrincipal {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php b/vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php
new file mode 100644
index 000000000..eda87a4ff
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Sabre\CalDAV\Principal;
+
+use Sabre\DAVACL;
+
+/**
+ * ProxyWrite principal interface
+ *
+ * Any principal node implementing this interface will be picked up as a 'proxy
+ * principal group'.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IProxyWrite extends DAVACL\IPrincipal {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php b/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php
new file mode 100644
index 000000000..93f0fe095
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace Sabre\CalDAV\Principal;
+
+use Sabre\DAVACL;
+use Sabre\DAV;
+
+/**
+ * ProxyRead principal
+ *
+ * This class represents a principal group, hosted under the main principal.
+ * This is needed to implement 'Calendar delegation' support. This class is
+ * instantiated by User.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ProxyRead implements IProxyRead {
+
+ /**
+ * Principal information from the parent principal.
+ *
+ * @var array
+ */
+ protected $principalInfo;
+
+ /**
+ * Principal backend
+ *
+ * @var DAVACL\PrincipalBackend\BackendInterface
+ */
+ protected $principalBackend;
+
+ /**
+ * Creates the object.
+ *
+ * Note that you MUST supply the parent principal information.
+ *
+ * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
+ * @param array $principalInfo
+ */
+ function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
+
+ $this->principalInfo = $principalInfo;
+ $this->principalBackend = $principalBackend;
+
+ }
+
+ /**
+ * Returns this principals name.
+ *
+ * @return string
+ */
+ function getName() {
+
+ return 'calendar-proxy-read';
+
+ }
+
+ /**
+ * Returns the last modification time
+ *
+ * @return null
+ */
+ function getLastModified() {
+
+ return null;
+
+ }
+
+ /**
+ * Deletes the current node
+ *
+ * @throws DAV\Exception\Forbidden
+ * @return void
+ */
+ function delete() {
+
+ throw new DAV\Exception\Forbidden('Permission denied to delete node');
+
+ }
+
+ /**
+ * Renames the node
+ *
+ * @throws DAV\Exception\Forbidden
+ * @param string $name The new name
+ * @return void
+ */
+ function setName($name) {
+
+ throw new DAV\Exception\Forbidden('Permission denied to rename file');
+
+ }
+
+
+ /**
+ * Returns a list of alternative urls for a principal
+ *
+ * This can for example be an email address, or ldap url.
+ *
+ * @return array
+ */
+ function getAlternateUriSet() {
+
+ return [];
+
+ }
+
+ /**
+ * Returns the full principal url
+ *
+ * @return string
+ */
+ function getPrincipalUrl() {
+
+ return $this->principalInfo['uri'] . '/' . $this->getName();
+
+ }
+
+ /**
+ * Returns the list of group members
+ *
+ * If this principal is a group, this function should return
+ * all member principal uri's for the group.
+ *
+ * @return array
+ */
+ function getGroupMemberSet() {
+
+ return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
+
+ }
+
+ /**
+ * Returns the list of groups this principal is member of
+ *
+ * If this principal is a member of a (list of) groups, this function
+ * should return a list of principal uri's for it's members.
+ *
+ * @return array
+ */
+ function getGroupMembership() {
+
+ return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
+
+ }
+
+ /**
+ * Sets a list of group members
+ *
+ * If this principal is a group, this method sets all the group members.
+ * The list of members is always overwritten, never appended to.
+ *
+ * This method should throw an exception if the members could not be set.
+ *
+ * @param array $principals
+ * @return void
+ */
+ function setGroupMemberSet(array $principals) {
+
+ $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
+
+ }
+
+ /**
+ * Returns the displayname
+ *
+ * This should be a human readable name for the principal.
+ * If none is available, return the nodename.
+ *
+ * @return string
+ */
+ function getDisplayName() {
+
+ return $this->getName();
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php b/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php
new file mode 100644
index 000000000..8124c05e0
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace Sabre\CalDAV\Principal;
+
+use Sabre\DAVACL;
+use Sabre\DAV;
+
+/**
+ * ProxyWrite principal
+ *
+ * This class represents a principal group, hosted under the main principal.
+ * This is needed to implement 'Calendar delegation' support. This class is
+ * instantiated by User.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ProxyWrite implements IProxyWrite {
+
+ /**
+ * Parent principal information
+ *
+ * @var array
+ */
+ protected $principalInfo;
+
+ /**
+ * Principal Backend
+ *
+ * @var DAVACL\PrincipalBackend\BackendInterface
+ */
+ protected $principalBackend;
+
+ /**
+ * Creates the object
+ *
+ * Note that you MUST supply the parent principal information.
+ *
+ * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
+ * @param array $principalInfo
+ */
+ function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {
+
+ $this->principalInfo = $principalInfo;
+ $this->principalBackend = $principalBackend;
+
+ }
+
+ /**
+ * Returns this principals name.
+ *
+ * @return string
+ */
+ function getName() {
+
+ return 'calendar-proxy-write';
+
+ }
+
+ /**
+ * Returns the last modification time
+ *
+ * @return null
+ */
+ function getLastModified() {
+
+ return null;
+
+ }
+
+ /**
+ * Deletes the current node
+ *
+ * @throws DAV\Exception\Forbidden
+ * @return void
+ */
+ function delete() {
+
+ throw new DAV\Exception\Forbidden('Permission denied to delete node');
+
+ }
+
+ /**
+ * Renames the node
+ *
+ * @throws DAV\Exception\Forbidden
+ * @param string $name The new name
+ * @return void
+ */
+ function setName($name) {
+
+ throw new DAV\Exception\Forbidden('Permission denied to rename file');
+
+ }
+
+
+ /**
+ * Returns a list of alternative urls for a principal
+ *
+ * This can for example be an email address, or ldap url.
+ *
+ * @return array
+ */
+ function getAlternateUriSet() {
+
+ return [];
+
+ }
+
+ /**
+ * Returns the full principal url
+ *
+ * @return string
+ */
+ function getPrincipalUrl() {
+
+ return $this->principalInfo['uri'] . '/' . $this->getName();
+
+ }
+
+ /**
+ * Returns the list of group members
+ *
+ * If this principal is a group, this function should return
+ * all member principal uri's for the group.
+ *
+ * @return array
+ */
+ function getGroupMemberSet() {
+
+ return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
+
+ }
+
+ /**
+ * Returns the list of groups this principal is member of
+ *
+ * If this principal is a member of a (list of) groups, this function
+ * should return a list of principal uri's for it's members.
+ *
+ * @return array
+ */
+ function getGroupMembership() {
+
+ return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
+
+ }
+
+ /**
+ * Sets a list of group members
+ *
+ * If this principal is a group, this method sets all the group members.
+ * The list of members is always overwritten, never appended to.
+ *
+ * This method should throw an exception if the members could not be set.
+ *
+ * @param array $principals
+ * @return void
+ */
+ function setGroupMemberSet(array $principals) {
+
+ $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
+
+ }
+
+ /**
+ * Returns the displayname
+ *
+ * This should be a human readable name for the principal.
+ * If none is available, return the nodename.
+ *
+ * @return string
+ */
+ function getDisplayName() {
+
+ return $this->getName();
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/User.php b/vendor/sabre/dav/lib/CalDAV/Principal/User.php
new file mode 100644
index 000000000..6e97e7cca
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Principal/User.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Sabre\CalDAV\Principal;
+
+use Sabre\DAV;
+use Sabre\DAVACL;
+
+/**
+ * CalDAV principal
+ *
+ * This is a standard user-principal for CalDAV. This principal is also a
+ * collection and returns the caldav-proxy-read and caldav-proxy-write child
+ * principals.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class User extends DAVACL\Principal implements DAV\ICollection {
+
+ /**
+ * Creates a new file in the directory
+ *
+ * @param string $name Name of the file
+ * @param resource $data Initial payload, passed as a readable stream resource.
+ * @throws DAV\Exception\Forbidden
+ * @return void
+ */
+ function createFile($name, $data = null) {
+
+ throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');
+
+ }
+
+ /**
+ * Creates a new subdirectory
+ *
+ * @param string $name
+ * @throws DAV\Exception\Forbidden
+ * @return void
+ */
+ function createDirectory($name) {
+
+ throw new DAV\Exception\Forbidden('Permission denied to create directory');
+
+ }
+
+ /**
+ * Returns a specific child node, referenced by its name
+ *
+ * @param string $name
+ * @return DAV\INode
+ */
+ function getChild($name) {
+
+ $principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
+ if (!$principal) {
+ throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
+ }
+ if ($name === 'calendar-proxy-read')
+ return new ProxyRead($this->principalBackend, $this->principalProperties);
+
+ if ($name === 'calendar-proxy-write')
+ return new ProxyWrite($this->principalBackend, $this->principalProperties);
+
+ throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
+
+ }
+
+ /**
+ * Returns an array with all the child nodes
+ *
+ * @return DAV\INode[]
+ */
+ function getChildren() {
+
+ $r = [];
+ if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
+ $r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
+ }
+ if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
+ $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
+ }
+
+ return $r;
+
+ }
+
+ /**
+ * Returns whether or not the child node exists
+ *
+ * @param string $name
+ * @return bool
+ */
+ function childExists($name) {
+
+ try {
+ $this->getChild($name);
+ return true;
+ } catch (DAV\Exception\NotFound $e) {
+ return false;
+ }
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ $acl = parent::getACL();
+ $acl[] = [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
+ 'protected' => true,
+ ];
+ $acl[] = [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ];
+ return $acl;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php
new file mode 100644
index 000000000..c9fd77d93
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+/**
+ * Implement this interface to have a node be recognized as a CalDAV scheduling
+ * inbox.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IInbox extends \Sabre\CalDAV\ICalendarObjectContainer, \Sabre\DAVACL\IACL {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php b/vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php
new file mode 100644
index 000000000..ffb1fe45b
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+use Sabre\DAV;
+use Sabre\VObject\ITip;
+
+/**
+ * iMIP handler.
+ *
+ * This class is responsible for sending out iMIP messages. iMIP is the
+ * email-based transport for iTIP. iTIP deals with scheduling operations for
+ * iCalendar objects.
+ *
+ * If you want to customize the email that gets sent out, you can do so by
+ * extending this class and overriding the sendMessage method.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class IMipPlugin extends DAV\ServerPlugin {
+
+ /**
+ * Email address used in From: header.
+ *
+ * @var string
+ */
+ protected $senderEmail;
+
+ /**
+ * ITipMessage
+ *
+ * @var ITip\Message
+ */
+ protected $itipMessage;
+
+ /**
+ * Creates the email handler.
+ *
+ * @param string $senderEmail. The 'senderEmail' is the email that shows up
+ * in the 'From:' address. This should
+ * generally be some kind of no-reply email
+ * address you own.
+ */
+ function __construct($senderEmail) {
+
+ $this->senderEmail = $senderEmail;
+
+ }
+
+ /*
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ $server->on('schedule', [$this, 'schedule'], 120);
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using \Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'imip';
+
+ }
+
+ /**
+ * Event handler for the 'schedule' event.
+ *
+ * @param ITip\Message $iTipMessage
+ * @return void
+ */
+ function schedule(ITip\Message $iTipMessage) {
+
+ // Not sending any emails if the system considers the update
+ // insignificant.
+ if (!$iTipMessage->significantChange) {
+ if (!$iTipMessage->scheduleStatus) {
+ $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
+ }
+ return;
+ }
+
+ $summary = $iTipMessage->message->VEVENT->SUMMARY;
+
+ if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto')
+ return;
+
+ if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto')
+ return;
+
+ $sender = substr($iTipMessage->sender, 7);
+ $recipient = substr($iTipMessage->recipient, 7);
+
+ if ($iTipMessage->senderName) {
+ $sender = $iTipMessage->senderName . ' <' . $sender . '>';
+ }
+ if ($iTipMessage->recipientName) {
+ $recipient = $iTipMessage->recipientName . ' <' . $recipient . '>';
+ }
+
+ $subject = 'SabreDAV iTIP message';
+ switch (strtoupper($iTipMessage->method)) {
+ case 'REPLY' :
+ $subject = 'Re: ' . $summary;
+ break;
+ case 'REQUEST' :
+ $subject = $summary;
+ break;
+ case 'CANCEL' :
+ $subject = 'Cancelled: ' . $summary;
+ break;
+ }
+
+ $headers = [
+ 'Reply-To: ' . $sender,
+ 'From: ' . $this->senderEmail,
+ 'Content-Type: text/calendar; charset=UTF-8; method=' . $iTipMessage->method,
+ ];
+ if (DAV\Server::$exposeVersion) {
+ $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION;
+ }
+ $this->mail(
+ $recipient,
+ $subject,
+ $iTipMessage->message->serialize(),
+ $headers
+ );
+ $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
+
+ }
+
+ // @codeCoverageIgnoreStart
+ // This is deemed untestable in a reasonable manner
+
+ /**
+ * This function is responsible for sending the actual email.
+ *
+ * @param string $to Recipient email address
+ * @param string $subject Subject of the email
+ * @param string $body iCalendar body
+ * @param array $headers List of headers
+ * @return void
+ */
+ protected function mail($to, $subject, $body, array $headers) {
+
+ mail($to, $subject, $body, implode("\r\n", $headers));
+
+ }
+
+ // @codeCoverageIgnoreEnd
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Email delivery (rfc6037) for CalDAV scheduling',
+ 'link' => 'http://sabre.io/dav/scheduling/',
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php
new file mode 100644
index 000000000..88fbdc411
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+/**
+ * Implement this interface to have a node be recognized as a CalDAV scheduling
+ * outbox.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface IOutbox extends \Sabre\DAV\ICollection, \Sabre\DAVACL\IACL {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php b/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php
new file mode 100644
index 000000000..b37cb40a1
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+/**
+ * The SchedulingObject represents a scheduling object in the Inbox collection
+ *
+ * @license http://sabre.io/license/ Modified BSD License
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ */
+interface ISchedulingObject extends \Sabre\CalDAV\ICalendarObject {
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
new file mode 100644
index 000000000..13212565e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php
@@ -0,0 +1,261 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+use Sabre\DAV;
+use Sabre\CalDAV;
+use Sabre\DAVACL;
+use Sabre\CalDAV\Backend;
+use Sabre\VObject;
+
+/**
+ * The CalDAV scheduling inbox
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Inbox extends DAV\Collection implements IInbox {
+
+ /**
+ * CalDAV backend
+ *
+ * @var Backend\BackendInterface
+ */
+ protected $caldavBackend;
+
+ /**
+ * The principal Uri
+ *
+ * @var string
+ */
+ protected $principalUri;
+
+ /**
+ * Constructor
+ *
+ * @param Backend\SchedulingSupport $caldavBackend
+ * @param string $principalUri
+ */
+ function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->principalUri = $principalUri;
+
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * This is used to generate the url.
+ *
+ * @return string
+ */
+ function getName() {
+
+ return 'inbox';
+
+ }
+
+ /**
+ * Returns an array with all the child nodes
+ *
+ * @return \Sabre\DAV\INode[]
+ */
+ function getChildren() {
+
+ $objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
+ $children = [];
+ foreach ($objs as $obj) {
+ //$obj['acl'] = $this->getACL();
+ $obj['principaluri'] = $this->principalUri;
+ $children[] = new SchedulingObject($this->caldavBackend, $obj);
+ }
+ return $children;
+
+ }
+
+ /**
+ * Creates a new file in the directory
+ *
+ * Data will either be supplied as a stream resource, or in certain cases
+ * as a string. Keep in mind that you may have to support either.
+ *
+ * After succesful creation of the file, you may choose to return the ETag
+ * of the new file here.
+ *
+ * The returned ETag must be surrounded by double-quotes (The quotes should
+ * be part of the actual string).
+ *
+ * If you cannot accurately determine the ETag, you should not return it.
+ * If you don't store the file exactly as-is (you're transforming it
+ * somehow) you should also not return an ETag.
+ *
+ * This means that if a subsequent GET to this new file does not exactly
+ * return the same contents of what was submitted here, you are strongly
+ * recommended to omit the ETag.
+ *
+ * @param string $name Name of the file
+ * @param resource|string $data Initial payload
+ * @return null|string
+ */
+ function createFile($name, $data = null) {
+
+ $this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->principalUri;
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write-properties',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}unbind',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}unbind',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-invite',
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-reply',
+ 'principal' => '{DAV:}authenticated',
+ 'protected' => true,
+ ],
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ $ns = '{' . CalDAV\Plugin::NS_CALDAV . '}';
+
+ $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
+ $default['aggregates'][] = [
+ 'privilege' => $ns . 'schedule-deliver',
+ 'aggregates' => [
+ ['privilege' => $ns . 'schedule-deliver-invite'],
+ ['privilege' => $ns . 'schedule-deliver-reply'],
+ ],
+ ];
+ return $default;
+
+ }
+
+ /**
+ * Performs a calendar-query on the contents of this calendar.
+ *
+ * The calendar-query is defined in RFC4791 : CalDAV. Using the
+ * calendar-query it is possible for a client to request a specific set of
+ * object, based on contents of iCalendar properties, date-ranges and
+ * iCalendar component types (VTODO, VEVENT).
+ *
+ * This method should just return a list of (relative) urls that match this
+ * query.
+ *
+ * The list of filters are specified as an array. The exact array is
+ * documented by \Sabre\CalDAV\CalendarQueryParser.
+ *
+ * @param array $filters
+ * @return array
+ */
+ function calendarQuery(array $filters) {
+
+ $result = [];
+ $validator = new CalDAV\CalendarQueryValidator();
+
+ $objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
+ foreach ($objects as $object) {
+ $vObject = VObject\Reader::read($object['calendardata']);
+ if ($validator->validate($vObject, $filters)) {
+ $result[] = $object['uri'];
+ }
+
+ // Destroy circular references to PHP will GC the object.
+ $vObject->destroy();
+ }
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
new file mode 100644
index 000000000..dabaee2ca
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+use Sabre\DAV;
+use Sabre\CalDAV;
+use Sabre\DAVACL;
+
+/**
+ * The CalDAV scheduling outbox
+ *
+ * The outbox is mainly used as an endpoint in the tree for a client to do
+ * free-busy requests. This functionality is completely handled by the
+ * Scheduling plugin, so this object is actually mostly static.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Outbox extends DAV\Collection implements IOutbox {
+
+ /**
+ * The principal Uri
+ *
+ * @var string
+ */
+ protected $principalUri;
+
+ /**
+ * Constructor
+ *
+ * @param string $principalUri
+ */
+ function __construct($principalUri) {
+
+ $this->principalUri = $principalUri;
+
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * This is used to generate the url.
+ *
+ * @return string
+ */
+ function getName() {
+
+ return 'outbox';
+
+ }
+
+ /**
+ * Returns an array with all the child nodes
+ *
+ * @return \Sabre\DAV\INode[]
+ */
+ function getChildren() {
+
+ return [];
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->principalUri;
+
+ }
+
+ /**
+ * Returns a group principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ ];
+
+ }
+
+ /**
+ * Updates the ACL
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet();
+ $default['aggregates'][] = [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy',
+ ];
+ $default['aggregates'][] = [
+ 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent',
+ ];
+
+ return $default;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
new file mode 100644
index 000000000..827d6209b
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php
@@ -0,0 +1,994 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+use DateTimeZone;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\PropPatch;
+use Sabre\DAV\INode;
+use Sabre\DAV\Xml\Property\Href;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\VObject;
+use Sabre\VObject\Reader;
+use Sabre\VObject\Component\VCalendar;
+use Sabre\VObject\ITip;
+use Sabre\VObject\ITip\Message;
+use Sabre\DAVACL;
+use Sabre\CalDAV\ICalendar;
+use Sabre\CalDAV\ICalendarObject;
+use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\NotImplemented;
+
+/**
+ * CalDAV scheduling plugin.
+ * =========================
+ *
+ * This plugin provides the functionality added by the "Scheduling Extensions
+ * to CalDAV" standard, as defined in RFC6638.
+ *
+ * calendar-auto-schedule largely works by intercepting a users request to
+ * update their local calendar. If a user creates a new event with attendees,
+ * this plugin is supposed to grab the information from that event, and notify
+ * the attendees of this.
+ *
+ * There's 3 possible transports for this:
+ * * local delivery
+ * * delivery through email (iMip)
+ * * server-to-server delivery (iSchedule)
+ *
+ * iMip is simply, because we just need to add the iTip message as an email
+ * attachment. Local delivery is harder, because we both need to add this same
+ * message to a local DAV inbox, as well as live-update the relevant events.
+ *
+ * iSchedule is something for later.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends ServerPlugin {
+
+ /**
+ * This is the official CalDAV namespace
+ */
+ const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
+
+ /**
+ * Reference to main Server object.
+ *
+ * @var Server
+ */
+ protected $server;
+
+ /**
+ * Returns a list of features for the DAV: HTTP header.
+ *
+ * @return array
+ */
+ function getFeatures() {
+
+ return ['calendar-auto-schedule', 'calendar-availability'];
+
+ }
+
+ /**
+ * Returns the name of the plugin.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'caldav-schedule';
+
+ }
+
+ /**
+ * Initializes the plugin
+ *
+ * @param Server $server
+ * @return void
+ */
+ function initialize(Server $server) {
+
+ $this->server = $server;
+ $server->on('method:POST', [$this, 'httpPost']);
+ $server->on('propFind', [$this, 'propFind']);
+ $server->on('propPatch', [$this, 'propPatch']);
+ $server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
+ $server->on('beforeUnbind', [$this, 'beforeUnbind']);
+ $server->on('schedule', [$this, 'scheduleLocalDelivery']);
+
+ $ns = '{' . self::NS_CALDAV . '}';
+
+ /**
+ * This information ensures that the {DAV:}resourcetype property has
+ * the correct values.
+ */
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
+ $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';
+
+ /**
+ * Properties we protect are made read-only by the server.
+ */
+ array_push($server->protectedProperties,
+ $ns . 'schedule-inbox-URL',
+ $ns . 'schedule-outbox-URL',
+ $ns . 'calendar-user-address-set',
+ $ns . 'calendar-user-type',
+ $ns . 'schedule-default-calendar-URL'
+ );
+
+ }
+
+ /**
+ * Use this method to tell the server this plugin defines additional
+ * HTTP methods.
+ *
+ * This method is passed a uri. It should only return HTTP methods that are
+ * available for the specified uri.
+ *
+ * @param string $uri
+ * @return array
+ */
+ function getHTTPMethods($uri) {
+
+ try {
+ $node = $this->server->tree->getNodeForPath($uri);
+ } catch (NotFound $e) {
+ return [];
+ }
+
+ if ($node instanceof IOutbox) {
+ return ['POST'];
+ }
+
+ return [];
+
+ }
+
+ /**
+ * This method handles POST request for the outbox.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return bool
+ */
+ function httpPost(RequestInterface $request, ResponseInterface $response) {
+
+ // Checking if this is a text/calendar content type
+ $contentType = $request->getHeader('Content-Type');
+ if (strpos($contentType, 'text/calendar') !== 0) {
+ return;
+ }
+
+ $path = $request->getPath();
+
+ // Checking if we're talking to an outbox
+ try {
+ $node = $this->server->tree->getNodeForPath($path);
+ } catch (NotFound $e) {
+ return;
+ }
+ if (!$node instanceof IOutbox)
+ return;
+
+ $this->server->transactionType = 'post-caldav-outbox';
+ $this->outboxRequest($node, $request, $response);
+
+ // Returning false breaks the event chain and tells the server we've
+ // handled the request.
+ return false;
+
+ }
+
+ /**
+ * This method handler is invoked during fetching of properties.
+ *
+ * We use this event to add calendar-auto-schedule-specific properties.
+ *
+ * @param PropFind $propFind
+ * @param INode $node
+ * @return void
+ */
+ function propFind(PropFind $propFind, INode $node) {
+
+ if ($node instanceof DAVACL\IPrincipal) {
+
+ $caldavPlugin = $this->server->getPlugin('caldav');
+ $principalUrl = $node->getPrincipalUrl();
+
+ // schedule-outbox-URL property
+ $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {
+
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
+ if (!$calendarHomePath) {
+ return null;
+ }
+ $outboxPath = $calendarHomePath . '/outbox/';
+
+ return new Href($outboxPath);
+
+ });
+ // schedule-inbox-URL property
+ $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {
+
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
+ if (!$calendarHomePath) {
+ return null;
+ }
+ $inboxPath = $calendarHomePath . '/inbox/';
+
+ return new Href($inboxPath);
+
+ });
+
+ $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {
+
+ // We don't support customizing this property yet, so in the
+ // meantime we just grab the first calendar in the home-set.
+ $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
+
+ if (!$calendarHomePath) {
+ return null;
+ }
+
+ $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';
+
+ $result = $this->server->getPropertiesForPath($calendarHomePath, [
+ '{DAV:}resourcetype',
+ $sccs,
+ ], 1);
+
+ foreach ($result as $child) {
+ if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) {
+ // Node is either not a calendar or a shared instance.
+ continue;
+ }
+ if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
+ // Either there is no supported-calendar-component-set
+ // (which is fine) or we found one that supports VEVENT.
+ return new Href($child['href']);
+ }
+ }
+
+ });
+
+ // The server currently reports every principal to be of type
+ // 'INDIVIDUAL'
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {
+
+ return 'INDIVIDUAL';
+
+ });
+
+ }
+
+ // Mapping the old property to the new property.
+ $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {
+
+ // In case it wasn't clear, the only difference is that we map the
+ // old property to a different namespace.
+ $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
+ $subPropFind = new PropFind(
+ $propFind->getPath(),
+ [$availProp]
+ );
+
+ $this->server->getPropertiesByNode(
+ $subPropFind,
+ $node
+ );
+
+ $propFind->set(
+ '{http://calendarserver.org/ns/}calendar-availability',
+ $subPropFind->get($availProp),
+ $subPropFind->getStatus($availProp)
+ );
+
+ });
+
+ }
+
+ /**
+ * This method is called during property updates.
+ *
+ * @param string $path
+ * @param PropPatch $propPatch
+ * @return void
+ */
+ function propPatch($path, PropPatch $propPatch) {
+
+ // Mapping the old property to the new property.
+ $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {
+
+ $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
+ $subPropPatch = new PropPatch([$availProp => $value]);
+ $this->server->emit('propPatch', [$path, $subPropPatch]);
+ $subPropPatch->commit();
+
+ return $subPropPatch->getResult()[$availProp];
+
+ });
+
+ }
+
+ /**
+ * This method is triggered whenever there was a calendar object gets
+ * created or updated.
+ *
+ * @param RequestInterface $request HTTP request
+ * @param ResponseInterface $response HTTP Response
+ * @param VCalendar $vCal Parsed iCalendar object
+ * @param mixed $calendarPath Path to calendar collection
+ * @param mixed $modified The iCalendar object has been touched.
+ * @param mixed $isNew Whether this was a new item or we're updating one
+ * @return void
+ */
+ function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
+
+ if (!$this->scheduleReply($this->server->httpRequest)) {
+ return;
+ }
+
+ $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
+
+ $addresses = $this->getAddressesForPrincipal(
+ $calendarNode->getOwner()
+ );
+
+ if (!$isNew) {
+ $node = $this->server->tree->getNodeForPath($request->getPath());
+ $oldObj = Reader::read($node->get());
+ } else {
+ $oldObj = null;
+ }
+
+ $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
+
+ if ($oldObj) {
+ // Destroy circular references so PHP will GC the object.
+ $oldObj->destroy();
+ }
+
+ }
+
+ /**
+ * This method is responsible for delivering the ITip message.
+ *
+ * @param ITip\Message $itipMessage
+ * @return void
+ */
+ function deliver(ITip\Message $iTipMessage) {
+
+ $this->server->emit('schedule', [$iTipMessage]);
+ if (!$iTipMessage->scheduleStatus) {
+ $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
+ }
+ // In case the change was considered 'insignificant', we are going to
+ // remove any error statuses, if any. See ticket #525.
+ list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
+ if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
+ $iTipMessage->scheduleStatus = null;
+ }
+
+ }
+
+ /**
+ * This method is triggered before a file gets deleted.
+ *
+ * We use this event to make sure that when this happens, attendees get
+ * cancellations, and organizers get 'DECLINED' statuses.
+ *
+ * @param string $path
+ * @return void
+ */
+ function beforeUnbind($path) {
+
+ // FIXME: We shouldn't trigger this functionality when we're issuing a
+ // MOVE. This is a hack.
+ if ($this->server->httpRequest->getMethod() === 'MOVE') return;
+
+ $node = $this->server->tree->getNodeForPath($path);
+
+ if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
+ return;
+ }
+
+ if (!$this->scheduleReply($this->server->httpRequest)) {
+ return;
+ }
+
+ $addresses = $this->getAddressesForPrincipal(
+ $node->getOwner()
+ );
+
+ $broker = new ITip\Broker();
+ $messages = $broker->parseEvent(null, $addresses, $node->get());
+
+ foreach ($messages as $message) {
+ $this->deliver($message);
+ }
+
+ }
+
+ /**
+ * Event handler for the 'schedule' event.
+ *
+ * This handler attempts to look at local accounts to deliver the
+ * scheduling object.
+ *
+ * @param ITip\Message $iTipMessage
+ * @return void
+ */
+ function scheduleLocalDelivery(ITip\Message $iTipMessage) {
+
+ $aclPlugin = $this->server->getPlugin('acl');
+
+ // Local delivery is not available if the ACL plugin is not loaded.
+ if (!$aclPlugin) {
+ return;
+ }
+
+ $caldavNS = '{' . self::NS_CALDAV . '}';
+
+ $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
+ if (!$principalUri) {
+ $iTipMessage->scheduleStatus = '3.7;Could not find principal.';
+ return;
+ }
+
+ // We found a principal URL, now we need to find its inbox.
+ // Unfortunately we may not have sufficient privileges to find this, so
+ // we are temporarily turning off ACL to let this come through.
+ //
+ // Once we support PHP 5.5, this should be wrapped in a try..finally
+ // block so we can ensure that this privilege gets added again after.
+ $this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
+
+ $result = $this->server->getProperties(
+ $principalUri,
+ [
+ '{DAV:}principal-URL',
+ $caldavNS . 'calendar-home-set',
+ $caldavNS . 'schedule-inbox-URL',
+ $caldavNS . 'schedule-default-calendar-URL',
+ '{http://sabredav.org/ns}email-address',
+ ]
+ );
+
+ // Re-registering the ACL event
+ $this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
+
+ if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
+ $iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
+ return;
+ }
+ if (!isset($result[$caldavNS . 'calendar-home-set'])) {
+ $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
+ return;
+ }
+ if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
+ $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
+ return;
+ }
+
+ $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
+ $homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
+ $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();
+
+ if ($iTipMessage->method === 'REPLY') {
+ $privilege = 'schedule-deliver-reply';
+ } else {
+ $privilege = 'schedule-deliver-invite';
+ }
+
+ if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
+ $iTipMessage->scheduleStatus = '3.8;organizer did not have the ' . $privilege . ' privilege on the attendees inbox';
+ return;
+ }
+
+ // Next, we're going to find out if the item already exits in one of
+ // the users' calendars.
+ $uid = $iTipMessage->uid;
+
+ $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';
+
+ $home = $this->server->tree->getNodeForPath($homePath);
+ $inbox = $this->server->tree->getNodeForPath($inboxPath);
+
+ $currentObject = null;
+ $objectNode = null;
+ $isNewNode = false;
+
+ $result = $home->getCalendarObjectByUID($uid);
+ if ($result) {
+ // There was an existing object, we need to update probably.
+ $objectPath = $homePath . '/' . $result;
+ $objectNode = $this->server->tree->getNodeForPath($objectPath);
+ $oldICalendarData = $objectNode->get();
+ $currentObject = Reader::read($oldICalendarData);
+ } else {
+ $isNewNode = true;
+ }
+
+ $broker = new ITip\Broker();
+ $newObject = $broker->processMessage($iTipMessage, $currentObject);
+
+ $inbox->createFile($newFileName, $iTipMessage->message->serialize());
+
+ if (!$newObject) {
+ // We received an iTip message referring to a UID that we don't
+ // have in any calendars yet, and processMessage did not give us a
+ // calendarobject back.
+ //
+ // The implication is that processMessage did not understand the
+ // iTip message.
+ $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
+ return;
+ }
+
+ // Note that we are bypassing ACL on purpose by calling this directly.
+ // We may need to look a bit deeper into this later. Supporting ACL
+ // here would be nice.
+ if ($isNewNode) {
+ $calendar = $this->server->tree->getNodeForPath($calendarPath);
+ $calendar->createFile($newFileName, $newObject->serialize());
+ } else {
+ // If the message was a reply, we may have to inform other
+ // attendees of this attendees status. Therefore we're shooting off
+ // another itipMessage.
+ if ($iTipMessage->method === 'REPLY') {
+ $this->processICalendarChange(
+ $oldICalendarData,
+ $newObject,
+ [$iTipMessage->recipient],
+ [$iTipMessage->sender]
+ );
+ }
+ $objectNode->put($newObject->serialize());
+ }
+ $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
+
+ }
+
+ /**
+ * This method looks at an old iCalendar object, a new iCalendar object and
+ * starts sending scheduling messages based on the changes.
+ *
+ * A list of addresses needs to be specified, so the system knows who made
+ * the update, because the behavior may be different based on if it's an
+ * attendee or an organizer.
+ *
+ * This method may update $newObject to add any status changes.
+ *
+ * @param VCalendar|string $oldObject
+ * @param VCalendar $newObject
+ * @param array $addresses
+ * @param array $ignore Any addresses to not send messages to.
+ * @param bool $modified A marker to indicate that the original object
+ * modified by this process.
+ * @return void
+ */
+ protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {
+
+ $broker = new ITip\Broker();
+ $messages = $broker->parseEvent($newObject, $addresses, $oldObject);
+
+ if ($messages) $modified = true;
+
+ foreach ($messages as $message) {
+
+ if (in_array($message->recipient, $ignore)) {
+ continue;
+ }
+
+ $this->deliver($message);
+
+ if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
+ if ($message->scheduleStatus) {
+ $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
+ }
+ unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
+
+ } else {
+
+ if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
+
+ if ($attendee->getNormalizedValue() === $message->recipient) {
+ if ($message->scheduleStatus) {
+ $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
+ }
+ unset($attendee['SCHEDULE-FORCE-SEND']);
+ break;
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Returns a list of addresses that are associated with a principal.
+ *
+ * @param string $principal
+ * @return array
+ */
+ protected function getAddressesForPrincipal($principal) {
+
+ $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';
+
+ $properties = $this->server->getProperties(
+ $principal,
+ [$CUAS]
+ );
+
+ // If we can't find this information, we'll stop processing
+ if (!isset($properties[$CUAS])) {
+ return;
+ }
+
+ $addresses = $properties[$CUAS]->getHrefs();
+ return $addresses;
+
+ }
+
+ /**
+ * This method handles POST requests to the schedule-outbox.
+ *
+ * Currently, two types of requests are support:
+ * * FREEBUSY requests from RFC 6638
+ * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
+ *
+ * The latter is from an expired early draft of the CalDAV scheduling
+ * extensions, but iCal depends on a feature from that spec, so we
+ * implement it.
+ *
+ * @param IOutbox $outboxNode
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return void
+ */
+ function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) {
+
+ $outboxPath = $request->getPath();
+
+ // Parsing the request body
+ try {
+ $vObject = VObject\Reader::read($request->getBody());
+ } catch (VObject\ParseException $e) {
+ throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
+ }
+
+ // The incoming iCalendar object must have a METHOD property, and a
+ // component. The combination of both determines what type of request
+ // this is.
+ $componentType = null;
+ foreach ($vObject->getComponents() as $component) {
+ if ($component->name !== 'VTIMEZONE') {
+ $componentType = $component->name;
+ break;
+ }
+ }
+ if (is_null($componentType)) {
+ throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
+ }
+
+ // Validating the METHOD
+ $method = strtoupper((string)$vObject->METHOD);
+ if (!$method) {
+ throw new BadRequest('A METHOD property must be specified in iTIP messages');
+ }
+
+ // So we support one type of request:
+ //
+ // REQUEST with a VFREEBUSY component
+
+ $acl = $this->server->getPlugin('acl');
+
+ if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
+
+ $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
+ $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
+
+ // Destroy circular references so PHP can GC the object.
+ $vObject->destroy();
+ unset($vObject);
+
+ } else {
+
+ throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
+
+ }
+
+ }
+
+ /**
+ * This method is responsible for parsing a free-busy query request and
+ * returning it's result.
+ *
+ * @param IOutbox $outbox
+ * @param VObject\Component $vObject
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return string
+ */
+ protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
+
+ $vFreeBusy = $vObject->VFREEBUSY;
+ $organizer = $vFreeBusy->organizer;
+
+ $organizer = (string)$organizer;
+
+ // Validating if the organizer matches the owner of the inbox.
+ $owner = $outbox->getOwner();
+
+ $caldavNS = '{' . self::NS_CALDAV . '}';
+
+ $uas = $caldavNS . 'calendar-user-address-set';
+ $props = $this->server->getProperties($owner, [$uas]);
+
+ if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
+ throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
+ }
+
+ if (!isset($vFreeBusy->ATTENDEE)) {
+ throw new BadRequest('You must at least specify 1 attendee');
+ }
+
+ $attendees = [];
+ foreach ($vFreeBusy->ATTENDEE as $attendee) {
+ $attendees[] = (string)$attendee;
+ }
+
+
+ if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
+ throw new BadRequest('DTSTART and DTEND must both be specified');
+ }
+
+ $startRange = $vFreeBusy->DTSTART->getDateTime();
+ $endRange = $vFreeBusy->DTEND->getDateTime();
+
+ $results = [];
+ foreach ($attendees as $attendee) {
+ $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
+ }
+
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+ $scheduleResponse = $dom->createElement('cal:schedule-response');
+ foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
+
+ $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
+
+ }
+ $dom->appendChild($scheduleResponse);
+
+ foreach ($results as $result) {
+ $xresponse = $dom->createElement('cal:response');
+
+ $recipient = $dom->createElement('cal:recipient');
+ $recipientHref = $dom->createElement('d:href');
+
+ $recipientHref->appendChild($dom->createTextNode($result['href']));
+ $recipient->appendChild($recipientHref);
+ $xresponse->appendChild($recipient);
+
+ $reqStatus = $dom->createElement('cal:request-status');
+ $reqStatus->appendChild($dom->createTextNode($result['request-status']));
+ $xresponse->appendChild($reqStatus);
+
+ if (isset($result['calendar-data'])) {
+
+ $calendardata = $dom->createElement('cal:calendar-data');
+ $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
+ $xresponse->appendChild($calendardata);
+
+ }
+ $scheduleResponse->appendChild($xresponse);
+ }
+
+ $response->setStatus(200);
+ $response->setHeader('Content-Type', 'application/xml');
+ $response->setBody($dom->saveXML());
+
+ }
+
+ /**
+ * Returns free-busy information for a specific address. The returned
+ * data is an array containing the following properties:
+ *
+ * calendar-data : A VFREEBUSY VObject
+ * request-status : an iTip status code.
+ * href: The principal's email address, as requested
+ *
+ * The following request status codes may be returned:
+ * * 2.0;description
+ * * 3.7;description
+ *
+ * @param string $email address
+ * @param DateTimeInterface $start
+ * @param DateTimeInterface $end
+ * @param VObject\Component $request
+ * @return array
+ */
+ protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {
+
+ $caldavNS = '{' . self::NS_CALDAV . '}';
+
+ $aclPlugin = $this->server->getPlugin('acl');
+ if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);
+
+ $result = $aclPlugin->principalSearch(
+ ['{http://sabredav.org/ns}email-address' => $email],
+ [
+ '{DAV:}principal-URL',
+ $caldavNS . 'calendar-home-set',
+ $caldavNS . 'schedule-inbox-URL',
+ '{http://sabredav.org/ns}email-address',
+
+ ]
+ );
+
+ if (!count($result)) {
+ return [
+ 'request-status' => '3.7;Could not find principal',
+ 'href' => 'mailto:' . $email,
+ ];
+ }
+
+ if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
+ return [
+ 'request-status' => '3.7;No calendar-home-set property found',
+ 'href' => 'mailto:' . $email,
+ ];
+ }
+ if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
+ return [
+ 'request-status' => '3.7;No schedule-inbox-URL property found',
+ 'href' => 'mailto:' . $email,
+ ];
+ }
+ $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
+ $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
+
+ // Grabbing the calendar list
+ $objects = [];
+ $calendarTimeZone = new DateTimeZone('UTC');
+
+ foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
+ if (!$node instanceof ICalendar) {
+ continue;
+ }
+
+ $sct = $caldavNS . 'schedule-calendar-transp';
+ $ctz = $caldavNS . 'calendar-timezone';
+ $props = $node->getProperties([$sct, $ctz]);
+
+ if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
+ // If a calendar is marked as 'transparent', it means we must
+ // ignore it for free-busy purposes.
+ continue;
+ }
+
+ $aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy');
+
+ if (isset($props[$ctz])) {
+ $vtimezoneObj = VObject\Reader::read($props[$ctz]);
+ $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
+
+ // Destroy circular references so PHP can garbage collect the object.
+ $vtimezoneObj->destroy();
+
+ }
+
+ // Getting the list of object uris within the time-range
+ $urls = $node->calendarQuery([
+ 'name' => 'VCALENDAR',
+ 'comp-filters' => [
+ [
+ 'name' => 'VEVENT',
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => [
+ 'start' => $start,
+ 'end' => $end,
+ ],
+ ],
+ ],
+ 'prop-filters' => [],
+ 'is-not-defined' => false,
+ 'time-range' => null,
+ ]);
+
+ $calObjects = array_map(function($url) use ($node) {
+ $obj = $node->getChild($url)->get();
+ return $obj;
+ }, $urls);
+
+ $objects = array_merge($objects, $calObjects);
+
+ }
+
+ $inboxProps = $this->server->getProperties(
+ $inboxUrl,
+ $caldavNS . 'calendar-availability'
+ );
+
+ $vcalendar = new VObject\Component\VCalendar();
+ $vcalendar->METHOD = 'REPLY';
+
+ $generator = new VObject\FreeBusyGenerator();
+ $generator->setObjects($objects);
+ $generator->setTimeRange($start, $end);
+ $generator->setBaseObject($vcalendar);
+ $generator->setTimeZone($calendarTimeZone);
+
+ if ($inboxProps) {
+ $generator->setVAvailability(
+ VObject\Reader::read(
+ $inboxProps[$caldavNS . 'calendar-availability']
+ )
+ );
+ }
+
+ $result = $generator->getResult();
+
+ $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
+ $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
+ $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
+
+ return [
+ 'calendar-data' => $result,
+ 'request-status' => '2.0;Success',
+ 'href' => 'mailto:' . $email,
+ ];
+ }
+
+ /**
+ * This method checks the 'Schedule-Reply' header
+ * and returns false if it's 'F', otherwise true.
+ *
+ * @param RequestInterface $request
+ * @return bool
+ */
+ private function scheduleReply(RequestInterface $request) {
+
+ $scheduleReply = $request->getHeader('Schedule-Reply');
+ return $scheduleReply !== 'F';
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds calendar-auto-schedule, as defined in rf6868',
+ 'link' => 'http://sabre.io/dav/scheduling/',
+ ];
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php b/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
new file mode 100644
index 000000000..a36646e6c
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php
@@ -0,0 +1,165 @@
+<?php
+
+namespace Sabre\CalDAV\Schedule;
+
+use Sabre\CalDAV\Backend;
+use Sabre\DAV\Exception\MethodNotAllowed;
+
+/**
+ * The SchedulingObject represents a scheduling object in the Inbox collection
+ *
+ * @author Brett (https://github.com/bretten)
+ * @license http://sabre.io/license/ Modified BSD License
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ */
+class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject {
+
+ /**
+ /* The CalDAV backend
+ *
+ * @var Backend\SchedulingSupport
+ */
+ protected $caldavBackend;
+
+ /**
+ * Array with information about this SchedulingObject
+ *
+ * @var array
+ */
+ protected $objectData;
+
+ /**
+ * Constructor
+ *
+ * The following properties may be passed within $objectData:
+ *
+ * * uri - A unique uri. Only the 'basename' must be passed.
+ * * principaluri - the principal that owns the object.
+ * * calendardata (optional) - The iCalendar data
+ * * etag - (optional) The etag for this object, MUST be encloded with
+ * double-quotes.
+ * * size - (optional) The size of the data in bytes.
+ * * lastmodified - (optional) format as a unix timestamp.
+ * * acl - (optional) Use this to override the default ACL for the node.
+ *
+ * @param Backend\BackendInterface $caldavBackend
+ * @param array $objectData
+ */
+ function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData) {
+
+ $this->caldavBackend = $caldavBackend;
+
+ if (!isset($objectData['uri'])) {
+ throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
+ }
+
+ $this->objectData = $objectData;
+
+ }
+
+ /**
+ * Returns the ICalendar-formatted object
+ *
+ * @return string
+ */
+ function get() {
+
+ // Pre-populating the 'calendardata' is optional, if we don't have it
+ // already we fetch it from the backend.
+ if (!isset($this->objectData['calendardata'])) {
+ $this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
+ }
+ return $this->objectData['calendardata'];
+
+ }
+
+ /**
+ * Updates the ICalendar-formatted object
+ *
+ * @param string|resource $calendarData
+ * @return string
+ */
+ function put($calendarData) {
+
+ throw new MethodNotAllowed('Updating scheduling objects is not supported');
+
+ }
+
+ /**
+ * Deletes the scheduling message
+ *
+ * @return void
+ */
+ function delete() {
+
+ $this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->objectData['principaluri'];
+
+ }
+
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ // An alternative acl may be specified in the object data.
+ //
+
+ if (isset($this->objectData['acl'])) {
+ return $this->objectData['acl'];
+ }
+
+ // The default ACL
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->objectData['principaluri'],
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->objectData['principaluri'],
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-read',
+ 'protected' => true,
+ ],
+ ];
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php b/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php
new file mode 100644
index 000000000..c11695d5f
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+/**
+ * This object represents a CalDAV calendar that can be shared with other
+ * users.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ShareableCalendar extends Calendar implements IShareableCalendar {
+
+ /**
+ * Updates the list of shares.
+ *
+ * The first array is a list of people that are to be added to the
+ * calendar.
+ *
+ * Every element in the add array has the following properties:
+ * * href - A url. Usually a mailto: address
+ * * commonName - Usually a first and last name, or false
+ * * summary - A description of the share, can also be false
+ * * readOnly - A boolean value
+ *
+ * Every element in the remove array is just the address string.
+ *
+ * @param array $add
+ * @param array $remove
+ * @return void
+ */
+ function updateShares(array $add, array $remove) {
+
+ $this->caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove);
+
+ }
+
+ /**
+ * Returns the list of people whom this calendar is shared with.
+ *
+ * Every element in this array should have the following properties:
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first + last name
+ * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
+ * * readOnly - boolean
+ * * summary - Optional, a description for the share
+ *
+ * @return array
+ */
+ function getShares() {
+
+ return $this->caldavBackend->getShares($this->calendarInfo['id']);
+
+ }
+
+ /**
+ * Marks this calendar as published.
+ *
+ * Publishing a calendar should automatically create a read-only, public,
+ * subscribable calendar.
+ *
+ * @param bool $value
+ * @return void
+ */
+ function setPublishStatus($value) {
+
+ $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php b/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
new file mode 100644
index 000000000..7973eff9c
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+/**
+ * This object represents a CalDAV calendar that is shared by a different user.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SharedCalendar extends Calendar implements ISharedCalendar {
+
+ /**
+ * Constructor
+ *
+ * @param Backend\BackendInterface $caldavBackend
+ * @param array $calendarInfo
+ */
+ function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {
+
+ $required = [
+ '{http://calendarserver.org/ns/}shared-url',
+ '{http://sabredav.org/ns}owner-principal',
+ '{http://sabredav.org/ns}read-only',
+ ];
+ foreach ($required as $r) {
+ if (!isset($calendarInfo[$r])) {
+ throw new \InvalidArgumentException('The ' . $r . ' property must be specified for SharedCalendar(s)');
+ }
+ }
+
+ parent::__construct($caldavBackend, $calendarInfo);
+
+ }
+
+ /**
+ * This method should return the url of the owners' copy of the shared
+ * calendar.
+ *
+ * @return string
+ */
+ function getSharedUrl() {
+
+ return $this->calendarInfo['{http://calendarserver.org/ns/}shared-url'];
+
+ }
+
+ /**
+ * Returns the owner principal
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->calendarInfo['{http://sabredav.org/ns}owner-principal'];
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ // The top-level ACL only contains access information for the true
+ // owner of the calendar, so we need to add the information for the
+ // sharee.
+ $acl = parent::getACL();
+ $acl[] = [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ];
+ if ($this->calendarInfo['{http://sabredav.org/ns}read-only']) {
+ $acl[] = [
+ 'privilege' => '{DAV:}write-properties',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ];
+ } else {
+ $acl[] = [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ];
+ }
+ return $acl;
+
+ }
+
+ /**
+ * This method returns the ACL's for calendar objects in this calendar.
+ * The result of this method automatically gets passed to the
+ * calendar-object nodes in the calendar.
+ *
+ * @return array
+ */
+ function getChildACL() {
+
+ $acl = parent::getChildACL();
+ $acl[] = [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ];
+
+ if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) {
+ $acl[] = [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->calendarInfo['principaluri'],
+ 'protected' => true,
+ ];
+ }
+ return $acl;
+
+ }
+
+
+ /**
+ * Returns the list of people whom this calendar is shared with.
+ *
+ * Every element in this array should have the following properties:
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first + last name
+ * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
+ * * readOnly - boolean
+ * * summary - Optional, a description for the share
+ *
+ * @return array
+ */
+ function getShares() {
+
+ return $this->caldavBackend->getShares($this->calendarInfo['id']);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php b/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
new file mode 100644
index 000000000..5154fb1de
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php
@@ -0,0 +1,426 @@
+<?php
+
+namespace Sabre\CalDAV;
+
+use Sabre\DAV;
+use Sabre\DAV\Xml\Property\Href;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+/**
+ * This plugin implements support for caldav sharing.
+ *
+ * This spec is defined at:
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
+ *
+ * See:
+ * Sabre\CalDAV\Backend\SharingSupport for all the documentation.
+ *
+ * Note: This feature is experimental, and may change in between different
+ * SabreDAV versions.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SharingPlugin extends DAV\ServerPlugin {
+
+ /**
+ * These are the various status constants used by sharing-messages.
+ */
+ const STATUS_ACCEPTED = 1;
+ const STATUS_DECLINED = 2;
+ const STATUS_DELETED = 3;
+ const STATUS_NORESPONSE = 4;
+ const STATUS_INVALID = 5;
+
+ /**
+ * Reference to SabreDAV server object.
+ *
+ * @var Sabre\DAV\Server
+ */
+ protected $server;
+
+ /**
+ * This method should return a list of server-features.
+ *
+ * This is for example 'versioning' and is added to the DAV: header
+ * in an OPTIONS response.
+ *
+ * @return array
+ */
+ function getFeatures() {
+
+ return ['calendarserver-sharing'];
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'caldav-sharing';
+
+ }
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param DAV\Server $server
+ * @return void
+ */
+ function initialize(DAV\Server $server) {
+
+ $this->server = $server;
+ $server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared';
+
+ array_push(
+ $this->server->protectedProperties,
+ '{' . Plugin::NS_CALENDARSERVER . '}invite',
+ '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
+ '{' . Plugin::NS_CALENDARSERVER . '}shared-url'
+ );
+
+ $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
+ $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';
+
+ $this->server->on('propFind', [$this, 'propFindEarly']);
+ $this->server->on('propFind', [$this, 'propFindLate'], 150);
+ $this->server->on('propPatch', [$this, 'propPatch'], 40);
+ $this->server->on('method:POST', [$this, 'httpPost']);
+
+ }
+
+ /**
+ * This event is triggered when properties are requested for a certain
+ * node.
+ *
+ * This allows us to inject any properties early.
+ *
+ * @param DAV\PropFind $propFind
+ * @param DAV\INode $node
+ * @return void
+ */
+ function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
+
+ if ($node instanceof IShareableCalendar) {
+
+ $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
+ return new Xml\Property\Invite(
+ $node->getShares()
+ );
+ });
+
+ }
+
+ if ($node instanceof ISharedCalendar) {
+
+ $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}shared-url', function() use ($node) {
+ return new Href(
+ $node->getSharedUrl()
+ );
+ });
+
+ $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {
+
+ // Fetching owner information
+ $props = $this->server->getPropertiesForPath($node->getOwner(), [
+ '{http://sabredav.org/ns}email-address',
+ '{DAV:}displayname',
+ ], 0);
+
+ $ownerInfo = [
+ 'href' => $node->getOwner(),
+ ];
+
+ if (isset($props[0][200])) {
+
+ // We're mapping the internal webdav properties to the
+ // elements caldav-sharing expects.
+ if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) {
+ $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address'];
+ }
+ if (isset($props[0][200]['{DAV:}displayname'])) {
+ $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname'];
+ }
+
+ }
+
+ return new Xml\Property\Invite(
+ $node->getShares(),
+ $ownerInfo
+ );
+
+ });
+
+ }
+
+ }
+
+ /**
+ * This method is triggered *after* all properties have been retrieved.
+ * This allows us to inject the correct resourcetype for calendars that
+ * have been shared.
+ *
+ * @param DAV\PropFind $propFind
+ * @param DAV\INode $node
+ * @return void
+ */
+ function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
+
+ if ($node instanceof IShareableCalendar) {
+ if ($rt = $propFind->get('{DAV:}resourcetype')) {
+ if (count($node->getShares()) > 0) {
+ $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
+ }
+ }
+ $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() {
+ return new Xml\Property\AllowedSharingModes(true, false);
+ });
+
+ }
+
+ }
+
+ /**
+ * This method is trigged when a user attempts to update a node's
+ * properties.
+ *
+ * A previous draft of the sharing spec stated that it was possible to use
+ * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
+ * the calendar.
+ *
+ * Even though this is no longer in the current spec, we keep this around
+ * because OS X 10.7 may still make use of this feature.
+ *
+ * @param string $path
+ * @param DAV\PropPatch $propPatch
+ * @return void
+ */
+ function propPatch($path, DAV\PropPatch $propPatch) {
+
+ $node = $this->server->tree->getNodeForPath($path);
+ if (!$node instanceof IShareableCalendar)
+ return;
+
+ $propPatch->handle('{DAV:}resourcetype', function($value) use ($node) {
+ if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false;
+ $shares = $node->getShares();
+ $remove = [];
+ foreach ($shares as $share) {
+ $remove[] = $share['href'];
+ }
+ $node->updateShares([], $remove);
+
+ return true;
+
+ });
+
+ }
+
+ /**
+ * We intercept this to handle POST requests on calendars.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @return null|bool
+ */
+ function httpPost(RequestInterface $request, ResponseInterface $response) {
+
+ $path = $request->getPath();
+
+ // Only handling xml
+ $contentType = $request->getHeader('Content-Type');
+ if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
+ return;
+
+ // Making sure the node exists
+ try {
+ $node = $this->server->tree->getNodeForPath($path);
+ } catch (DAV\Exception\NotFound $e) {
+ return;
+ }
+
+ $requestBody = $request->getBodyAsString();
+
+ // If this request handler could not deal with this POST request, it
+ // will return 'null' and other plugins get a chance to handle the
+ // request.
+ //
+ // However, we already requested the full body. This is a problem,
+ // because a body can only be read once. This is why we preemptively
+ // re-populated the request body with the existing data.
+ $request->setBody($requestBody);
+
+ $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
+
+ switch ($documentType) {
+
+ // Dealing with the 'share' document, which modified invitees on a
+ // calendar.
+ case '{' . Plugin::NS_CALENDARSERVER . '}share' :
+
+ // We can only deal with IShareableCalendar objects
+ if (!$node instanceof IShareableCalendar) {
+ return;
+ }
+
+ $this->server->transactionType = 'post-calendar-share';
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($path, '{DAV:}write');
+ }
+
+ $node->updateShares($message->set, $message->remove);
+
+ $response->setStatus(200);
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ // The invite-reply document is sent when the user replies to an
+ // invitation of a calendar share.
+ case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply' :
+
+ // This only works on the calendar-home-root node.
+ if (!$node instanceof CalendarHome) {
+ return;
+ }
+ $this->server->transactionType = 'post-invite-reply';
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($path, '{DAV:}write');
+ }
+
+ $url = $node->shareReply(
+ $message->href,
+ $message->status,
+ $message->calendarUri,
+ $message->inReplyTo,
+ $message->summary
+ );
+
+ $response->setStatus(200);
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ if ($url) {
+ $writer = $this->server->xml->getWriter($this->server->getBaseUri());
+ $writer->openMemory();
+ $writer->startDocument();
+ $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
+ $writer->write(new Href($url));
+ $writer->endElement();
+ $response->setHeader('Content-Type', 'application/xml');
+ $response->setBody($writer->outputMemory());
+
+ }
+
+ // Breaking the event chain
+ return false;
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :
+
+ // We can only deal with IShareableCalendar objects
+ if (!$node instanceof IShareableCalendar) {
+ return;
+ }
+ $this->server->transactionType = 'post-publish-calendar';
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($path, '{DAV:}write');
+ }
+
+ $node->setPublishStatus(true);
+
+ // iCloud sends back the 202, so we will too.
+ $response->setStatus(202);
+
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
+
+ // We can only deal with IShareableCalendar objects
+ if (!$node instanceof IShareableCalendar) {
+ return;
+ }
+ $this->server->transactionType = 'post-unpublish-calendar';
+
+ // Getting ACL info
+ $acl = $this->server->getPlugin('acl');
+
+ // If there's no ACL support, we allow everything
+ if ($acl) {
+ $acl->checkPrivileges($path, '{DAV:}write');
+ }
+
+ $node->setPublishStatus(false);
+
+ $response->setStatus(200);
+
+ // Adding this because sending a response body may cause issues,
+ // and I wanted some type of indicator the response was handled.
+ $response->setHeader('X-Sabre-Status', 'everything-went-well');
+
+ // Breaking the event chain
+ return false;
+
+ }
+
+
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'Adds support for caldav-sharing.',
+ 'link' => 'http://sabre.io/dav/caldav-sharing/',
+ ];
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php b/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php
new file mode 100644
index 000000000..7ba259c7b
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Sabre\CalDAV\Subscriptions;
+
+use Sabre\DAV\ICollection;
+use Sabre\DAV\IProperties;
+
+/**
+ * ISubscription
+ *
+ * Nodes implementing this interface represent calendar subscriptions.
+ *
+ * The subscription node doesn't do much, other than returning and updating
+ * subscription-related properties.
+ *
+ * The following properties should be supported:
+ *
+ * 1. {DAV:}displayname
+ * 2. {http://apple.com/ns/ical/}refreshrate
+ * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
+ * should not be stripped).
+ * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
+ * should not be stripped).
+ * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
+ * attachments should not be stripped).
+ * 6. {http://calendarserver.org/ns/}source (Must be a
+ * Sabre\DAV\Property\Href).
+ * 7. {http://apple.com/ns/ical/}calendar-color
+ * 8. {http://apple.com/ns/ical/}calendar-order
+ *
+ * It is recommended to support every property.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface ISubscription extends ICollection, IProperties {
+
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php
new file mode 100644
index 000000000..7abbfb1f9
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Sabre\CalDAV\Subscriptions;
+
+use Sabre\DAV\INode;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Server;
+
+/**
+ * This plugin adds calendar-subscription support to your CalDAV server.
+ *
+ * Some clients support 'managed subscriptions' server-side. This is basically
+ * a list of subscription urls a user is using.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Plugin extends ServerPlugin {
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ *
+ * @param Server $server
+ * @return void
+ */
+ function initialize(Server $server) {
+
+ $server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] =
+ '{http://calendarserver.org/ns/}subscribed';
+
+ $server->xml->elementMap['{http://calendarserver.org/ns/}source'] =
+ 'Sabre\\DAV\\Xml\\Property\\Href';
+
+ $server->on('propFind', [$this, 'propFind'], 150);
+
+ }
+
+ /**
+ * This method should return a list of server-features.
+ *
+ * This is for example 'versioning' and is added to the DAV: header
+ * in an OPTIONS response.
+ *
+ * @return array
+ */
+ function getFeatures() {
+
+ return ['calendarserver-subscribed'];
+
+ }
+
+ /**
+ * Triggered after properties have been fetched.
+ *
+ * @param PropFind $propFind
+ * @param INode $node
+ * @return void
+ */
+ function propFind(PropFind $propFind, INode $node) {
+
+ // There's a bunch of properties that must appear as a self-closing
+ // xml-element. This event handler ensures that this will be the case.
+ $props = [
+ '{http://calendarserver.org/ns/}subscribed-strip-alarms',
+ '{http://calendarserver.org/ns/}subscribed-strip-attachments',
+ '{http://calendarserver.org/ns/}subscribed-strip-todos',
+ ];
+
+ foreach ($props as $prop) {
+
+ if ($propFind->getStatus($prop) === 200) {
+ $propFind->set($prop, '', 200);
+ }
+
+ }
+
+ }
+
+ /**
+ * Returns a plugin name.
+ *
+ * Using this name other plugins will be able to access other plugins
+ * using \Sabre\DAV\Server::getPlugin
+ *
+ * @return string
+ */
+ function getPluginName() {
+
+ return 'subscriptions';
+
+ }
+
+ /**
+ * Returns a bunch of meta-data about the plugin.
+ *
+ * Providing this information is optional, and is mainly displayed by the
+ * Browser plugin.
+ *
+ * The description key in the returned array may contain html and will not
+ * be sanitized.
+ *
+ * @return array
+ */
+ function getPluginInfo() {
+
+ return [
+ 'name' => $this->getPluginName(),
+ 'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.',
+ 'link' => null,
+ ];
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
new file mode 100644
index 000000000..ee53da2c6
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php
@@ -0,0 +1,274 @@
+<?php
+
+namespace Sabre\CalDAV\Subscriptions;
+
+use Sabre\DAV\Collection;
+use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\PropPatch;
+use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\DAVACL\IACL;
+use Sabre\CalDAV\Backend\SubscriptionSupport;
+
+/**
+ * Subscription Node
+ *
+ * This node represents a subscription.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Subscription extends Collection implements ISubscription, IACL {
+
+ /**
+ * caldavBackend
+ *
+ * @var SupportsSubscriptions
+ */
+ protected $caldavBackend;
+
+ /**
+ * subscriptionInfo
+ *
+ * @var array
+ */
+ protected $subscriptionInfo;
+
+ /**
+ * Constructor
+ *
+ * @param SubscriptionSupport $caldavBackend
+ * @param array $calendarInfo
+ */
+ function __construct(SubscriptionSupport $caldavBackend, array $subscriptionInfo) {
+
+ $this->caldavBackend = $caldavBackend;
+ $this->subscriptionInfo = $subscriptionInfo;
+
+ $required = [
+ 'id',
+ 'uri',
+ 'principaluri',
+ 'source',
+ ];
+
+ foreach ($required as $r) {
+ if (!isset($subscriptionInfo[$r])) {
+ throw new \InvalidArgumentException('The ' . $r . ' field is required when creating a subscription node');
+ }
+ }
+
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * This is used to generate the url.
+ *
+ * @return string
+ */
+ function getName() {
+
+ return $this->subscriptionInfo['uri'];
+
+ }
+
+ /**
+ * Returns the last modification time
+ *
+ * @return int
+ */
+ function getLastModified() {
+
+ if (isset($this->subscriptionInfo['lastmodified'])) {
+ return $this->subscriptionInfo['lastmodified'];
+ }
+
+ }
+
+ /**
+ * Deletes the current node
+ *
+ * @return void
+ */
+ function delete() {
+
+ $this->caldavBackend->deleteSubscription(
+ $this->subscriptionInfo['id']
+ );
+
+ }
+
+ /**
+ * Returns an array with all the child nodes
+ *
+ * @return DAV\INode[]
+ */
+ function getChildren() {
+
+ return [];
+
+ }
+
+ /**
+ * Updates properties on this node.
+ *
+ * This method received a PropPatch object, which contains all the
+ * information about the update.
+ *
+ * To update specific properties, call the 'handle' method on this object.
+ * Read the PropPatch documentation for more information.
+ *
+ * @param PropPatch $propPatch
+ * @return void
+ */
+ function propPatch(PropPatch $propPatch) {
+
+ return $this->caldavBackend->updateSubscription(
+ $this->subscriptionInfo['id'],
+ $propPatch
+ );
+
+ }
+
+ /**
+ * Returns a list of properties for this nodes.
+ *
+ * The properties list is a list of propertynames the client requested,
+ * encoded in clark-notation {xmlnamespace}tagname.
+ *
+ * If the array is empty, it means 'all properties' were requested.
+ *
+ * Note that it's fine to liberally give properties back, instead of
+ * conforming to the list of requested properties.
+ * The Server class will filter out the extra.
+ *
+ * @param array $properties
+ * @return void
+ */
+ function getProperties($properties) {
+
+ $r = [];
+
+ foreach ($properties as $prop) {
+
+ switch ($prop) {
+ case '{http://calendarserver.org/ns/}source' :
+ $r[$prop] = new Href($this->subscriptionInfo['source'], false);
+ break;
+ default :
+ if (array_key_exists($prop, $this->subscriptionInfo)) {
+ $r[$prop] = $this->subscriptionInfo[$prop];
+ }
+ break;
+ }
+
+ }
+
+ return $r;
+
+ }
+
+ /**
+ * Returns the owner principal.
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getOwner() {
+
+ return $this->subscriptionInfo['principaluri'];
+
+ }
+
+ /**
+ * Returns a group principal.
+ *
+ * This must be a url to a principal, or null if there's no owner
+ *
+ * @return string|null
+ */
+ function getGroup() {
+
+ return null;
+
+ }
+
+ /**
+ * Returns a list of ACE's for this node.
+ *
+ * Each ACE has the following properties:
+ * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
+ * currently the only supported privileges
+ * * 'principal', a url to the principal who owns the node
+ * * 'protected' (optional), indicating that this ACE is not allowed to
+ * be updated.
+ *
+ * @return array
+ */
+ function getACL() {
+
+ return [
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner(),
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}write',
+ 'principal' => $this->getOwner() . '/calendar-proxy-write',
+ 'protected' => true,
+ ],
+ [
+ 'privilege' => '{DAV:}read',
+ 'principal' => $this->getOwner() . '/calendar-proxy-read',
+ 'protected' => true,
+ ]
+ ];
+
+ }
+
+ /**
+ * Updates the ACL.
+ *
+ * This method will receive a list of new ACE's.
+ *
+ * @param array $acl
+ * @return void
+ */
+ function setACL(array $acl) {
+
+ throw new MethodNotAllowed('Changing ACL is not yet supported');
+
+ }
+
+ /**
+ * Returns the list of supported privileges for this node.
+ *
+ * The returned data structure is a list of nested privileges.
+ * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
+ * standard structure.
+ *
+ * If null is returned from this method, the default privilege set is used,
+ * which is fine for most common usecases.
+ *
+ * @return array|null
+ */
+ function getSupportedPrivilegeSet() {
+
+ return null;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php
new file mode 100644
index 000000000..9babcf15c
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Filter;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CalDAV\Plugin;
+use Sabre\VObject\DateTimeParser;
+
+/**
+ * CalendarData parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-data XML
+ * element, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-9.6
+ *
+ * This element is used in three distinct places in the caldav spec, but in
+ * this case, this element class only implements the calendar-data element as
+ * it appears in a DAV:prop element, in a calendar-query or calendar-multiget
+ * REPORT request.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarData implements XmlDeserializable {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar',
+ 'version' => $reader->getAttribute('version') ?: '2.0',
+ ];
+
+ $elems = (array)$reader->parseInnerTree();
+ foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+ case '{' . Plugin::NS_CALDAV . '}expand' :
+
+ $result['expand'] = [
+ 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
+ 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
+ ];
+
+ if (!$result['expand']['start'] || !$result['expand']['end']) {
+ throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data');
+ }
+ if ($result['expand']['end'] <= $result['expand']['start']) {
+ throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data');
+ }
+ break;
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
new file mode 100644
index 000000000..c9b27dbfd
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Filter;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CalDAV\Plugin;
+use Sabre\VObject\DateTimeParser;
+
+/**
+ * CompFilter parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:caldav}comp-filter XML
+ * element, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-9.6
+ *
+ * The result will be spit out as an array.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CompFilter implements XmlDeserializable {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'name' => null,
+ 'is-not-defined' => false,
+ 'comp-filters' => [],
+ 'prop-filters' => [],
+ 'time-range' => false,
+ ];
+
+ $att = $reader->parseAttributes();
+ $result['name'] = $att['name'];
+
+ $elems = $reader->parseInnerTree();
+
+ if (is_array($elems)) foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{' . Plugin::NS_CALDAV . '}comp-filter' :
+ $result['comp-filters'][] = $elem['value'];
+ break;
+ case '{' . Plugin::NS_CALDAV . '}prop-filter' :
+ $result['prop-filters'][] = $elem['value'];
+ break;
+ case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
+ $result['is-not-defined'] = true;
+ break;
+ case '{' . Plugin::NS_CALDAV . '}time-range' :
+ if ($result['name'] === 'VCALENDAR') {
+ throw new BadRequest('You cannot add time-range filters on the VCALENDAR component');
+ }
+ $result['time-range'] = [
+ 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
+ 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
+ ];
+ if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
+ throw new BadRequest('The end-date must be larger than the start-date');
+ }
+ break;
+
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php
new file mode 100644
index 000000000..eb7f564df
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Filter;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * PropFilter parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:caldav}param-filter XML
+ * element, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-9.7.3
+ *
+ * The result will be spit out as an array.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ParamFilter implements XmlDeserializable {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * Important note 2: You are responsible for advancing the reader to the
+ * next element. Not doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'name' => null,
+ 'is-not-defined' => false,
+ 'text-match' => null,
+ ];
+
+ $att = $reader->parseAttributes();
+ $result['name'] = $att['name'];
+
+ $elems = $reader->parseInnerTree();
+
+ if (is_array($elems)) foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
+ $result['is-not-defined'] = true;
+ break;
+ case '{' . Plugin::NS_CALDAV . '}text-match' :
+ $result['text-match'] = [
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
+ 'value' => $elem['value'],
+ ];
+ break;
+
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
new file mode 100644
index 000000000..4c2e1b172
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Filter;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CalDAV\Plugin;
+use Sabre\VObject\DateTimeParser;
+
+/**
+ * PropFilter parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:caldav}prop-filter XML
+ * element, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-9.7.2
+ *
+ * The result will be spit out as an array.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class PropFilter implements XmlDeserializable {
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $result = [
+ 'name' => null,
+ 'is-not-defined' => false,
+ 'param-filters' => [],
+ 'text-match' => null,
+ 'time-range' => false,
+ ];
+
+ $att = $reader->parseAttributes();
+ $result['name'] = $att['name'];
+
+ $elems = $reader->parseInnerTree();
+
+ if (is_array($elems)) foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{' . Plugin::NS_CALDAV . '}param-filter' :
+ $result['param-filters'][] = $elem['value'];
+ break;
+ case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
+ $result['is-not-defined'] = true;
+ break;
+ case '{' . Plugin::NS_CALDAV . '}time-range' :
+ $result['time-range'] = [
+ 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
+ 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
+ ];
+ if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
+ throw new BadRequest('The end-date must be larger than the start-date');
+ }
+ break;
+ case '{' . Plugin::NS_CALDAV . '}text-match' :
+ $result['text-match'] = [
+ 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
+ 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
+ 'value' => $elem['value'],
+ ];
+ break;
+
+ }
+
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
new file mode 100644
index 000000000..7fb022e33
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php
@@ -0,0 +1,307 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Notification;
+
+use Sabre\Xml\Writer;
+use Sabre\CalDAV\SharingPlugin as SharingPlugin;
+use Sabre\CalDAV;
+
+/**
+ * This class represents the cs:invite-notification notification element.
+ *
+ * This element is defined here:
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Invite implements NotificationInterface {
+
+ /**
+ * A unique id for the message
+ *
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * Timestamp of the notification
+ *
+ * @var DateTime
+ */
+ protected $dtStamp;
+
+ /**
+ * A url to the recipient of the notification. This can be an email
+ * address (mailto:), or a principal url.
+ *
+ * @var string
+ */
+ protected $href;
+
+ /**
+ * The type of message, see the SharingPlugin::STATUS_* constants.
+ *
+ * @var int
+ */
+ protected $type;
+
+ /**
+ * True if access to a calendar is read-only.
+ *
+ * @var bool
+ */
+ protected $readOnly;
+
+ /**
+ * A url to the shared calendar.
+ *
+ * @var string
+ */
+ protected $hostUrl;
+
+ /**
+ * Url to the sharer of the calendar
+ *
+ * @var string
+ */
+ protected $organizer;
+
+ /**
+ * The name of the sharer.
+ *
+ * @var string
+ */
+ protected $commonName;
+
+ /**
+ * The name of the sharer.
+ *
+ * @var string
+ */
+ protected $firstName;
+
+ /**
+ * The name of the sharer.
+ *
+ * @var string
+ */
+ protected $lastName;
+
+ /**
+ * A description of the share request
+ *
+ * @var string
+ */
+ protected $summary;
+
+ /**
+ * The Etag for the notification
+ *
+ * @var string
+ */
+ protected $etag;
+
+ /**
+ * The list of supported components
+ *
+ * @var Sabre\CalDAV\Property\SupportedCalendarComponentSet
+ */
+ protected $supportedComponents;
+
+ /**
+ * Creates the Invite notification.
+ *
+ * This constructor receives an array with the following elements:
+ *
+ * * id - A unique id
+ * * etag - The etag
+ * * dtStamp - A DateTime object with a timestamp for the notification.
+ * * type - The type of notification, see SharingPlugin::STATUS_*
+ * constants for details.
+ * * readOnly - This must be set to true, if this is an invite for
+ * read-only access to a calendar.
+ * * hostUrl - A url to the shared calendar.
+ * * organizer - Url to the sharer principal.
+ * * commonName - The real name of the sharer (optional).
+ * * firstName - The first name of the sharer (optional).
+ * * lastName - The last name of the sharer (optional).
+ * * summary - Description of the share, can be the same as the
+ * calendar, but may also be modified (optional).
+ * * supportedComponents - An instance of
+ * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
+ * This allows the client to determine which components
+ * will be supported in the shared calendar. This is
+ * also optional.
+ *
+ * @param array $values All the options
+ */
+ function __construct(array $values) {
+
+ $required = [
+ 'id',
+ 'etag',
+ 'href',
+ 'dtStamp',
+ 'type',
+ 'readOnly',
+ 'hostUrl',
+ 'organizer',
+ ];
+ foreach ($required as $item) {
+ if (!isset($values[$item])) {
+ throw new \InvalidArgumentException($item . ' is a required constructor option');
+ }
+ }
+
+ foreach ($values as $key => $value) {
+ if (!property_exists($this, $key)) {
+ throw new \InvalidArgumentException('Unknown option: ' . $key);
+ }
+ $this->$key = $value;
+ }
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-notification');
+
+ }
+
+ /**
+ * This method serializes the entire notification, as it is used in the
+ * response body.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerializeFull(Writer $writer) {
+
+ $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}';
+
+ $this->dtStamp->setTimezone(new \DateTimezone('GMT'));
+ $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
+
+ $writer->startElement($cs . 'invite-notification');
+
+ $writer->writeElement($cs . 'uid', $this->id);
+ $writer->writeElement('{DAV:}href', $this->href);
+
+ switch ($this->type) {
+
+ case SharingPlugin::STATUS_ACCEPTED :
+ $writer->writeElement($cs . 'invite-accepted');
+ break;
+ case SharingPlugin::STATUS_DECLINED :
+ $writer->writeElement($cs . 'invite-declined');
+ break;
+ case SharingPlugin::STATUS_DELETED :
+ $writer->writeElement($cs . 'invite-deleted');
+ break;
+ case SharingPlugin::STATUS_NORESPONSE :
+ $writer->writeElement($cs . 'invite-noresponse');
+ break;
+
+ }
+
+ $writer->writeElement($cs . 'hosturl', [
+ '{DAV:}href' => $writer->contextUri . $this->hostUrl
+ ]);
+
+ if ($this->summary) {
+ $writer->writeElement($cs . 'summary', $this->summary);
+ }
+
+ $writer->startElement($cs . 'access');
+ if ($this->readOnly) {
+ $writer->writeElement($cs . 'read');
+ } else {
+ $writer->writeElement($cs . 'read-write');
+ }
+ $writer->endElement(); // access
+
+ $writer->startElement($cs . 'organizer');
+ // If the organizer contains a 'mailto:' part, it means it should be
+ // treated as absolute.
+ if (strtolower(substr($this->organizer, 0, 7)) === 'mailto:') {
+ $writer->writeElement('{DAV:}href', $this->organizer);
+ } else {
+ $writer->writeElement('{DAV:}href', $writer->contextUri . $this->organizer);
+ }
+ if ($this->commonName) {
+ $writer->writeElement($cs . 'common-name', $this->commonName);
+ }
+ if ($this->firstName) {
+ $writer->writeElement($cs . 'first-name', $this->firstName);
+ }
+ if ($this->lastName) {
+ $writer->writeElement($cs . 'last-name', $this->lastName);
+ }
+ $writer->endElement(); // organizer
+
+ if ($this->commonName) {
+ $writer->writeElement($cs . 'organizer-cn', $this->commonName);
+ }
+ if ($this->firstName) {
+ $writer->writeElement($cs . 'organizer-first', $this->firstName);
+ }
+ if ($this->lastName) {
+ $writer->writeElement($cs . 'organizer-last', $this->lastName);
+ }
+ if ($this->supportedComponents) {
+ $writer->writeElement('{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set', $this->supportedComponents);
+ }
+
+ $writer->endElement(); // invite-notification
+
+ }
+
+ /**
+ * Returns a unique id for this notification
+ *
+ * This is just the base url. This should generally be some kind of unique
+ * id.
+ *
+ * @return string
+ */
+ function getId() {
+
+ return $this->id;
+
+ }
+
+ /**
+ * Returns the ETag for this notification.
+ *
+ * The ETag must be surrounded by literal double-quotes.
+ *
+ * @return string
+ */
+ function getETag() {
+
+ return $this->etag;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
new file mode 100644
index 000000000..945323fed
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Notification;
+
+use Sabre\Xml\Writer;
+use Sabre\CalDAV;
+use Sabre\CalDAV\SharingPlugin;
+
+/**
+ * This class represents the cs:invite-reply notification element.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class InviteReply implements NotificationInterface {
+
+ /**
+ * A unique id for the message
+ *
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * Timestamp of the notification
+ *
+ * @var DateTime
+ */
+ protected $dtStamp;
+
+ /**
+ * The unique id of the notification this was a reply to.
+ *
+ * @var string
+ */
+ protected $inReplyTo;
+
+ /**
+ * A url to the recipient of the original (!) notification.
+ *
+ * @var string
+ */
+ protected $href;
+
+ /**
+ * The type of message, see the SharingPlugin::STATUS_ constants.
+ *
+ * @var int
+ */
+ protected $type;
+
+ /**
+ * A url to the shared calendar.
+ *
+ * @var string
+ */
+ protected $hostUrl;
+
+ /**
+ * A description of the share request
+ *
+ * @var string
+ */
+ protected $summary;
+
+ /**
+ * Notification Etag
+ *
+ * @var string
+ */
+ protected $etag;
+
+ /**
+ * Creates the Invite Reply Notification.
+ *
+ * This constructor receives an array with the following elements:
+ *
+ * * id - A unique id
+ * * etag - The etag
+ * * dtStamp - A DateTime object with a timestamp for the notification.
+ * * inReplyTo - This should refer to the 'id' of the notification
+ * this is a reply to.
+ * * type - The type of notification, see SharingPlugin::STATUS_*
+ * constants for details.
+ * * hostUrl - A url to the shared calendar.
+ * * summary - Description of the share, can be the same as the
+ * calendar, but may also be modified (optional).
+ *
+ * @param array $values
+ */
+ function __construct(array $values) {
+
+ $required = [
+ 'id',
+ 'etag',
+ 'href',
+ 'dtStamp',
+ 'inReplyTo',
+ 'type',
+ 'hostUrl',
+ ];
+ foreach ($required as $item) {
+ if (!isset($values[$item])) {
+ throw new \InvalidArgumentException($item . ' is a required constructor option');
+ }
+ }
+
+ foreach ($values as $key => $value) {
+ if (!property_exists($this, $key)) {
+ throw new \InvalidArgumentException('Unknown option: ' . $key);
+ }
+ $this->$key = $value;
+ }
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-reply');
+
+ }
+
+ /**
+ * This method serializes the entire notification, as it is used in the
+ * response body.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerializeFull(Writer $writer) {
+
+ $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}';
+
+ $this->dtStamp->setTimezone(new \DateTimezone('GMT'));
+ $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));
+
+ $writer->startElement($cs . 'invite-reply');
+
+ $writer->writeElement($cs . 'uid', $this->id);
+ $writer->writeElement($cs . 'in-reply-to', $this->inReplyTo);
+ $writer->writeElement('{DAV:}href', $this->href);
+
+ switch ($this->type) {
+
+ case SharingPlugin::STATUS_ACCEPTED :
+ $writer->writeElement($cs . 'invite-accepted');
+ break;
+ case SharingPlugin::STATUS_DECLINED :
+ $writer->writeElement($cs . 'invite-declined');
+ break;
+
+ }
+
+ $writer->writeElement($cs . 'hosturl', [
+ '{DAV:}href' => $writer->contextUri . $this->hostUrl
+ ]);
+
+ if ($this->summary) {
+ $writer->writeElement($cs . 'summary', $this->summary);
+ }
+ $writer->endElement(); // invite-reply
+
+ }
+
+ /**
+ * Returns a unique id for this notification
+ *
+ * This is just the base url. This should generally be some kind of unique
+ * id.
+ *
+ * @return string
+ */
+ function getId() {
+
+ return $this->id;
+
+ }
+
+ /**
+ * Returns the ETag for this notification.
+ *
+ * The ETag must be surrounded by literal double-quotes.
+ *
+ * @return string
+ */
+ function getETag() {
+
+ return $this->etag;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php
new file mode 100644
index 000000000..1c08f12fd
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Notification;
+
+use Sabre\Xml\XmlSerializable;
+use Sabre\Xml\Writer;
+
+/**
+ * This interface reflects a single notification type.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+interface NotificationInterface extends XmlSerializable {
+
+ /**
+ * This method serializes the entire notification, as it is used in the
+ * response body.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerializeFull(Writer $writer);
+
+ /**
+ * Returns a unique id for this notification
+ *
+ * This is just the base url. This should generally be some kind of unique
+ * id.
+ *
+ * @return string
+ */
+ function getId();
+
+ /**
+ * Returns the ETag for this notification.
+ *
+ * The ETag must be surrounded by literal double-quotes.
+ *
+ * @return string
+ */
+ function getETag();
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php
new file mode 100644
index 000000000..d41702e07
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Notification;
+
+use Sabre\Xml\Writer;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * SystemStatus notification
+ *
+ * This notification can be used to indicate to the user that the system is
+ * down.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SystemStatus implements NotificationInterface {
+
+ const TYPE_LOW = 1;
+ const TYPE_MEDIUM = 2;
+ const TYPE_HIGH = 3;
+
+ /**
+ * A unique id
+ *
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * The type of alert. This should be one of the TYPE_ constants.
+ *
+ * @var int
+ */
+ protected $type;
+
+ /**
+ * A human-readable description of the problem.
+ *
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * A url to a website with more information for the user.
+ *
+ * @var string
+ */
+ protected $href;
+
+ /**
+ * Notification Etag
+ *
+ * @var string
+ */
+ protected $etag;
+
+ /**
+ * Creates the notification.
+ *
+ * Some kind of unique id should be provided. This is used to generate a
+ * url.
+ *
+ * @param string $id
+ * @param string $etag
+ * @param int $type
+ * @param string $description
+ * @param string $href
+ */
+ function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null) {
+
+ $this->id = $id;
+ $this->type = $type;
+ $this->description = $description;
+ $this->href = $href;
+ $this->etag = $etag;
+
+ }
+
+ /**
+ * The serialize method is called during xml writing.
+ *
+ * It should use the $writer argument to encode this object into XML.
+ *
+ * Important note: it is not needed to create the parent element. The
+ * parent element is already created, and we only have to worry about
+ * attributes, child elements and text (if any).
+ *
+ * Important note 2: If you are writing any new elements, you are also
+ * responsible for closing them.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ switch ($this->type) {
+ case self::TYPE_LOW :
+ $type = 'low';
+ break;
+ case self::TYPE_MEDIUM :
+ $type = 'medium';
+ break;
+ default :
+ case self::TYPE_HIGH :
+ $type = 'high';
+ break;
+ }
+
+ $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}systemstatus');
+ $writer->writeAttribute('type', $type);
+ $writer->endElement();
+
+ }
+
+ /**
+ * This method serializes the entire notification, as it is used in the
+ * response body.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerializeFull(Writer $writer) {
+
+ $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
+ switch ($this->type) {
+ case self::TYPE_LOW :
+ $type = 'low';
+ break;
+ case self::TYPE_MEDIUM :
+ $type = 'medium';
+ break;
+ default :
+ case self::TYPE_HIGH :
+ $type = 'high';
+ break;
+ }
+
+ $writer->startElement($cs . 'systemstatus');
+ $writer->writeAttribute('type', $type);
+
+
+ if ($this->description) {
+ $writer->writeElement($cs . 'description', $this->description);
+ }
+ if ($this->href) {
+ $writer->writeElement('{DAV:}href', $this->href);
+ }
+
+ $writer->endElement(); // systemstatus
+
+ }
+
+ /**
+ * Returns a unique id for this notification
+ *
+ * This is just the base url. This should generally be some kind of unique
+ * id.
+ *
+ * @return string
+ */
+ function getId() {
+
+ return $this->id;
+
+ }
+
+ /*
+ * Returns the ETag for this notification.
+ *
+ * The ETag must be surrounded by literal double-quotes.
+ *
+ * @return string
+ */
+ function getETag() {
+
+ return $this->etag;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php
new file mode 100644
index 000000000..c2a2d565e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\XmlSerializable;
+use Sabre\Xml\Writer;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * AllowedSharingModes
+ *
+ * This property encodes the 'allowed-sharing-modes' property, as defined by
+ * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
+ * namespace.
+ *
+ * This property is a representation of the supported-calendar_component-set
+ * property in the CalDAV namespace. It simply requires an array of components,
+ * such as VEVENT, VTODO
+ *
+ * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class AllowedSharingModes implements XmlSerializable {
+
+ /**
+ * Whether or not a calendar can be shared with another user
+ *
+ * @var bool
+ */
+ protected $canBeShared;
+
+ /**
+ * Whether or not the calendar can be placed on a public url.
+ *
+ * @var bool
+ */
+ protected $canBePublished;
+
+ /**
+ * Constructor
+ *
+ * @param bool $canBeShared
+ * @param bool $canBePublished
+ * @return void
+ */
+ function __construct($canBeShared, $canBePublished) {
+
+ $this->canBeShared = $canBeShared;
+ $this->canBePublished = $canBePublished;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ if ($this->canBeShared) {
+ $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-shared');
+ }
+ if ($this->canBePublished) {
+ $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-published');
+ }
+
+ }
+
+
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php
new file mode 100644
index 000000000..f577a9919
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\Writer;
+use Sabre\Xml\XmlSerializable;
+
+/**
+ * email-address-set property
+ *
+ * This property represents the email-address-set property in the
+ * http://calendarserver.org/ns/ namespace.
+ *
+ * It's a list of email addresses associated with a user.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class EmailAddressSet implements XmlSerializable {
+
+ /**
+ * emails
+ *
+ * @var array
+ */
+ private $emails;
+
+ /**
+ * __construct
+ *
+ * @param array $emails
+ */
+ function __construct(array $emails) {
+
+ $this->emails = $emails;
+
+ }
+
+ /**
+ * Returns the email addresses
+ *
+ * @return array
+ */
+ function getValue() {
+
+ return $this->emails;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ foreach ($this->emails as $email) {
+
+ $writer->writeElement('{http://calendarserver.org/ns/}email-address', $email);
+
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
new file mode 100644
index 000000000..3ee053214
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php
@@ -0,0 +1,252 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\Element;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Writer;
+use Sabre\CalDAV\Plugin;
+use Sabre\CalDAV\SharingPlugin;
+
+/**
+ * Invite property
+ *
+ * This property encodes the 'invite' property, as defined by
+ * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
+ * namespace.
+ *
+ * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Invite implements Element {
+
+ /**
+ * The list of users a calendar has been shared to.
+ *
+ * @var array
+ */
+ protected $users;
+
+ /**
+ * The organizer contains information about the person who shared the
+ * object.
+ *
+ * @var array
+ */
+ protected $organizer;
+
+ /**
+ * Creates the property.
+ *
+ * Users is an array. Each element of the array has the following
+ * properties:
+ *
+ * * href - Often a mailto: address
+ * * commonName - Optional, for example a first and lastname for a user.
+ * * status - One of the SharingPlugin::STATUS_* constants.
+ * * readOnly - true or false
+ * * summary - Optional, description of the share
+ *
+ * The organizer key is optional to specify. It's only useful when a
+ * 'sharee' requests the sharing information.
+ *
+ * The organizer may have the following properties:
+ * * href - Often a mailto: address.
+ * * commonName - Optional human-readable name.
+ * * firstName - Optional first name.
+ * * lastName - Optional last name.
+ *
+ * If you wonder why these two structures are so different, I guess a
+ * valid answer is that the current spec is still a draft.
+ *
+ * @param array $users
+ */
+ function __construct(array $users, array $organizer = null) {
+
+ $this->users = $users;
+ $this->organizer = $organizer;
+
+ }
+
+ /**
+ * Returns the list of users, as it was passed to the constructor.
+ *
+ * @return array
+ */
+ function getValue() {
+
+ return $this->users;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
+
+ if (!is_null($this->organizer)) {
+
+ $writer->startElement($cs . 'organizer');
+ $writer->writeElement('{DAV:}href', $this->organizer['href']);
+
+ if (isset($this->organizer['commonName']) && $this->organizer['commonName']) {
+ $writer->writeElement($cs . 'common-name', $this->organizer['commonName']);
+ }
+ if (isset($this->organizer['firstName']) && $this->organizer['firstName']) {
+ $writer->writeElement($cs . 'first-name', $this->organizer['firstName']);
+ }
+ if (isset($this->organizer['lastName']) && $this->organizer['lastName']) {
+ $writer->writeElement($cs . 'last-name', $this->organizer['lastName']);
+ }
+ $writer->endElement(); // organizer
+
+ }
+
+ foreach ($this->users as $user) {
+
+ $writer->startElement($cs . 'user');
+ $writer->writeElement('{DAV:}href', $user['href']);
+ if (isset($user['commonName']) && $user['commonName']) {
+ $writer->writeElement($cs . 'common-name', $user['commonName']);
+ }
+ switch ($user['status']) {
+
+ case SharingPlugin::STATUS_ACCEPTED :
+ $writer->writeElement($cs . 'invite-accepted');
+ break;
+ case SharingPlugin::STATUS_DECLINED :
+ $writer->writeElement($cs . 'invite-declined');
+ break;
+ case SharingPlugin::STATUS_NORESPONSE :
+ $writer->writeElement($cs . 'invite-noresponse');
+ break;
+ case SharingPlugin::STATUS_INVALID :
+ $writer->writeElement($cs . 'invite-invalid');
+ break;
+ }
+
+ $writer->startElement($cs . 'access');
+ if ($user['readOnly']) {
+ $writer->writeElement($cs . 'read');
+ } else {
+ $writer->writeElement($cs . 'read-write');
+ }
+ $writer->endElement(); // access
+
+ if (isset($user['summary']) && $user['summary']) {
+ $writer->writeElement($cs . 'summary', $user['summary']);
+ }
+
+ $writer->endElement(); //user
+
+ }
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
+
+ $users = [];
+
+ foreach ($reader->parseInnerTree() as $elem) {
+
+ if ($elem['name'] !== $cs . 'user')
+ continue;
+
+ $user = [
+ 'href' => null,
+ 'commonName' => null,
+ 'readOnly' => null,
+ 'summary' => null,
+ 'status' => null,
+ ];
+
+ foreach ($elem['value'] as $userElem) {
+
+ switch ($userElem['name']) {
+ case $cs . 'invite-accepted' :
+ $user['status'] = SharingPlugin::STATUS_ACCEPTED;
+ break;
+ case $cs . 'invite-declined' :
+ $user['status'] = SharingPlugin::STATUS_DECLINED;
+ break;
+ case $cs . 'invite-noresponse' :
+ $user['status'] = SharingPlugin::STATUS_NORESPONSE;
+ break;
+ case $cs . 'invite-invalid' :
+ $user['status'] = SharingPlugin::STATUS_INVALID;
+ break;
+ case '{DAV:}href' :
+ $user['href'] = $userElem['value'];
+ break;
+ case $cs . 'common-name' :
+ $user['commonName'] = $userElem['value'];
+ break;
+ case $cs . 'access' :
+ foreach ($userElem['value'] as $accessHref) {
+ if ($accessHref['name'] === $cs . 'read') {
+ $user['readOnly'] = true;
+ }
+ }
+ break;
+ case $cs . 'summary' :
+ $user['summary'] = $userElem['value'];
+ break;
+
+ }
+
+ }
+ if (!$user['status']) {
+ throw new \InvalidArgumentException('Every user must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid');
+ }
+
+ $users[] = $user;
+
+ }
+
+ return new self($users);
+
+ }
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php
new file mode 100644
index 000000000..4a253e008
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\Element;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Writer;
+use Sabre\Xml\Element\Elements;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * schedule-calendar-transp property.
+ *
+ * This property is a representation of the schedule-calendar-transp property.
+ * This property is defined in:
+ *
+ * http://tools.ietf.org/html/rfc6638#section-9.1
+ *
+ * Its values are either 'transparent' or 'opaque'. If it's transparent, it
+ * means that this calendar will not be taken into consideration when a
+ * different user queries for free-busy information. If it's 'opaque', it will.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class ScheduleCalendarTransp implements Element {
+
+ const TRANSPARENT = 'transparent';
+ const OPAQUE = 'opaque';
+
+ /**
+ * value
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * Creates the property
+ *
+ * @param string $value
+ */
+ function __construct($value) {
+
+ if ($value !== self::TRANSPARENT && $value !== self::OPAQUE) {
+ throw new \InvalidArgumentException('The value must either be specified as "transparent" or "opaque"');
+ }
+ $this->value = $value;
+
+ }
+
+ /**
+ * Returns the current value
+ *
+ * @return string
+ */
+ function getValue() {
+
+ return $this->value;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ switch ($this->value) {
+ case self::TRANSPARENT :
+ $writer->writeElement('{' . Plugin::NS_CALDAV . '}transparent');
+ break;
+ case self::OPAQUE :
+ $writer->writeElement('{' . Plugin::NS_CALDAV . '}opaque');
+ break;
+ }
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = Elements::xmlDeserialize($reader);
+
+ $value = null;
+
+ foreach ($elems as $elem) {
+ switch ($elem) {
+ case '{' . Plugin::NS_CALDAV . '}opaque' :
+ $value = self::OPAQUE;
+ break;
+ case '{' . Plugin::NS_CALDAV . '}transparent' :
+ $value = self::TRANSPARENT;
+ break;
+ }
+ }
+ if (is_null($value))
+ return null;
+
+ return new self($value);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php
new file mode 100644
index 000000000..7a26e767e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\Element;
+use Sabre\Xml\ParseException;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Writer;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * SupportedCalendarComponentSet property.
+ *
+ * This class represents the
+ * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set property, as
+ * defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-5.2.3
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SupportedCalendarComponentSet implements Element {
+
+ /**
+ * List of supported components.
+ *
+ * This array will contain values such as VEVENT, VTODO and VJOURNAL.
+ *
+ * @var array
+ */
+ protected $components = [];
+
+ /**
+ * Creates the property.
+ *
+ * @param array $components
+ */
+ function __construct(array $components) {
+
+ $this->components = $components;
+
+ }
+
+ /**
+ * Returns the list of supported components
+ *
+ * @return array
+ */
+ function getValue() {
+
+ return $this->components;
+
+ }
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ foreach ($this->components as $component) {
+
+ $writer->startElement('{' . Plugin::NS_CALDAV . '}comp');
+ $writer->writeAttributes(['name' => $component]);
+ $writer->endElement();
+
+ }
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree();
+
+ $components = [];
+
+ foreach ((array)$elems as $elem) {
+ if ($elem['name'] === '{' . Plugin::NS_CALDAV . '}comp') {
+ $components[] = $elem['attributes']['name'];
+ }
+ }
+
+ if (!$components) {
+ throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element');
+ }
+
+ return new self($components);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php
new file mode 100644
index 000000000..b0c407fd6
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\XmlSerializable;
+use Sabre\Xml\Writer;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * Supported-calendar-data property
+ *
+ * This property is a representation of the supported-calendar-data property
+ * in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0
+ * so the value is currently hardcoded.
+ *
+ * This property is defined in:
+ * http://tools.ietf.org/html/rfc4791#section-5.2.4
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SupportedCalendarData implements XmlSerializable {
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data');
+ $writer->writeAttributes([
+ 'content-type' => 'text/calendar',
+ 'version' => '2.0',
+ ]);
+ $writer->endElement(); // calendar-data
+ $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data');
+ $writer->writeAttributes([
+ 'content-type' => 'application/calendar+json',
+ ]);
+ $writer->endElement(); // calendar-data
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php
new file mode 100644
index 000000000..71de25a62
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Property;
+
+use Sabre\Xml\Writer;
+use Sabre\Xml\XmlSerializable;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * supported-collation-set property
+ *
+ * This property is a representation of the supported-collation-set property
+ * in the CalDAV namespace.
+ *
+ * This property is defined in:
+ * http://tools.ietf.org/html/rfc4791#section-7.5.1
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class SupportedCollationSet implements XmlSerializable {
+
+ /**
+ * The xmlSerialize metod is called during xml writing.
+ *
+ * Use the $writer argument to write its own xml serialization.
+ *
+ * An important note: do _not_ create a parent element. Any element
+ * implementing XmlSerializble should only ever write what's considered
+ * its 'inner xml'.
+ *
+ * The parent of the current element is responsible for writing a
+ * containing element.
+ *
+ * This allows serializers to be re-used for different element names.
+ *
+ * If you are opening new elements, you must also close them again.
+ *
+ * @param Writer $writer
+ * @return void
+ */
+ function xmlSerialize(Writer $writer) {
+
+ $collations = [
+ 'i;ascii-casemap',
+ 'i;octet',
+ 'i;unicode-casemap'
+ ];
+
+ foreach ($collations as $collation) {
+ $writer->writeElement('{' . Plugin::NS_CALDAV . '}supported-collation', $collation);
+ }
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
new file mode 100644
index 000000000..79b3bb3ac
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Request;
+
+use Sabre\Xml\XmlDeserializable;
+use Sabre\Xml\Reader;
+use Sabre\CalDAV\Plugin;
+use Sabre\Uri;
+
+/**
+ * CalendarMultiGetReport request parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-multiget
+ * REPORT, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-7.9
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarMultiGetReport implements XmlDeserializable {
+
+ /**
+ * An array with requested properties.
+ *
+ * @var array
+ */
+ public $properties;
+
+ /**
+ * This is an array with the urls that are being requested.
+ *
+ * @var array
+ */
+ public $hrefs;
+
+ /**
+ * If the calendar data must be expanded, this will contain an array with 2
+ * elements: start and end.
+ *
+ * Each may be a DateTime or null.
+ *
+ * @var array|null
+ */
+ public $expand = null;
+
+ /**
+ * The mimetype of the content that should be returend. Usually
+ * text/calendar.
+ *
+ * @var string
+ */
+ public $contentType = null;
+
+ /**
+ * The version of calendar-data that should be returned. Usually '2.0',
+ * referring to iCalendar 2.0.
+ *
+ * @var string
+ */
+ public $version = null;
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'hrefs' => [],
+ 'properties' => [],
+ ];
+
+ foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{DAV:}prop' :
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
+ $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'];
+ }
+ break;
+ case '{DAV:}href' :
+ $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
+ break;
+
+ }
+
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+
+ return $obj;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php
new file mode 100644
index 000000000..848a4dc46
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Request;
+
+use Sabre\Xml\XmlDeserializable;
+use Sabre\Xml\Reader;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * CalendarQueryReport request parser.
+ *
+ * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-query
+ * REPORT, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-7.9
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class CalendarQueryReport implements XmlDeserializable {
+
+ /**
+ * An array with requested properties.
+ *
+ * @var array
+ */
+ public $properties;
+
+ /**
+ * List of property/component filters.
+ *
+ * @var array
+ */
+ public $filters;
+
+ /**
+ * If the calendar data must be expanded, this will contain an array with 2
+ * elements: start and end.
+ *
+ * Each may be a DateTime or null.
+ *
+ * @var array|null
+ */
+ public $expand = null;
+
+ /**
+ * The mimetype of the content that should be returend. Usually
+ * text/calendar.
+ *
+ * @var string
+ */
+ public $contentType = null;
+
+ /**
+ * The version of calendar-data that should be returned. Usually '2.0',
+ * referring to iCalendar 2.0.
+ *
+ * @var string
+ */
+ public $version = null;
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree([
+ '{urn:ietf:params:xml:ns:caldav}comp-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter',
+ '{urn:ietf:params:xml:ns:caldav}prop-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter',
+ '{urn:ietf:params:xml:ns:caldav}param-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter',
+ '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
+ '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $newProps = [
+ 'filters' => null,
+ 'properties' => [],
+ ];
+
+ if (!is_array($elems)) $elems = [];
+
+ foreach ($elems as $elem) {
+
+ switch ($elem['name']) {
+
+ case '{DAV:}prop' :
+ $newProps['properties'] = array_keys($elem['value']);
+ if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
+ $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'];
+ }
+ break;
+ case '{' . Plugin::NS_CALDAV . '}filter' :
+ foreach ($elem['value'] as $subElem) {
+ if ($subElem['name'] === '{' . Plugin::NS_CALDAV . '}comp-filter') {
+ if (!is_null($newProps['filters'])) {
+ throw new BadRequest('Only one top-level comp-filter may be defined');
+ }
+ $newProps['filters'] = $subElem['value'];
+ }
+ }
+ break;
+
+ }
+
+ }
+
+ if (is_null($newProps['filters'])) {
+ throw new BadRequest('The {' . Plugin::NS_CALDAV . '}filter element is required for this request');
+ }
+
+ $obj = new self();
+ foreach ($newProps as $key => $value) {
+ $obj->$key = $value;
+ }
+ return $obj;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php
new file mode 100644
index 000000000..e3b27d08e
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Request;
+
+use Sabre\CalDAV\Plugin;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\VObject\DateTimeParser;
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+
+/**
+ * FreeBusyQueryReport
+ *
+ * This class parses the {DAV:}free-busy-query REPORT, as defined in:
+ *
+ * http://tools.ietf.org/html/rfc3253#section-3.8
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class FreeBusyQueryReport implements XmlDeserializable {
+
+ /**
+ * Starttime of report
+ *
+ * @var DateTime|null
+ */
+ public $start;
+
+ /**
+ * End time of report
+ *
+ * @var DateTime|null
+ */
+ public $end;
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $timeRange = '{' . Plugin::NS_CALDAV . '}time-range';
+
+ $start = null;
+ $end = null;
+
+ foreach ((array)$reader->parseInnerTree([]) as $elem) {
+
+ if ($elem['name'] !== $timeRange) continue;
+
+ $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start'];
+ $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end'];
+
+ }
+ if (!$start && !$end) {
+ throw new BadRequest('The freebusy report must have a time-range element');
+ }
+ if ($start) {
+ $start = DateTimeParser::parseDateTime($start);
+ }
+ if ($end) {
+ $end = DateTimeParser::parseDateTime($end);
+ }
+ $result = new self();
+ $result->start = $start;
+ $result->end = $end;
+
+ return $result;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
new file mode 100644
index 000000000..ec627156f
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Request;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\Xml\Element\KeyValue;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\CalDAV\Plugin;
+use Sabre\CalDAV\SharingPlugin;
+
+/**
+ * Invite-reply POST request parser
+ *
+ * This class parses the invite-reply POST request, as defined in:
+ *
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class InviteReply implements XmlDeserializable {
+
+ /**
+ * The sharee calendar user address.
+ *
+ * This is the address that the original invite was set to
+ *
+ * @var string
+ */
+ public $href;
+
+ /**
+ * The uri to the calendar that was being shared.
+ *
+ * @var string
+ */
+ public $calendarUri;
+
+ /**
+ * The id of the invite message that's being responded to
+ *
+ * @var string
+ */
+ public $inReplyTo;
+
+ /**
+ * An optional message
+ *
+ * @var string
+ */
+ public $summary;
+
+ /**
+ * Either SharingPlugin::STATUS_ACCEPTED or SharingPlugin::STATUS_DECLINED.
+ *
+ * @var int
+ */
+ public $status;
+
+ /**
+ * Constructor
+ *
+ * @param string $href
+ * @param string $calendarUri
+ * @param string $inReplyTo
+ * @param string $summary
+ * @param int $status
+ */
+ function __construct($href, $calendarUri, $inReplyTo, $summary, $status) {
+
+ $this->href = $href;
+ $this->calendarUri = $calendarUri;
+ $this->inReplyTo = $inReplyTo;
+ $this->summary = $summary;
+ $this->status = $status;
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = KeyValue::xmlDeserialize($reader);
+
+ $href = null;
+ $calendarUri = null;
+ $inReplyTo = null;
+ $summary = null;
+ $status = null;
+
+ foreach ($elems as $name => $value) {
+
+ switch ($name) {
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}hosturl' :
+ foreach ($value as $bla) {
+ if ($bla['name'] === '{DAV:}href') {
+ $calendarUri = $bla['value'];
+ }
+ }
+ break;
+ case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' :
+ $status = SharingPlugin::STATUS_ACCEPTED;
+ break;
+ case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' :
+ $status = SharingPlugin::STATUS_DECLINED;
+ break;
+ case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' :
+ $inReplyTo = $value;
+ break;
+ case '{' . Plugin::NS_CALENDARSERVER . '}summary' :
+ $summary = $value;
+ break;
+ case '{DAV:}href' :
+ $href = $value;
+ break;
+ }
+
+ }
+ if (is_null($calendarUri)) {
+ throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist');
+ }
+
+ return new self($href, $calendarUri, $inReplyTo, $summary, $status);
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php
new file mode 100644
index 000000000..7b745db55
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Request;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+
+/**
+ * MKCALENDAR parser.
+ *
+ * This class parses the MKCALENDAR request, as defined in:
+ *
+ * https://tools.ietf.org/html/rfc4791#section-5.3.1
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class MkCalendar implements XmlDeserializable {
+
+ /**
+ * The list of properties that will be set.
+ *
+ * @var array
+ */
+ public $properties = [];
+
+ /**
+ * Returns the list of properties the calendar will be initialized with.
+ *
+ * @return array
+ */
+ function getProperties() {
+
+ return $this->properties;
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $self = new self();
+
+ $elementMap = $reader->elementMap;
+ $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
+ $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
+ $elems = $reader->parseInnerTree($elementMap);
+
+ foreach ($elems as $elem) {
+ if ($elem['name'] === '{DAV:}set') {
+ $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
+ }
+ }
+
+ return $self;
+
+ }
+
+}
diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
new file mode 100644
index 000000000..dacc5dc94
--- /dev/null
+++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Sabre\CalDAV\Xml\Request;
+
+use Sabre\Xml\Reader;
+use Sabre\Xml\XmlDeserializable;
+use Sabre\CalDAV\Plugin;
+
+/**
+ * Share POST request parser
+ *
+ * This class parses the share POST request, as defined in:
+ *
+ * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class Share implements XmlDeserializable {
+
+ /**
+ * The list of new people added or updated.
+ *
+ * Every element has the following keys:
+ * 1. href - An email address
+ * 2. commonName - Some name
+ * 3. summary - An optional description of the share
+ * 4. readOnly - true or false
+ *
+ * @var array
+ */
+ public $set = [];
+
+ /**
+ * List of people removed from the share list.
+ *
+ * The list is a flat list of email addresses (including mailto:).
+ *
+ * @var array
+ */
+ public $remove = [];
+
+ /**
+ * Constructor
+ *
+ * @param array $set
+ * @param array $remove
+ */
+ function __construct(array $set, array $remove) {
+
+ $this->set = $set;
+ $this->remove = $remove;
+
+ }
+
+ /**
+ * The deserialize method is called during xml parsing.
+ *
+ * This method is called statictly, this is because in theory this method
+ * may be used as a type of constructor, or factory method.
+ *
+ * Often you want to return an instance of the current class, but you are
+ * free to return other data as well.
+ *
+ * You are responsible for advancing the reader to the next element. Not
+ * doing anything will result in a never-ending loop.
+ *
+ * If you just want to skip parsing for this element altogether, you can
+ * just call $reader->next();
+ *
+ * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
+ * the next element.
+ *
+ * @param Reader $reader
+ * @return mixed
+ */
+ static function xmlDeserialize(Reader $reader) {
+
+ $elems = $reader->parseInnerTree([
+ '{' . Plugin::NS_CALENDARSERVER . '}set' => 'Sabre\\Xml\\Element\\KeyValue',
+ '{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue',
+ ]);
+
+ $set = [];
+ $remove = [];
+
+ foreach ($elems as $elem) {
+ switch ($elem['name']) {
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}set' :
+ $sharee = $elem['value'];
+
+ $sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary';
+ $commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name';
+
+ $set[] = [
+ 'href' => $sharee['{DAV:}href'],
+ 'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null,
+ 'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null,
+ 'readOnly' => !array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee),
+ ];
+ break;
+
+ case '{' . Plugin::NS_CALENDARSERVER . '}remove' :
+ $remove[] = $elem['value']['{DAV:}href'];
+ break;
+
+ }
+ }
+
+ return new self($set, $remove);
+
+ }
+
+}