From b35122f7a6ad42756c35bb60ba1f06c3dcd45c77 Mon Sep 17 00:00:00 2001 From: friendica Date: Mon, 21 Oct 2013 15:46:31 -0700 Subject: add sabre (1.8.x) via composer in the !@#$ place it wants to be --- .../lib/Sabre/CalDAV/Backend/AbstractBackend.php | 155 +++ .../lib/Sabre/CalDAV/Backend/BackendInterface.php | 233 ++++ .../Sabre/CalDAV/Backend/NotificationSupport.php | 47 + vendor/sabre/dav/lib/Sabre/CalDAV/Backend/PDO.php | 691 ++++++++++ .../lib/Sabre/CalDAV/Backend/SharingSupport.php | 243 ++++ vendor/sabre/dav/lib/Sabre/CalDAV/Calendar.php | 376 ++++++ .../sabre/dav/lib/Sabre/CalDAV/CalendarObject.php | 279 ++++ .../dav/lib/Sabre/CalDAV/CalendarQueryParser.php | 298 +++++ .../lib/Sabre/CalDAV/CalendarQueryValidator.php | 392 ++++++ .../dav/lib/Sabre/CalDAV/CalendarRootNode.php | 77 ++ .../CalDAV/Exception/InvalidComponentType.php | 35 + .../sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php | 142 +++ vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php | 36 + .../sabre/dav/lib/Sabre/CalDAV/ICalendarObject.php | 21 + .../dav/lib/Sabre/CalDAV/IShareableCalendar.php | 48 + .../sabre/dav/lib/Sabre/CalDAV/ISharedCalendar.php | 36 + .../lib/Sabre/CalDAV/Notifications/Collection.php | 173 +++ .../lib/Sabre/CalDAV/Notifications/ICollection.php | 24 + .../dav/lib/Sabre/CalDAV/Notifications/INode.php | 38 + .../CalDAV/Notifications/INotificationType.php | 44 + .../dav/lib/Sabre/CalDAV/Notifications/Node.php | 192 +++ .../CalDAV/Notifications/Notification/Invite.php | 324 +++++ .../Notifications/Notification/InviteReply.php | 218 ++++ .../Notifications/Notification/SystemStatus.php | 182 +++ vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php | 1338 ++++++++++++++++++++ .../dav/lib/Sabre/CalDAV/Principal/Collection.php | 32 + .../dav/lib/Sabre/CalDAV/Principal/IProxyRead.php | 19 + .../dav/lib/Sabre/CalDAV/Principal/IProxyWrite.php | 19 + .../dav/lib/Sabre/CalDAV/Principal/ProxyRead.php | 180 +++ .../dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php | 180 +++ .../sabre/dav/lib/Sabre/CalDAV/Principal/User.php | 134 ++ .../Sabre/CalDAV/Property/AllowedSharingModes.php | 74 ++ .../sabre/dav/lib/Sabre/CalDAV/Property/Invite.php | 227 ++++ .../CalDAV/Property/ScheduleCalendarTransp.php | 102 ++ .../Property/SupportedCalendarComponentSet.php | 88 ++ .../CalDAV/Property/SupportedCalendarData.php | 40 + .../CalDAV/Property/SupportedCollationSet.php | 45 + .../sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php | 111 ++ .../dav/lib/Sabre/CalDAV/Schedule/IOutbox.php | 16 + .../sabre/dav/lib/Sabre/CalDAV/Schedule/Outbox.php | 163 +++ .../dav/lib/Sabre/CalDAV/ShareableCalendar.php | 72 ++ .../sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php | 116 ++ .../sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php | 526 ++++++++ .../sabre/dav/lib/Sabre/CalDAV/UserCalendars.php | 342 +++++ vendor/sabre/dav/lib/Sabre/CalDAV/Version.php | 24 + 45 files changed, 8152 insertions(+) create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/PDO.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Calendar.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ICalendarObject.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/IShareableCalendar.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ISharedCalendar.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Collection.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/INode.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/INotificationType.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Node.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyWrite.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyRead.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/Outbox.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php create mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Version.php (limited to 'vendor/sabre/dav/lib/Sabre/CalDAV') diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php new file mode 100644 index 000000000..21f88162b --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php @@ -0,0 +1,155 @@ + array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param mixed $calendarId + * @param array $mutations + * @return bool|array + */ + public function updateCalendar($calendarId, array $mutations) { + + return false; + + } + + /** + * 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 + */ + public function calendarQuery($calendarId, array $filters) { + + $result = array(); + $objects = $this->getCalendarObjects($calendarId); + + $validator = new \Sabre\CalDAV\CalendarQueryValidator(); + + foreach($objects as $object) { + + if ($this->validateFilterForObject($object, $filters)) { + $result[] = $object['uri']; + } + + } + + return $result; + + } + + /** + * This method validates if a filters (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']); + } + + $data = is_resource($object['calendardata'])?stream_get_contents($object['calendardata']):$object['calendardata']; + $vObject = VObject\Reader::read($data); + + $validator = new CalDAV\CalendarQueryValidator(); + return $validator->validate($vObject, $filters); + + } + + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php new file mode 100644 index 000000000..b8334e5d0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php @@ -0,0 +1,233 @@ + array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param mixed $calendarId + * @param array $mutations + * @return bool|array + */ + public function updateCalendar($calendarId, array $mutations); + + /** + * Delete a calendar and all it's objects + * + * @param mixed $calendarId + * @return void + */ + public function deleteCalendar($calendarId); + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * id - unique identifier which will be used for subsequent updates + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * calendarid - The calendarid as it was passed to this function. + * * size - The size of the calendar objects, in bytes. + * + * 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 + */ + public function getCalendarObjects($calendarId); + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * 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 + */ + public function getCalendarObject($calendarId,$objectUri); + + /** + * Creates a new calendar object. + * + * 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 + */ + public function createCalendarObject($calendarId,$objectUri,$calendarData); + + /** + * Updates an existing calendarobject, based on it's uri. + * + * 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 + */ + public function updateCalendarObject($calendarId,$objectUri,$calendarData); + + /** + * Deletes an existing calendar object. + * + * @param mixed $calendarId + * @param string $objectUri + * @return void + */ + public 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 + */ + public function calendarQuery($calendarId, array $filters); + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php new file mode 100644 index 000000000..50021a3e7 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php @@ -0,0 +1,47 @@ + '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', + ); + + /** + * Creates the backend + * + * @param \PDO $pdo + * @param string $calendarTableName + * @param string $calendarObjectTableName + */ + public function __construct(\PDO $pdo, $calendarTableName = 'calendars', $calendarObjectTableName = 'calendarobjects') { + + $this->pdo = $pdo; + $this->calendarTableName = $calendarTableName; + $this->calendarObjectTableName = $calendarObjectTableName; + + } + + /** + * 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 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'. + * + * @param string $principalUri + * @return array + */ + public function getCalendarsForUser($principalUri) { + + $fields = array_values($this->propertyMap); + $fields[] = 'id'; + $fields[] = 'uri'; + $fields[] = 'ctag'; + $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(array($principalUri)); + + $calendars = array(); + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $components = array(); + if ($row['components']) { + $components = explode(',',$row['components']); + } + + $calendar = array( + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $row['ctag']?$row['ctag']:'0', + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Property\SupportedCalendarComponentSet($components), + '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\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 + */ + public function createCalendar($principalUri, $calendarUri, array $properties) { + + $fieldNames = array( + 'principaluri', + 'uri', + 'ctag', + 'transparent', + ); + $values = array( + ':principaluri' => $principalUri, + ':uri' => $calendarUri, + ':ctag' => 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\Property\SupportedCalendarComponentSet)) { + throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\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 mutations array uses the propertyName in clark-notation as key, + * and the array value for the property value. In the case a property + * should be deleted, the property value will be null. + * + * This method must be atomic. If one property cannot be changed, the + * entire operation must fail. + * + * If the operation was successful, true can be returned. + * If the operation failed, false can be returned. + * + * Deletion of a non-existent property is always successful. + * + * Lastly, it is optional to return detailed information about any + * failures. In this case an array should be returned with the following + * structure: + * + * array( + * 403 => array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param string $calendarId + * @param array $mutations + * @return bool|array + */ + public function updateCalendar($calendarId, array $mutations) { + + $newValues = array(); + $result = array( + 200 => array(), // Ok + 403 => array(), // Forbidden + 424 => array(), // Failed Dependency + ); + + $hasError = false; + + foreach($mutations as $propertyName=>$propertyValue) { + + switch($propertyName) { + case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' : + $fieldName = 'transparent'; + $newValues[$fieldName] = $propertyValue->getValue()==='transparent'; + break; + default : + // Checking the property map + if (!isset($this->propertyMap[$propertyName])) { + // We don't know about this property. + $hasError = true; + $result[403][$propertyName] = null; + unset($mutations[$propertyName]); + continue; + } + + $fieldName = $this->propertyMap[$propertyName]; + $newValues[$fieldName] = $propertyValue; + } + + } + + // If there were any errors we need to fail the request + if ($hasError) { + // Properties has the remaining properties + foreach($mutations as $propertyName=>$propertyValue) { + $result[424][$propertyName] = null; + } + + // Removing unused statuscodes for cleanliness + foreach($result as $status=>$properties) { + if (is_array($properties) && count($properties)===0) unset($result[$status]); + } + + return $result; + + } + + // Success + + // Now we're generating the sql query. + $valuesSql = array(); + foreach($newValues as $fieldName=>$value) { + $valuesSql[] = $fieldName . ' = ?'; + } + $valuesSql[] = 'ctag = ctag + 1'; + + $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ',$valuesSql) . " WHERE id = ?"); + $newValues['id'] = $calendarId; + $stmt->execute(array_values($newValues)); + + return true; + + } + + /** + * Delete a calendar and all it's objects + * + * @param string $calendarId + * @return void + */ + public function deleteCalendar($calendarId) { + + $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?'); + $stmt->execute(array($calendarId)); + + $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?'); + $stmt->execute(array($calendarId)); + + } + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * id - unique identifier which will be used for subsequent updates + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * calendarid - The calendarid as it was passed to this function. + * * size - The size of the calendar objects, in bytes. + * + * 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 + */ + public function getCalendarObjects($calendarId) { + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?'); + $stmt->execute(array($calendarId)); + + $result = array(); + foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + $result[] = array( + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'calendarid' => $row['calendarid'], + 'size' => (int)$row['size'], + ); + } + + return $result; + + } + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * 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 + */ + public function getCalendarObject($calendarId,$objectUri) { + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?'); + $stmt->execute(array($calendarId, $objectUri)); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if(!$row) return null; + + return array( + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'calendarid' => $row['calendarid'], + 'size' => (int)$row['size'], + 'calendardata' => $row['calendardata'], + ); + + } + + + /** + * Creates a new calendar object. + * + * 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 + */ + public 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) VALUES (?,?,?,?,?,?,?,?,?)'); + $stmt->execute(array( + $calendarId, + $objectUri, + $calendarData, + time(), + $extraData['etag'], + $extraData['size'], + $extraData['componentType'], + $extraData['firstOccurence'], + $extraData['lastOccurence'], + )); + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?'); + $stmt->execute(array($calendarId)); + + return '"' . $extraData['etag'] . '"'; + + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * 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 + */ + public function updateCalendarObject($calendarId,$objectUri,$calendarData) { + + $extraData = $this->getDenormalizedData($calendarData); + + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE calendarid = ? AND uri = ?'); + $stmt->execute(array($calendarData,time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'] ,$calendarId,$objectUri)); + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?'); + $stmt->execute(array($calendarId)); + + return '"' . $extraData['etag'] . '"'; + + } + + /** + * Parses some information from calendar objects, used for optimized + * calendar-queries. + * + * Returns an array with the following keys: + * * etag + * * size + * * componentType + * * firstOccurence + * * lastOccurence + * + * @param string $calendarData + * @return array + */ + protected function getDenormalizedData($calendarData) { + + $vObject = VObject\Reader::read($calendarData); + $componentType = null; + $component = null; + $firstOccurence = null; + $lastOccurence = null; + foreach($vObject->getComponents() as $component) { + if ($component->name!=='VTIMEZONE') { + $componentType = $component->name; + 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->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); + $lastOccurence = $endDate->getTimeStamp(); + } elseif (!$component->DTSTART->hasTime()) { + $endDate = clone $component->DTSTART->getDateTime(); + $endDate->modify('+1 day'); + $lastOccurence = $endDate->getTimeStamp(); + } else { + $lastOccurence = $firstOccurence; + } + } else { + $it = new VObject\RecurrenceIterator($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(); + } + + } + } + + return array( + 'etag' => md5($calendarData), + 'size' => strlen($calendarData), + 'componentType' => $componentType, + 'firstOccurence' => $firstOccurence, + 'lastOccurence' => $lastOccurence, + ); + + } + + /** + * Deletes an existing calendar object. + * + * @param string $calendarId + * @param string $objectUri + * @return void + */ + public function deleteCalendarObject($calendarId,$objectUri) { + + $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?'); + $stmt->execute(array($calendarId,$objectUri)); + $stmt = $this->pdo->prepare('UPDATE '. $this->calendarTableName .' SET ctag = ctag + 1 WHERE id = ?'); + $stmt->execute(array($calendarId)); + + } + + /** + * 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 + */ + public function calendarQuery($calendarId, array $filters) { + + $result = array(); + $validator = new \Sabre\CalDAV\CalendarQueryValidator(); + + $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 = array( + '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 = array(); + while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if ($requirePostFilter) { + if (!$this->validateFilterForObject($row, $filters)) { + continue; + } + } + $result[] = $row['uri']; + + } + + return $result; + + } +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php new file mode 100644 index 000000000..8fb2b320c --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php @@ -0,0 +1,243 @@ +caldavBackend = $caldavBackend; + $this->calendarInfo = $calendarInfo; + + } + + /** + * Returns the name of the calendar + * + * @return string + */ + public function getName() { + + return $this->calendarInfo['uri']; + + } + + /** + * Updates properties such as the display name and description + * + * @param array $mutations + * @return array + */ + public function updateProperties($mutations) { + + return $this->caldavBackend->updateCalendar($this->calendarInfo['id'],$mutations); + + } + + /** + * Returns the list of properties + * + * @param array $requestedProperties + * @return array + */ + public function getProperties($requestedProperties) { + + $response = array(); + + foreach($requestedProperties as $prop) switch($prop) { + + case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' : + $response[$prop] = new Property\SupportedCalendarData(); + break; + case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' : + $response[$prop] = new Property\SupportedCollationSet(); + break; + case '{DAV:}owner' : + $response[$prop] = new DAVACL\Property\Principal(DAVACL\Property\Principal::HREF,$this->calendarInfo['principaluri']); + break; + default : + if (isset($this->calendarInfo[$prop])) $response[$prop] = $this->calendarInfo[$prop]; + break; + + } + return $response; + + } + + /** + * Returns a calendar object + * + * The contained calendar objects are for example Events or Todo's. + * + * @param string $name + * @return \Sabre\CalDAV\ICalendarObject + */ + public 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->getACL(); + // Removing the irrelivant + foreach($obj['acl'] as $key=>$acl) { + if ($acl['privilege'] === '{' . Plugin::NS_CALDAV . '}read-free-busy') { + unset($obj['acl'][$key]); + } + } + + return new CalendarObject($this->caldavBackend,$this->calendarInfo,$obj); + + } + + /** + * Returns the full list of calendar objects + * + * @return array + */ + public function getChildren() { + + $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); + $children = array(); + foreach($objs as $obj) { + $obj['acl'] = $this->getACL(); + // Removing the irrelivant + foreach($obj['acl'] as $key=>$acl) { + if ($acl['privilege'] === '{' . Plugin::NS_CALDAV . '}read-free-busy') { + unset($obj['acl'][$key]); + } + } + $children[] = new CalendarObject($this->caldavBackend,$this->calendarInfo,$obj); + } + return $children; + + } + + /** + * Checks if a child-node exists. + * + * @param string $name + * @return bool + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getACL() { + + return array( + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), + + ); + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + public 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 + */ + public 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'][] = array( + '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 + */ + public function calendarQuery(array $filters) { + + return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php new file mode 100644 index 000000000..1fbb51b79 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php @@ -0,0 +1,279 @@ +caldavBackend = $caldavBackend; + + if (!isset($objectData['calendarid'])) { + throw new \InvalidArgumentException('The objectData argument must contain a \'calendarid\' property'); + } + 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 + */ + public function getName() { + + return $this->objectData['uri']; + + } + + /** + * Returns the ICalendar-formatted object + * + * @return string + */ + public 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->objectData['calendarid'], $this->objectData['uri']); + } + return $this->objectData['calendardata']; + + } + + /** + * Updates the ICalendar-formatted object + * + * @param string|resource $calendarData + * @return string + */ + public 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 + */ + public function delete() { + + $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'],$this->objectData['uri']); + + } + + /** + * Returns the mime content-type + * + * @return string + */ + public function getContentType() { + + return 'text/calendar; charset=utf-8'; + + } + + /** + * Returns an ETag for this object. + * + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * @return string + */ + public 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 + */ + public function getLastModified() { + + return $this->objectData['lastmodified']; + + } + + /** + * Returns the size of this object in bytes + * + * @return int + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 array( + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ), + array( + '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 + */ + public 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 + */ + public function getSupportedPrivilegeSet() { + + return null; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php new file mode 100644 index 000000000..c894a066e --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php @@ -0,0 +1,298 @@ +dom = $dom; + $this->xpath = new \DOMXPath($dom); + $this->xpath->registerNameSpace('cal',Plugin::NS_CALDAV); + $this->xpath->registerNameSpace('dav','urn:DAV'); + + } + + /** + * Parses the request. + * + * @return void + */ + public function parse() { + + $filterNode = null; + + $filter = $this->xpath->query('/cal:calendar-query/cal:filter'); + if ($filter->length !== 1) { + throw new \Sabre\DAV\Exception\BadRequest('Only one filter element is allowed'); + } + + $compFilters = $this->parseCompFilters($filter->item(0)); + if (count($compFilters)!==1) { + throw new \Sabre\DAV\Exception\BadRequest('There must be exactly 1 top-level comp-filter.'); + } + + $this->filters = $compFilters[0]; + $this->requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($this->dom->firstChild)); + + $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand'); + if ($expand->length>0) { + $this->expand = $this->parseExpand($expand->item(0)); + } + + + } + + /** + * Parses all the 'comp-filter' elements from a node + * + * @param \DOMElement $parentNode + * @return array + */ + protected function parseCompFilters(\DOMElement $parentNode) { + + $compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode); + $result = array(); + + for($ii=0; $ii < $compFilterNodes->length; $ii++) { + + $compFilterNode = $compFilterNodes->item($ii); + + $compFilter = array(); + $compFilter['name'] = $compFilterNode->getAttribute('name'); + $compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0; + $compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode); + $compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode); + $compFilter['time-range'] = $this->parseTimeRange($compFilterNode); + + if ($compFilter['time-range'] && !in_array($compFilter['name'],array( + 'VEVENT', + 'VTODO', + 'VJOURNAL', + 'VFREEBUSY', + 'VALARM', + ))) { + throw new \Sabre\DAV\Exception\BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component'); + }; + + $result[] = $compFilter; + + } + + return $result; + + } + + /** + * Parses all the prop-filter elements from a node + * + * @param \DOMElement $parentNode + * @return array + */ + protected function parsePropFilters(\DOMElement $parentNode) { + + $propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode); + $result = array(); + + for ($ii=0; $ii < $propFilterNodes->length; $ii++) { + + $propFilterNode = $propFilterNodes->item($ii); + $propFilter = array(); + $propFilter['name'] = $propFilterNode->getAttribute('name'); + $propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0; + $propFilter['param-filters'] = $this->parseParamFilters($propFilterNode); + $propFilter['text-match'] = $this->parseTextMatch($propFilterNode); + $propFilter['time-range'] = $this->parseTimeRange($propFilterNode); + + $result[] = $propFilter; + + } + + return $result; + + } + + /** + * Parses the param-filter element + * + * @param \DOMElement $parentNode + * @return array + */ + protected function parseParamFilters(\DOMElement $parentNode) { + + $paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode); + $result = array(); + + for($ii=0;$ii<$paramFilterNodes->length;$ii++) { + + $paramFilterNode = $paramFilterNodes->item($ii); + $paramFilter = array(); + $paramFilter['name'] = $paramFilterNode->getAttribute('name'); + $paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0; + $paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode); + + $result[] = $paramFilter; + + } + + return $result; + + } + + /** + * Parses the text-match element + * + * @param \DOMElement $parentNode + * @return array|null + */ + protected function parseTextMatch(\DOMElement $parentNode) { + + $textMatchNodes = $this->xpath->query('cal:text-match', $parentNode); + + if ($textMatchNodes->length === 0) + return null; + + $textMatchNode = $textMatchNodes->item(0); + $negateCondition = $textMatchNode->getAttribute('negate-condition'); + $negateCondition = $negateCondition==='yes'; + $collation = $textMatchNode->getAttribute('collation'); + if (!$collation) $collation = 'i;ascii-casemap'; + + return array( + 'negate-condition' => $negateCondition, + 'collation' => $collation, + 'value' => $textMatchNode->nodeValue + ); + + } + + /** + * Parses the time-range element + * + * @param \DOMElement $parentNode + * @return array|null + */ + protected function parseTimeRange(\DOMElement $parentNode) { + + $timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode); + if ($timeRangeNodes->length === 0) { + return null; + } + + $timeRangeNode = $timeRangeNodes->item(0); + + if ($start = $timeRangeNode->getAttribute('start')) { + $start = VObject\DateTimeParser::parseDateTime($start); + } else { + $start = null; + } + if ($end = $timeRangeNode->getAttribute('end')) { + $end = VObject\DateTimeParser::parseDateTime($end); + } else { + $end = null; + } + + if (!is_null($start) && !is_null($end) && $end <= $start) { + throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the time-range filter'); + } + + return array( + 'start' => $start, + 'end' => $end, + ); + + } + + /** + * Parses the CALDAV:expand element + * + * @param \DOMElement $parentNode + * @return void + */ + protected function parseExpand(\DOMElement $parentNode) { + + $start = $parentNode->getAttribute('start'); + if(!$start) { + throw new \Sabre\DAV\Exception\BadRequest('The "start" attribute is required for the CALDAV:expand element'); + } + $start = VObject\DateTimeParser::parseDateTime($start); + + $end = $parentNode->getAttribute('end'); + if(!$end) { + throw new \Sabre\DAV\Exception\BadRequest('The "end" attribute is required for the CALDAV:expand element'); + } + + $end = VObject\DateTimeParser::parseDateTime($end); + + if ($end <= $start) { + throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); + } + + return array( + 'start' => $start, + 'end' => $end, + ); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php new file mode 100644 index 000000000..fd47127f2 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php @@ -0,0 +1,392 @@ +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 (version_compare(VObject\Version::VERSION, '3.0.0beta1', '>=')) { + + // 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 $subParam) { + + if($this->validateTextMatch($subParam,$filter['text-match'])) { + // We had a match, so this param-filter succeeds + continue 2; + } + + } + + } else { + + // If there are sub-filters, we need to find at least one parameter + // for which the subfilters hold true. + foreach($parent[$filter['name']] as $subParam) { + + if($this->validateTextMatch($subParam,$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 = (string)$check; + } + + $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\RecurrenceIterator($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/Sabre/CalDAV/CalendarRootNode.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php new file mode 100644 index 000000000..5d1ee7780 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php @@ -0,0 +1,77 @@ +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 + */ + public 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 + */ + public function getChildForPrincipal(array $principal) { + + return new UserCalendars($this->caldavBackend, $principal); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php new file mode 100644 index 000000000..b8d36794c --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV,'cal:supported-calendar-component'); + $errorNode->appendChild($np); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php new file mode 100644 index 000000000..aa0637295 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php @@ -0,0 +1,142 @@ +server = $server; + $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90); + + } + + /** + * 'beforeMethod' event handles. This event handles intercepts GET requests ending + * with ?export + * + * @param string $method + * @param string $uri + * @return bool + */ + public function beforeMethod($method, $uri) { + + if ($method!='GET') return; + if ($this->server->httpRequest->getQueryString()!='export') return; + + // splitting uri + list($uri) = explode('?',$uri,2); + + $node = $this->server->tree->getNodeForPath($uri); + + if (!($node instanceof Calendar)) return; + + // Checking ACL, if available. + if ($aclPlugin = $this->server->getPlugin('acl')) { + $aclPlugin->checkPrivileges($uri, '{DAV:}read'); + } + + $this->server->httpResponse->setHeader('Content-Type','text/calendar'); + $this->server->httpResponse->sendStatus(200); + + $nodes = $this->server->getPropertiesForPath($uri, array( + '{' . Plugin::NS_CALDAV . '}calendar-data', + ),1); + + $this->server->httpResponse->sendBody($this->generateICS($nodes)); + + // Returning false to break the event chain + return false; + + } + + /** + * Merges all calendar objects, and builds one big ics export + * + * @param array $nodes + * @return string + */ + public function generateICS(array $nodes) { + + $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'; + } + $calendar->calscale = 'GREGORIAN'; + + $collectedTimezones = array(); + + $timezones = array(); + $objects = array(); + + foreach($nodes as $node) { + + if (!isset($node[200]['{' . Plugin::NS_CALDAV . '}calendar-data'])) { + continue; + } + $nodeData = $node[200]['{' . Plugin::NS_CALDAV . '}calendar-data']; + + $nodeComp = VObject\Reader::read($nodeData); + + foreach($nodeComp->children() as $child) { + + switch($child->name) { + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + $objects[] = $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[] = $child; + $collectedTimezones[] = $child->TZID; + break; + + } + + } + + } + + foreach($timezones as $tz) $calendar->add($tz); + foreach($objects as $obj) $calendar->add($obj); + + return $calendar->serialize(); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php b/vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php new file mode 100644 index 000000000..8be36d35a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php @@ -0,0 +1,36 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns all notifications for a principal + * + * @return array + */ + public function getChildren() { + + $children = array(); + $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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getACL() { + + return array( + array( + 'principal' => $this->getOwner(), + 'privilege' => '{DAV:}read', + 'protected' => true, + ), + array( + '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 + */ + public 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 + */ + public function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php new file mode 100644 index 000000000..57a2f7dfb --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php @@ -0,0 +1,24 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + $this->notification = $notification; + + } + + /** + * Returns the path name for this notification + * + * @return id + */ + public function getName() { + + return $this->notification->getId() . '.xml'; + + } + + /** + * Returns the etag for the notification. + * + * The etag must be surrounded by litteral double-quotes. + * + * @return string + */ + public function getETag() { + + return $this->notification->getETag(); + + } + + /** + * This method must return an xml element, using the + * Sabre\CalDAV\Notifications\INotificationType classes. + * + * @return INotificationType + */ + public function getNotificationType() { + + return $this->notification; + + } + + /** + * Deletes this notification + * + * @return void + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getACL() { + + return array( + array( + 'principal' => $this->getOwner(), + 'privilege' => '{DAV:}read', + 'protected' => true, + ), + array( + '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 + */ + public 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 + */ + public function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php new file mode 100644 index 000000000..42daf3fb0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php @@ -0,0 +1,324 @@ +$value) { + if (!property_exists($this, $key)) { + throw new \InvalidArgumentException('Unknown option: ' . $key); + } + $this->$key = $value; + } + + } + + /** + * Serializes the notification as a single property. + * + * You should usually just encode the single top-level element of the + * notification. + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $node) { + + $prop = $node->ownerDocument->createElement('cs:invite-notification'); + $node->appendChild($prop); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serializeBody(DAV\Server $server, \DOMElement $node) { + + $doc = $node->ownerDocument; + + $dt = $doc->createElement('cs:dtstamp'); + $this->dtStamp->setTimezone(new \DateTimezone('GMT')); + $dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z'))); + $node->appendChild($dt); + + $prop = $doc->createElement('cs:invite-notification'); + $node->appendChild($prop); + + $uid = $doc->createElement('cs:uid'); + $uid->appendChild( $doc->createTextNode($this->id) ); + $prop->appendChild($uid); + + $href = $doc->createElement('d:href'); + $href->appendChild( $doc->createTextNode( $this->href ) ); + $prop->appendChild($href); + + $nodeName = null; + switch($this->type) { + + case SharingPlugin::STATUS_ACCEPTED : + $nodeName = 'cs:invite-accepted'; + break; + case SharingPlugin::STATUS_DECLINED : + $nodeName = 'cs:invite-declined'; + break; + case SharingPlugin::STATUS_DELETED : + $nodeName = 'cs:invite-deleted'; + break; + case SharingPlugin::STATUS_NORESPONSE : + $nodeName = 'cs:invite-noresponse'; + break; + + } + $prop->appendChild( + $doc->createElement($nodeName) + ); + $hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl); + $hostUrl = $doc->createElement('cs:hosturl'); + $hostUrl->appendChild($hostHref); + $prop->appendChild($hostUrl); + + $access = $doc->createElement('cs:access'); + if ($this->readOnly) { + $access->appendChild($doc->createElement('cs:read')); + } else { + $access->appendChild($doc->createElement('cs:read-write')); + } + $prop->appendChild($access); + + $organizerUrl = $doc->createElement('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:') { + $organizerHref = new DAV\Property\Href($this->organizer, false); + } else { + $organizerHref = new DAV\Property\Href($this->organizer, true); + } + $organizerHref->serialize($server, $organizerUrl); + + if ($this->commonName) { + $commonName = $doc->createElement('cs:common-name'); + $commonName->appendChild($doc->createTextNode($this->commonName)); + $organizerUrl->appendChild($commonName); + + $commonNameOld = $doc->createElement('cs:organizer-cn'); + $commonNameOld->appendChild($doc->createTextNode($this->commonName)); + $prop->appendChild($commonNameOld); + + } + if ($this->firstName) { + $firstName = $doc->createElement('cs:first-name'); + $firstName->appendChild($doc->createTextNode($this->firstName)); + $organizerUrl->appendChild($firstName); + + $firstNameOld = $doc->createElement('cs:organizer-first'); + $firstNameOld->appendChild($doc->createTextNode($this->firstName)); + $prop->appendChild($firstNameOld); + } + if ($this->lastName) { + $lastName = $doc->createElement('cs:last-name'); + $lastName->appendChild($doc->createTextNode($this->lastName)); + $organizerUrl->appendChild($lastName); + + $lastNameOld = $doc->createElement('cs:organizer-last'); + $lastNameOld->appendChild($doc->createTextNode($this->lastName)); + $prop->appendChild($lastNameOld); + } + $prop->appendChild($organizerUrl); + + if ($this->summary) { + $summary = $doc->createElement('cs:summary'); + $summary->appendChild($doc->createTextNode($this->summary)); + $prop->appendChild($summary); + } + if ($this->supportedComponents) { + + $xcomp = $doc->createElement('cal:supported-calendar-component-set'); + $this->supportedComponents->serialize($server, $xcomp); + $prop->appendChild($xcomp); + + } + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + public function getId() { + + return $this->id; + + } + + /** + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + public function getETag() { + + return $this->etag; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php new file mode 100644 index 000000000..3654a3e16 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php @@ -0,0 +1,218 @@ +$value) { + if (!property_exists($this, $key)) { + throw new \InvalidArgumentException('Unknown option: ' . $key); + } + $this->$key = $value; + } + + } + + /** + * Serializes the notification as a single property. + * + * You should usually just encode the single top-level element of the + * notification. + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $node) { + + $prop = $node->ownerDocument->createElement('cs:invite-reply'); + $node->appendChild($prop); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serializeBody(DAV\Server $server, \DOMElement $node) { + + $doc = $node->ownerDocument; + + $dt = $doc->createElement('cs:dtstamp'); + $this->dtStamp->setTimezone(new \DateTimezone('GMT')); + $dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z'))); + $node->appendChild($dt); + + $prop = $doc->createElement('cs:invite-reply'); + $node->appendChild($prop); + + $uid = $doc->createElement('cs:uid'); + $uid->appendChild($doc->createTextNode($this->id)); + $prop->appendChild($uid); + + $inReplyTo = $doc->createElement('cs:in-reply-to'); + $inReplyTo->appendChild( $doc->createTextNode($this->inReplyTo) ); + $prop->appendChild($inReplyTo); + + $href = $doc->createElement('d:href'); + $href->appendChild( $doc->createTextNode($this->href) ); + $prop->appendChild($href); + + $nodeName = null; + switch($this->type) { + + case SharingPlugin::STATUS_ACCEPTED : + $nodeName = 'cs:invite-accepted'; + break; + case SharingPlugin::STATUS_DECLINED : + $nodeName = 'cs:invite-declined'; + break; + + } + $prop->appendChild( + $doc->createElement($nodeName) + ); + $hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl); + $hostUrl = $doc->createElement('cs:hosturl'); + $hostUrl->appendChild($hostHref); + $prop->appendChild($hostUrl); + + if ($this->summary) { + $summary = $doc->createElement('cs:summary'); + $summary->appendChild($doc->createTextNode($this->summary)); + $prop->appendChild($summary); + } + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + public function getId() { + + return $this->id; + + } + + /** + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + public function getETag() { + + return $this->etag; + + } +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php new file mode 100644 index 000000000..a6a1c4928 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php @@ -0,0 +1,182 @@ +id = $id; + $this->type = $type; + $this->description = $description; + $this->href = $href; + $this->etag = $etag; + + } + + /** + * Serializes the notification as a single property. + * + * You should usually just encode the single top-level element of the + * notification. + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $node) { + + 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; + } + + $prop = $node->ownerDocument->createElement('cs:systemstatus'); + $prop->setAttribute('type', $type); + + $node->appendChild($prop); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serializeBody(DAV\Server $server, \DOMElement $node) { + + 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; + } + + $prop = $node->ownerDocument->createElement('cs:systemstatus'); + $prop->setAttribute('type', $type); + + if ($this->description) { + $text = $node->ownerDocument->createTextNode($this->description); + $desc = $node->ownerDocument->createElement('cs:description'); + $desc->appendChild($text); + $prop->appendChild($desc); + } + if ($this->href) { + $text = $node->ownerDocument->createTextNode($this->href); + $href = $node->ownerDocument->createElement('d:href'); + $href->appendChild($text); + $prop->appendChild($href); + } + + $node->appendChild($prop); + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + public function getId() { + + return $this->id; + + } + + /* + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + public function getETag() { + + return $this->etag; + + } +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php new file mode 100644 index 000000000..084036cf9 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php @@ -0,0 +1,1338 @@ +imipHandler = $imipHandler; + + } + + /** + * 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 + */ + public function getHTTPMethods($uri) { + + // The MKCALENDAR is only available on unmapped uri's, whose + // parents extend IExtendedCollection + list($parent, $name) = DAV\URLUtil::splitPath($uri); + + $node = $this->server->tree->getNodeForPath($parent); + + if ($node instanceof DAV\IExtendedCollection) { + try { + $node->getChild($name); + } catch (DAV\Exception\NotFound $e) { + return array('MKCALENDAR'); + } + } + return array(); + + } + + /** + * Returns a list of features for the DAV: HTTP header. + * + * @return array + */ + public function getFeatures() { + + return array('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 + */ + public 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 + */ + public function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + + $reports = array(); + if ($node instanceof ICalendar || $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'; + } + return $reports; + + } + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + + $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); + //$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000); + $server->subscribeEvent('report',array($this,'report')); + $server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties')); + $server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel')); + $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction')); + $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent')); + $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile')); + $server->subscribeEvent('beforeMethod', array($this,'beforeMethod')); + + $server->xmlNamespaces[self::NS_CALDAV] = 'cal'; + $server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs'; + + $server->propertyMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Property\\SupportedCalendarComponentSet'; + $server->propertyMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Property\\ScheduleCalendarTransp'; + + $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox'; + $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'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification'; + + 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', + + // scheduling extension + '{' . self::NS_CALDAV . '}schedule-inbox-URL', + '{' . self::NS_CALDAV . '}schedule-outbox-URL', + '{' . self::NS_CALDAV . '}calendar-user-address-set', + '{' . self::NS_CALDAV . '}calendar-user-type', + + // CalendarServer extensions + '{' . self::NS_CALENDARSERVER . '}getctag', + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for', + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for', + '{' . self::NS_CALENDARSERVER . '}notification-URL', + '{' . self::NS_CALENDARSERVER . '}notificationtype' + + ); + } + + /** + * This function handles support for the MKCALENDAR method + * + * @param string $method + * @param string $uri + * @return bool + */ + public function unknownMethod($method, $uri) { + + switch ($method) { + case 'MKCALENDAR' : + $this->httpMkCalendar($uri); + // false is returned to stop the propagation of the + // unknownMethod event. + return false; + case 'POST' : + + // Checking if this is a text/calendar content type + $contentType = $this->server->httpRequest->getHeader('Content-Type'); + if (strpos($contentType, 'text/calendar')!==0) { + return; + } + + // Checking if we're talking to an outbox + try { + $node = $this->server->tree->getNodeForPath($uri); + } catch (DAV\Exception\NotFound $e) { + return; + } + if (!$node instanceof Schedule\IOutbox) + return; + + $this->outboxRequest($node, $uri); + return false; + + } + + } + + /** + * This functions handles REPORT requests specific to CalDAV + * + * @param string $reportName + * @param \DOMNode $dom + * @return bool + */ + public function report($reportName,$dom) { + + switch($reportName) { + case '{'.self::NS_CALDAV.'}calendar-multiget' : + $this->calendarMultiGetReport($dom); + return false; + case '{'.self::NS_CALDAV.'}calendar-query' : + $this->calendarQueryReport($dom); + return false; + case '{'.self::NS_CALDAV.'}free-busy-query' : + $this->freeBusyQueryReport($dom); + return false; + + } + + + } + + /** + * This function handles the MKCALENDAR HTTP method, which creates + * a new calendar. + * + * @param string $uri + * @return void + */ + public function httpMkCalendar($uri) { + + // Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support + // for clients matching iCal in the user agent + //$ua = $this->server->httpRequest->getHeader('User-Agent'); + //if (strpos($ua,'iCal/')!==false) { + // throw new \Sabre\DAV\Exception\Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.'); + //} + + $body = $this->server->httpRequest->getBody(true); + $properties = array(); + + if ($body) { + + $dom = DAV\XMLUtil::loadDOMDocument($body); + + foreach($dom->firstChild->childNodes as $child) { + + if (DAV\XMLUtil::toClarkNotation($child)!=='{DAV:}set') continue; + foreach(DAV\XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) { + $properties[$k] = $prop; + } + + } + } + + $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'); + + $this->server->createCollection($uri,$resourceType,$properties); + + $this->server->httpResponse->sendStatus(201); + $this->server->httpResponse->setHeader('Content-Length',0); + } + + /** + * beforeGetProperties + * + * 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 string $path + * @param DAV\INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @return void + */ + public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) { + + if ($node instanceof DAVACL\IPrincipal) { + + // calendar-home-set property + $calHome = '{' . self::NS_CALDAV . '}calendar-home-set'; + if (in_array($calHome,$requestedProperties)) { + $principalId = $node->getName(); + $calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/'; + + unset($requestedProperties[array_search($calHome, $requestedProperties)]); + $returnedProperties[200][$calHome] = new DAV\Property\Href($calendarHomePath); + + } + + // schedule-outbox-URL property + $scheduleProp = '{' . self::NS_CALDAV . '}schedule-outbox-URL'; + if (in_array($scheduleProp,$requestedProperties)) { + $principalId = $node->getName(); + $outboxPath = self::CALENDAR_ROOT . '/' . $principalId . '/outbox'; + + unset($requestedProperties[array_search($scheduleProp, $requestedProperties)]); + $returnedProperties[200][$scheduleProp] = new DAV\Property\Href($outboxPath); + + } + + // calendar-user-address-set property + $calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set'; + if (in_array($calProp,$requestedProperties)) { + + $addresses = $node->getAlternateUriSet(); + $addresses[] = $this->server->getBaseUri() . DAV\URLUtil::encodePath($node->getPrincipalUrl() . '/'); + unset($requestedProperties[array_search($calProp, $requestedProperties)]); + $returnedProperties[200][$calProp] = new DAV\Property\HrefList($addresses, false); + + } + + // 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 (in_array($propRead,$requestedProperties) || in_array($propWrite,$requestedProperties)) { + + $aclPlugin = $this->server->getPlugin('acl'); + $membership = $aclPlugin->getPrincipalMembership($path); + $readList = array(); + $writeList = array(); + + foreach($membership as $group) { + + $groupNode = $this->server->tree->getNodeForPath($group); + + // 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) { + list($readList[]) = DAV\URLUtil::splitPath($group); + } + if ($groupNode instanceof Principal\IProxyWrite) { + list($writeList[]) = DAV\URLUtil::splitPath($group); + } + + } + if (in_array($propRead,$requestedProperties)) { + unset($requestedProperties[$propRead]); + $returnedProperties[200][$propRead] = new DAV\Property\HrefList($readList); + } + if (in_array($propWrite,$requestedProperties)) { + unset($requestedProperties[$propWrite]); + $returnedProperties[200][$propWrite] = new DAV\Property\HrefList($writeList); + } + + } + + // notification-URL property + $notificationUrl = '{' . self::NS_CALENDARSERVER . '}notification-URL'; + if (($index = array_search($notificationUrl, $requestedProperties)) !== false) { + $principalId = $node->getName(); + $calendarHomePath = 'calendars/' . $principalId . '/notifications/'; + unset($requestedProperties[$index]); + $returnedProperties[200][$notificationUrl] = new DAV\Property\Href($calendarHomePath); + } + + } // instanceof IPrincipal + + if ($node instanceof Notifications\INode) { + + $propertyName = '{' . self::NS_CALENDARSERVER . '}notificationtype'; + if (($index = array_search($propertyName, $requestedProperties)) !== false) { + + $returnedProperties[200][$propertyName] = + $node->getNotificationType(); + + unset($requestedProperties[$index]); + + } + + } // instanceof Notifications_INode + + + 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. + $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; + if (in_array($calDataProp, $requestedProperties)) { + unset($requestedProperties[$calDataProp]); + $val = $node->get(); + if (is_resource($val)) + $val = stream_get_contents($val); + + // Taking out \r to not screw up the xml output + $returnedProperties[200][$calDataProp] = 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 \DOMNode $dom + * @return void + */ + public function calendarMultiGetReport($dom) { + + $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); + $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); + + $xpath = new \DOMXPath($dom); + $xpath->registerNameSpace('cal',Plugin::NS_CALDAV); + $xpath->registerNameSpace('dav','urn:DAV'); + + $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); + if ($expand->length>0) { + $expandElem = $expand->item(0); + $start = $expandElem->getAttribute('start'); + $end = $expandElem->getAttribute('end'); + if(!$start || !$end) { + throw new DAV\Exception\BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element'); + } + $start = VObject\DateTimeParser::parseDateTime($start); + $end = VObject\DateTimeParser::parseDateTime($end); + + if ($end <= $start) { + throw new DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); + } + + $expand = true; + + } else { + + $expand = false; + + } + + foreach($hrefElems as $elem) { + $uri = $this->server->calculateUri($elem->nodeValue); + list($objProps) = $this->server->getPropertiesForPath($uri,$properties); + + if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { + $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); + $vObject->expand($start, $end); + $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + + $propertyList[]=$objProps; + + } + + $prefer = $this->server->getHTTPPRefer(); + + $this->server->httpResponse->sendStatus(207); + $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); + $this->server->httpResponse->sendBody($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 \DOMNode $dom + * @return void + */ + public function calendarQueryReport($dom) { + + $parser = new CalendarQueryParser($dom); + $parser->parse(); + + $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); + $depth = $this->server->getHTTPDepth(0); + + // The default result is an empty array + $result = array(); + + // The calendarobject was requested directly. In this case we handle + // this locally. + if ($depth == 0 && $node instanceof ICalendarObject) { + + $requestedCalendarData = true; + $requestedProperties = $parser->requestedProperties; + + 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( + $this->server->getRequestUri(), + $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,$parser->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 ($parser->expand) { + $vObject->expand($parser->expand['start'], $parser->expand['end']); + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + } + + $result = array($properties); + + } + + } + + } + // If we're dealing with a calendar, the calendar itself is responsible + // for the calendar-query. + if ($node instanceof ICalendar && $depth = 1) { + + $nodePaths = $node->calendarQuery($parser->filters); + + foreach($nodePaths as $path) { + + list($properties) = + $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); + + if ($parser->expand) { + // We need to do some post-processing + $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); + $vObject->expand($parser->expand['start'], $parser->expand['end']); + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + + $result[] = $properties; + + } + + } + + $prefer = $this->server->getHTTPPRefer(); + + $this->server->httpResponse->sendStatus(207); + $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); + $this->server->httpResponse->sendBody($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 \DOMNode $dom + * @return void + */ + protected function freeBusyQueryReport(\DOMNode $dom) { + + $start = null; + $end = null; + + foreach($dom->firstChild->childNodes as $childNode) { + + $clark = DAV\XMLUtil::toClarkNotation($childNode); + if ($clark == '{' . self::NS_CALDAV . '}time-range') { + $start = $childNode->getAttribute('start'); + $end = $childNode->getAttribute('end'); + break; + } + + } + if ($start) { + $start = VObject\DateTimeParser::parseDateTime($start); + } + if ($end) { + $end = VObject\DateTimeParser::parseDateTime($end); + } + + if (!$start && !$end) { + throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); + } + $acl = $this->server->getPlugin('acl'); + + if (!$acl) { + throw new DAV\Exception('The ACL plugin must be loaded for free-busy queries to work'); + } + $uri = $this->server->getRequestUri(); + $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'); + } + + // Doing a calendar-query first, to make sure we get the most + // performance. + $urls = $calendar->calendarQuery(array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => $start, + 'end' => $end, + ), + ), + ), + 'prop-filters' => array(), + '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($start, $end); + $result = $generator->getResult(); + $result = $result->serialize(); + + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); + $this->server->httpResponse->setHeader('Content-Length', strlen($result)); + $this->server->httpResponse->sendBody($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 + * @return void + */ + public function beforeWriteContent($path, DAV\IFile $node, &$data) { + + if (!$node instanceof ICalendarObject) + return; + + $this->validateICalendar($data, $path); + + } + + /** + * 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 + * @return void + */ + public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode) { + + if (!$parentNode instanceof Calendar) + return; + + $this->validateICalendar($data, $path); + + } + + /** + * This event is triggered before any HTTP request is handled. + * + * We use this to intercept GET calls to notification nodes, and return the + * proper response. + * + * @param string $method + * @param string $path + * @return void + */ + public function beforeMethod($method, $path) { + + if ($method!=='GET') return; + + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (DAV\Exception\NotFound $e) { + return; + } + + if (!$node instanceof Notifications\INode) + return; + + if (!$this->server->checkPreconditions(true)) return false; + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->formatOutput = true; + + $root = $dom->createElement('cs:notification'); + foreach($this->server->xmlNamespaces as $namespace => $prefix) { + $root->setAttribute('xmlns:' . $prefix, $namespace); + } + + $dom->appendChild($root); + $node->getNotificationType()->serializeBody($this->server, $root); + + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->setHeader('ETag',$node->getETag()); + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->sendBody($dom->saveXML()); + + return false; + + } + + /** + * 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 + * @return void + */ + protected function validateICalendar(&$data, $path) { + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + // Converting the data to unicode, if needed. + $data = DAV\StringUtil::ensureUTF8($data); + + try { + + $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.'); + } + + // Get the Supported Components for the target calendar + list($parentPath,$object) = DAV\URLUtil::splitPath($path); + $calendarProperties = $this->server->getProperties($parentPath,array('{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set')); + $supportedComponents = $calendarProperties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue(); + + $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'); + + } + + /** + * 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 Schedule\IOutbox $outboxNode + * @param string $outboxUri + * @return void + */ + public function outboxRequest(Schedule\IOutbox $outboxNode, $outboxUri) { + + // Parsing the request body + try { + $vObject = VObject\Reader::read($this->server->httpRequest->getBody(true)); + } catch (VObject\ParseException $e) { + throw new DAV\Exception\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 DAV\Exception\BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component'); + } + + // Validating the METHOD + $method = strtoupper((string)$vObject->METHOD); + if (!$method) { + throw new DAV\Exception\BadRequest('A METHOD property must be specified in iTIP messages'); + } + + // So we support two types of requests: + // + // REQUEST with a VFREEBUSY component + // REQUEST, REPLY, ADD, CANCEL on VEVENT components + + $acl = $this->server->getPlugin('acl'); + + if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') { + + $acl && $acl->checkPrivileges($outboxUri,'{' . Plugin::NS_CALDAV . '}schedule-query-freebusy'); + $this->handleFreeBusyRequest($outboxNode, $vObject); + + } elseif ($componentType === 'VEVENT' && in_array($method, array('REQUEST','REPLY','ADD','CANCEL'))) { + + $acl && $acl->checkPrivileges($outboxUri,'{' . Plugin::NS_CALDAV . '}schedule-post-vevent'); + $this->handleEventNotification($outboxNode, $vObject); + + } else { + + throw new DAV\Exception\NotImplemented('SabreDAV supports only VFREEBUSY (REQUEST) and VEVENT (REQUEST, REPLY, ADD, CANCEL)'); + + } + + } + + /** + * This method handles the REQUEST, REPLY, ADD and CANCEL methods for + * VEVENT iTip messages. + * + * @return void + */ + protected function handleEventNotification(Schedule\IOutbox $outboxNode, VObject\Component $vObject) { + + $originator = $this->server->httpRequest->getHeader('Originator'); + $recipients = $this->server->httpRequest->getHeader('Recipient'); + + if (!$originator) { + throw new DAV\Exception\BadRequest('The Originator: header must be specified when making POST requests'); + } + if (!$recipients) { + throw new DAV\Exception\BadRequest('The Recipient: header must be specified when making POST requests'); + } + + $recipients = explode(',',$recipients); + foreach($recipients as $k=>$recipient) { + + $recipient = trim($recipient); + if (!preg_match('/^mailto:(.*)@(.*)$/i', $recipient)) { + throw new DAV\Exception\BadRequest('Recipients must start with mailto: and must be valid email address'); + } + $recipient = substr($recipient, 7); + $recipients[$k] = $recipient; + } + + // We need to make sure that 'originator' matches one of the email + // addresses of the selected principal. + $principal = $outboxNode->getOwner(); + $props = $this->server->getProperties($principal,array( + '{' . self::NS_CALDAV . '}calendar-user-address-set', + )); + + $addresses = array(); + if (isset($props['{' . self::NS_CALDAV . '}calendar-user-address-set'])) { + $addresses = $props['{' . self::NS_CALDAV . '}calendar-user-address-set']->getHrefs(); + } + + $found = false; + foreach($addresses as $address) { + + // Trimming the / on both sides, just in case.. + if (rtrim(strtolower($originator),'/') === rtrim(strtolower($address),'/')) { + $found = true; + break; + } + + } + + if (!$found) { + throw new DAV\Exception\Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header'); + } + + // If the Originator header was a url, and not a mailto: address.. + // we're going to try to pull the mailto: from the vobject body. + if (strtolower(substr($originator,0,7)) !== 'mailto:') { + $originator = (string)$vObject->VEVENT->ORGANIZER; + + } + if (strtolower(substr($originator,0,7)) !== 'mailto:') { + throw new DAV\Exception\Forbidden('Could not find mailto: address in both the Orignator header, and the ORGANIZER property in the VEVENT'); + } + $originator = substr($originator,7); + + $result = $this->iMIPMessage($originator, $recipients, $vObject, $principal); + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->sendBody($this->generateScheduleResponse($result)); + + } + + /** + * Sends an iMIP message by email. + * + * This method must return an array with status codes per recipient. + * This should look something like: + * + * array( + * 'user1@example.org' => '2.0;Success' + * ) + * + * Formatting for this status code can be found at: + * https://tools.ietf.org/html/rfc5545#section-3.8.8.3 + * + * A list of valid status codes can be found at: + * https://tools.ietf.org/html/rfc5546#section-3.6 + * + * @param string $originator + * @param array $recipients + * @param VObject\Component $vObject + * @param string $principal Principal url + * @return array + */ + protected function iMIPMessage($originator, array $recipients, VObject\Component $vObject, $principal) { + + if (!$this->imipHandler) { + $resultStatus = '5.2;This server does not support this operation'; + } else { + $this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal); + $resultStatus = '2.0;Success'; + } + + $result = array(); + foreach($recipients as $recipient) { + $result[$recipient] = $resultStatus; + } + + return $result; + + } + + /** + * Generates a schedule-response XML body + * + * The recipients array is a key->value list, containing email addresses + * and iTip status codes. See the iMIPMessage method for a description of + * the value. + * + * @param array $recipients + * @return string + */ + public function generateScheduleResponse(array $recipients) { + + $dom = new \DOMDocument('1.0','utf-8'); + $dom->formatOutput = true; + $xscheduleResponse = $dom->createElement('cal:schedule-response'); + $dom->appendChild($xscheduleResponse); + + foreach($this->server->xmlNamespaces as $namespace=>$prefix) { + + $xscheduleResponse->setAttribute('xmlns:' . $prefix, $namespace); + + } + + foreach($recipients as $recipient=>$status) { + $xresponse = $dom->createElement('cal:response'); + + $xrecipient = $dom->createElement('cal:recipient'); + $xrecipient->appendChild($dom->createTextNode($recipient)); + $xresponse->appendChild($xrecipient); + + $xrequestStatus = $dom->createElement('cal:request-status'); + $xrequestStatus->appendChild($dom->createTextNode($status)); + $xresponse->appendChild($xrequestStatus); + + $xscheduleResponse->appendChild($xresponse); + + } + + return $dom->saveXML(); + + } + + /** + * This method is responsible for parsing a free-busy query request and + * returning it's result. + * + * @param Schedule\IOutbox $outbox + * @param string $request + * @return string + */ + protected function handleFreeBusyRequest(Schedule\IOutbox $outbox, VObject\Component $vObject) { + + $vFreeBusy = $vObject->VFREEBUSY; + $organizer = $vFreeBusy->organizer; + + $organizer = (string)$organizer; + + // Validating if the organizer matches the owner of the inbox. + $owner = $outbox->getOwner(); + + $caldavNS = '{' . Plugin::NS_CALDAV . '}'; + + $uas = $caldavNS . 'calendar-user-address-set'; + $props = $this->server->getProperties($owner,array($uas)); + + if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) { + throw new DAV\Exception\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 DAV\Exception\BadRequest('You must at least specify 1 attendee'); + } + + $attendees = array(); + foreach($vFreeBusy->ATTENDEE as $attendee) { + $attendees[]= (string)$attendee; + } + + + if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) { + throw new DAV\Exception\BadRequest('DTSTART and DTEND must both be specified'); + } + + $startRange = $vFreeBusy->DTSTART->getDateTime(); + $endRange = $vFreeBusy->DTEND->getDateTime(); + + $results = array(); + 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->xmlNamespaces as $namespace=>$prefix) { + + $scheduleResponse->setAttribute('xmlns:' . $prefix,$namespace); + + } + $dom->appendChild($scheduleResponse); + + foreach($results as $result) { + $response = $dom->createElement('cal:response'); + + $recipient = $dom->createElement('cal:recipient'); + $recipientHref = $dom->createElement('d:href'); + + $recipientHref->appendChild($dom->createTextNode($result['href'])); + $recipient->appendChild($recipientHref); + $response->appendChild($recipient); + + $reqStatus = $dom->createElement('cal:request-status'); + $reqStatus->appendChild($dom->createTextNode($result['request-status'])); + $response->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()))); + $response->appendChild($calendardata); + + } + $scheduleResponse->appendChild($response); + } + + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->sendBody($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 \DateTime $start + * @param \DateTime $end + * @param VObject\Component $request + * @return array + */ + protected function getFreeBusyForEmail($email, \DateTime $start, \DateTime $end, VObject\Component $request) { + + $caldavNS = '{' . Plugin::NS_CALDAV . '}'; + + $aclPlugin = $this->server->getPlugin('acl'); + if (substr($email,0,7)==='mailto:') $email = substr($email,7); + + $result = $aclPlugin->principalSearch( + array('{http://sabredav.org/ns}email-address' => $email), + array( + '{DAV:}principal-URL', $caldavNS . 'calendar-home-set', + '{http://sabredav.org/ns}email-address', + ) + ); + + if (!count($result)) { + return array( + 'request-status' => '3.7;Could not find principal', + 'href' => 'mailto:' . $email, + ); + } + + if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) { + return array( + 'request-status' => '3.7;No calendar-home-set property found', + 'href' => 'mailto:' . $email, + ); + } + $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref(); + + // Grabbing the calendar list + $objects = array(); + foreach($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) { + if (!$node instanceof ICalendar) { + continue; + } + $aclPlugin->checkPrivileges($homeSet . $node->getName() ,$caldavNS . 'read-free-busy'); + + // Getting the list of object uris within the time-range + $urls = $node->calendarQuery(array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => $start, + 'end' => $end, + ), + ), + ), + 'prop-filters' => array(), + '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); + + } + + $vcalendar = new VObject\Component\VCalendar(); + $vcalendar->VERSION = '2.0'; + $vcalendar->METHOD = 'REPLY'; + $vcalendar->CALSCALE = 'GREGORIAN'; + $vcalendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN'; + + $generator = new VObject\FreeBusyGenerator(); + $generator->setObjects($objects); + $generator->setTimeRange($start, $end); + $generator->setBaseObject($vcalendar); + + $result = $generator->getResult(); + + $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email; + $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID; + $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER; + + return array( + 'calendar-data' => $result, + 'request-status' => '2.0;Success', + 'href' => 'mailto:' . $email, + ); + } + + /** + * 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 + */ + public function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof UserCalendars) + return; + + $output.= '
+

Create new calendar

+ +
+
+ +
+ '; + + return false; + + } + + /** + * This method allows us to intercept the 'mkcalendar' sabreAction. This + * action enables the user to create new calendars from the browser plugin. + * + * @param string $uri + * @param string $action + * @param array $postVars + * @return bool + */ + public function browserPostAction($uri, $action, array $postVars) { + + if ($action!=='mkcalendar') + return; + + $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'); + $properties = array(); + if (isset($postVars['{DAV:}displayname'])) { + $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; + } + $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); + return false; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php new file mode 100644 index 000000000..4fc70e265 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php @@ -0,0 +1,32 @@ +principalBackend, $principalInfo); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php new file mode 100644 index 000000000..a774b2aad --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php @@ -0,0 +1,19 @@ +principalInfo = $principalInfo; + $this->principalBackend = $principalBackend; + + } + + /** + * Returns this principals name. + * + * @return string + */ + public function getName() { + + return 'calendar-proxy-read'; + + } + + /** + * Returns the last modification time + * + * @return null + */ + public function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws DAV\Exception\Forbidden + * @return void + */ + public 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 + */ + public 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 + */ + public function getAlternateUriSet() { + + return array(); + + } + + /** + * Returns the full principal url + * + * @return string + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getDisplayName() { + + return $this->getName(); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php new file mode 100644 index 000000000..c9b136fe0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php @@ -0,0 +1,180 @@ +principalInfo = $principalInfo; + $this->principalBackend = $principalBackend; + + } + + /** + * Returns this principals name. + * + * @return string + */ + public function getName() { + + return 'calendar-proxy-write'; + + } + + /** + * Returns the last modification time + * + * @return null + */ + public function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws DAV\Exception\Forbidden + * @return void + */ + public 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 + */ + public 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 + */ + public function getAlternateUriSet() { + + return array(); + + } + + /** + * Returns the full principal url + * + * @return string + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getDisplayName() { + + return $this->getName(); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php new file mode 100644 index 000000000..d37ca115d --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php @@ -0,0 +1,134 @@ +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[] + */ + public function getChildren() { + + $r = array(); + 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 + */ + public 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 + */ + public function getACL() { + + $acl = parent::getACL(); + $acl[] = array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read', + 'protected' => true, + ); + $acl[] = array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write', + 'protected' => true, + ); + return $acl; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php new file mode 100644 index 000000000..5284baf3a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php @@ -0,0 +1,74 @@ +canBeShared = $canBeShared; + $this->canBePublished = $canBePublished; + + } + + /** + * Serializes the property in a DOMDocument + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $node) { + + $doc = $node->ownerDocument; + if ($this->canBeShared) { + $xcomp = $doc->createElement('cs:can-be-shared'); + $node->appendChild($xcomp); + } + if ($this->canBePublished) { + $xcomp = $doc->createElement('cs:can-be-published'); + $node->appendChild($xcomp); + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php new file mode 100644 index 000000000..e955e5f1c --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php @@ -0,0 +1,227 @@ +users = $users; + $this->organizer = $organizer; + + } + + /** + * Returns the list of users, as it was passed to the constructor. + * + * @return array + */ + public function getValue() { + + return $this->users; + + } + + /** + * Serializes the property in a DOMDocument + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $node) { + + $doc = $node->ownerDocument; + + if (!is_null($this->organizer)) { + + $xorganizer = $doc->createElement('cs:organizer'); + + $href = $doc->createElement('d:href'); + $href->appendChild($doc->createTextNode($this->organizer['href'])); + $xorganizer->appendChild($href); + + if (isset($this->organizer['commonName']) && $this->organizer['commonName']) { + $commonName = $doc->createElement('cs:common-name'); + $commonName->appendChild($doc->createTextNode($this->organizer['commonName'])); + $xorganizer->appendChild($commonName); + } + if (isset($this->organizer['firstName']) && $this->organizer['firstName']) { + $firstName = $doc->createElement('cs:first-name'); + $firstName->appendChild($doc->createTextNode($this->organizer['firstName'])); + $xorganizer->appendChild($firstName); + } + if (isset($this->organizer['lastName']) && $this->organizer['lastName']) { + $lastName = $doc->createElement('cs:last-name'); + $lastName->appendChild($doc->createTextNode($this->organizer['lastName'])); + $xorganizer->appendChild($lastName); + } + + $node->appendChild($xorganizer); + + + } + + foreach($this->users as $user) { + + $xuser = $doc->createElement('cs:user'); + + $href = $doc->createElement('d:href'); + $href->appendChild($doc->createTextNode($user['href'])); + $xuser->appendChild($href); + + if (isset($user['commonName']) && $user['commonName']) { + $commonName = $doc->createElement('cs:common-name'); + $commonName->appendChild($doc->createTextNode($user['commonName'])); + $xuser->appendChild($commonName); + } + + switch($user['status']) { + + case SharingPlugin::STATUS_ACCEPTED : + $status = $doc->createElement('cs:invite-accepted'); + $xuser->appendChild($status); + break; + case SharingPlugin::STATUS_DECLINED : + $status = $doc->createElement('cs:invite-declined'); + $xuser->appendChild($status); + break; + case SharingPlugin::STATUS_NORESPONSE : + $status = $doc->createElement('cs:invite-noresponse'); + $xuser->appendChild($status); + break; + case SharingPlugin::STATUS_INVALID : + $status = $doc->createElement('cs:invite-invalid'); + $xuser->appendChild($status); + break; + + } + + $xaccess = $doc->createElement('cs:access'); + + if ($user['readOnly']) { + $xaccess->appendChild( + $doc->createElement('cs:read') + ); + } else { + $xaccess->appendChild( + $doc->createElement('cs:read-write') + ); + } + $xuser->appendChild($xaccess); + + if (isset($user['summary']) && $user['summary']) { + $summary = $doc->createElement('cs:summary'); + $summary->appendChild($doc->createTextNode($user['summary'])); + $xuser->appendChild($summary); + } + + $node->appendChild($xuser); + + } + + + } + + /** + * Unserializes the property. + * + * This static method should return a an instance of this object. + * + * @param \DOMElement $prop + * @return DAV\IProperty + */ + static function unserialize(\DOMElement $prop) { + + $xpath = new \DOMXPath($prop->ownerDocument); + $xpath->registerNamespace('cs', CalDAV\Plugin::NS_CALENDARSERVER); + $xpath->registerNamespace('d', 'urn:DAV'); + + $users = array(); + + foreach($xpath->query('cs:user', $prop) as $user) { + + $status = null; + if ($xpath->evaluate('boolean(cs:invite-accepted)', $user)) { + $status = SharingPlugin::STATUS_ACCEPTED; + } elseif ($xpath->evaluate('boolean(cs:invite-declined)', $user)) { + $status = SharingPlugin::STATUS_DECLINED; + } elseif ($xpath->evaluate('boolean(cs:invite-noresponse)', $user)) { + $status = SharingPlugin::STATUS_NORESPONSE; + } elseif ($xpath->evaluate('boolean(cs:invite-invalid)', $user)) { + $status = SharingPlugin::STATUS_INVALID; + } else { + throw new DAV\Exception('Every cs:user property must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid'); + } + $users[] = array( + 'href' => $xpath->evaluate('string(d:href)', $user), + 'commonName' => $xpath->evaluate('string(cs:common-name)', $user), + 'readOnly' => $xpath->evaluate('boolean(cs:access/cs:read)', $user), + 'summary' => $xpath->evaluate('string(cs:summary)', $user), + 'status' => $status, + ); + + } + + return new self($users); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php new file mode 100644 index 000000000..967c5e22a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php @@ -0,0 +1,102 @@ +value = $value; + + } + + /** + * Returns the current value + * + * @return string + */ + public function getValue() { + + return $this->value; + + } + + /** + * Serializes the property in a DOMDocument + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $node) { + + $doc = $node->ownerDocument; + switch($this->value) { + case self::TRANSPARENT : + $xval = $doc->createElement('cal:transparent'); + break; + case self::OPAQUE : + $xval = $doc->createElement('cal:opaque'); + break; + } + + $node->appendChild($xval); + + } + + /** + * Unserializes the DOMElement back into a Property class. + * + * @param \DOMElement $node + * @return ScheduleCalendarTransp + */ + static function unserialize(\DOMElement $node) { + + $value = null; + foreach($node->childNodes as $childNode) { + switch(DAV\XMLUtil::toClarkNotation($childNode)) { + case '{' . CalDAV\Plugin::NS_CALDAV . '}opaque' : + $value = self::OPAQUE; + break; + case '{' . CalDAV\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/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php new file mode 100644 index 000000000..84c66c913 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php @@ -0,0 +1,88 @@ +components = $components; + + } + + /** + * Returns the list of supported components + * + * @return array + */ + public function getValue() { + + return $this->components; + + } + + /** + * Serializes the property in a DOMDocument + * + * @param DAV\Server $server + * @param \DOMElement $node + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $node) { + + $doc = $node->ownerDocument; + foreach($this->components as $component) { + + $xcomp = $doc->createElement('cal:comp'); + $xcomp->setAttribute('name',$component); + $node->appendChild($xcomp); + + } + + } + + /** + * Unserializes the DOMElement back into a Property class. + * + * @param \DOMElement $node + * @return Property_SupportedCalendarComponentSet + */ + static function unserialize(\DOMElement $node) { + + $components = array(); + foreach($node->childNodes as $childNode) { + if (DAV\XMLUtil::toClarkNotation($childNode)==='{' . CalDAV\Plugin::NS_CALDAV . '}comp') { + $components[] = $childNode->getAttribute('name'); + } + } + return new self($components); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php new file mode 100644 index 000000000..ab989e831 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php @@ -0,0 +1,40 @@ +ownerDocument; + + $prefix = isset($server->xmlNamespaces[Plugin::NS_CALDAV])?$server->xmlNamespaces[Plugin::NS_CALDAV]:'cal'; + + $caldata = $doc->createElement($prefix . ':calendar-data'); + $caldata->setAttribute('content-type','text/calendar'); + $caldata->setAttribute('version','2.0'); + + $node->appendChild($caldata); + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php new file mode 100644 index 000000000..cefc3d3bd --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php @@ -0,0 +1,45 @@ +ownerDocument; + + $prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:caldav'); + if (!$prefix) $prefix = 'cal'; + + $node->appendChild( + $doc->createElement($prefix . ':supported-collation','i;ascii-casemap') + ); + $node->appendChild( + $doc->createElement($prefix . ':supported-collation','i;octet') + ); + $node->appendChild( + $doc->createElement($prefix . ':supported-collation','i;unicode-casemap') + ); + + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php new file mode 100644 index 000000000..2a6c5f360 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php @@ -0,0 +1,111 @@ +senderEmail = $senderEmail; + + } + + /** + * Sends one or more iTip messages through email. + * + * @param string $originator Originator Email + * @param array $recipients Array of email addresses + * @param VObject\Component $vObject + * @param string $principal Principal Url of the originator + * @return void + */ + public function sendMessage($originator, array $recipients, VObject\Component $vObject, $principal) { + + foreach($recipients as $recipient) { + + $to = $recipient; + $replyTo = $originator; + $subject = 'SabreDAV iTIP message'; + + switch(strtoupper($vObject->METHOD)) { + case 'REPLY' : + $subject = 'Response for: ' . $vObject->VEVENT->SUMMARY; + break; + case 'REQUEST' : + $subject = 'Invitation for: ' .$vObject->VEVENT->SUMMARY; + break; + case 'CANCEL' : + $subject = 'Cancelled event: ' . $vObject->VEVENT->SUMMARY; + break; + } + + $headers = array(); + $headers[] = 'Reply-To: ' . $replyTo; + $headers[] = 'From: ' . $this->senderEmail; + $headers[] = 'Content-Type: text/calendar; method=' . (string)$vObject->method . '; charset=utf-8'; + if (DAV\Server::$exposeVersion) { + $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION . '-' . DAV\Version::STABILITY; + } + + $vcalBody = $vObject->serialize(); + + $this->mail($to, $subject, $vcalBody, $headers); + + } + + } + + // @codeCoverageIgnoreStart + // This is deemed untestable in a reasonable manner + + /** + * This function is reponsible 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 + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php new file mode 100644 index 000000000..55a70bd34 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php @@ -0,0 +1,16 @@ +principalUri = $principalUri; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + public function getName() { + + return 'outbox'; + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + public function getChildren() { + + return array(); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + public 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 + */ + public 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 + */ + public function getACL() { + + return array( + array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ), + ); + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + public 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 + */ + public function getSupportedPrivilegeSet() { + + $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet(); + $default['aggregates'][] = array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + ); + $default['aggregates'][] = array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + ); + + return $default; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php b/vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php new file mode 100644 index 000000000..47e098847 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php @@ -0,0 +1,72 @@ +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 + */ + public 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 + */ + public function setPublishStatus($value) { + + $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php b/vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php new file mode 100644 index 000000000..7641289dd --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php @@ -0,0 +1,116 @@ +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 + */ + public 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 + */ + public 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[] = array( + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ); + if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) { + $acl[] = array( + '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 + */ + public function getShares() { + + return $this->caldavBackend->getShares($this->calendarInfo['id']); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php new file mode 100644 index 000000000..c37571706 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php @@ -0,0 +1,526 @@ +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->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); + $this->server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties')); + $this->server->subscribeEvent('updateProperties', array($this, 'updateProperties')); + $this->server->subscribeEvent('unknownMethod', array($this,'unknownMethod')); + + } + + /** + * This event is triggered when properties are requested for a certain + * node. + * + * This allows us to inject any properties early. + * + * @param string $path + * @param DAV\INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @return void + */ + public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) { + + if ($node instanceof IShareableCalendar) { + if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) { + + unset($requestedProperties[$index]); + $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] = + new Property\Invite( + $node->getShares() + ); + + } + + } + + if ($node instanceof ISharedCalendar) { + + if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}shared-url', $requestedProperties))!==false) { + + unset($requestedProperties[$index]); + $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}shared-url'] = + new DAV\Property\Href( + $node->getSharedUrl() + ); + + } + // The 'invite' property is slightly different for the 'shared' + // instance of the calendar, as it also contains the owner + // information. + if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) { + + unset($requestedProperties[$index]); + + // Fetching owner information + $props = $this->server->getPropertiesForPath($node->getOwner(), array( + '{http://sabredav.org/ns}email-address', + '{DAV:}displayname', + ), 1); + + $ownerInfo = array( + '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']; + } + + } + + $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] = + new 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 string $path + * @param array $properties + * @param DAV\INode $node + * @return void + */ + public function afterGetProperties($path, &$properties, DAV\INode $node) { + + if ($node instanceof IShareableCalendar) { + if (isset($properties[200]['{DAV:}resourcetype'])) { + if (count($node->getShares())>0) { + $properties[200]['{DAV:}resourcetype']->add( + '{' . Plugin::NS_CALENDARSERVER . '}shared-owner' + ); + } + } + $propName = '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes'; + if (array_key_exists($propName, $properties[404])) { + unset($properties[404][$propName]); + $properties[200][$propName] = new 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 array $mutations + * @param array $result + * @param DAV\INode $node + * @return void + */ + public function updateProperties(array &$mutations, array &$result, DAV\INode $node) { + + if (!$node instanceof IShareableCalendar) + return; + + if (!isset($mutations['{DAV:}resourcetype'])) { + return; + } + + // Only doing something if shared-owner is indeed not in the list. + if($mutations['{DAV:}resourcetype']->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return; + + $shares = $node->getShares(); + $remove = array(); + foreach($shares as $share) { + $remove[] = $share['href']; + } + $node->updateShares(array(), $remove); + + // We're marking this update as 200 OK + $result[200]['{DAV:}resourcetype'] = null; + + // Removing it from the mutations list + unset($mutations['{DAV:}resourcetype']); + + } + + /** + * This event is triggered when the server didn't know how to handle a + * certain request. + * + * We intercept this to handle POST requests on calendars. + * + * @param string $method + * @param string $uri + * @return null|bool + */ + public function unknownMethod($method, $uri) { + + if ($method!=='POST') { + return; + } + + // Only handling xml + $contentType = $this->server->httpRequest->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($uri); + } catch (DAV\Exception\NotFound $e) { + return; + } + + $requestBody = $this->server->httpRequest->getBody(true); + + // 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. + $this->server->httpRequest->setBody($requestBody); + + $dom = DAV\XMLUtil::loadDOMDocument($requestBody); + + $documentType = DAV\XMLUtil::toClarkNotation($dom->firstChild); + + 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; + } + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($uri, '{DAV:}write'); + } + + $mutations = $this->parseShareRequest($dom); + + $node->updateShares($mutations[0], $mutations[1]); + + $this->server->httpResponse->sendStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $this->server->httpResponse->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 UserCalendars) { + return; + } + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($uri, '{DAV:}write'); + } + + $message = $this->parseInviteReplyRequest($dom); + + $url = $node->shareReply( + $message['href'], + $message['status'], + $message['calendarUri'], + $message['inReplyTo'], + $message['summary'] + ); + + $this->server->httpResponse->sendStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); + + if ($url) { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $root = $dom->createElement('cs:shared-as'); + foreach($this->server->xmlNamespaces as $namespace => $prefix) { + $root->setAttribute('xmlns:' . $prefix, $namespace); + } + + $dom->appendChild($root); + $href = new DAV\Property\Href($url); + + $href->serialize($this->server, $root); + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->sendBody($dom->saveXML()); + + } + + // Breaking the event chain + return false; + + case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof IShareableCalendar) { + return; + } + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($uri, '{DAV:}write'); + } + + $node->setPublishStatus(true); + + // iCloud sends back the 202, so we will too. + $this->server->httpResponse->sendStatus(202); + + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $this->server->httpResponse->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; + } + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($uri, '{DAV:}write'); + } + + $node->setPublishStatus(false); + + $this->server->httpResponse->sendStatus(200); + + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + } + + + + } + + /** + * Parses the 'share' POST request. + * + * This method returns an array, containing two arrays. + * The first array is a list of new sharees. Every element is a struct + * containing a: + * * href element. (usually a mailto: address) + * * commonName element (often a first and lastname, but can also be + * false) + * * readOnly (true or false) + * * summary (A description of the share, can also be false) + * + * The second array is a list of sharees that are to be removed. This is + * just a simple array with 'hrefs'. + * + * @param \DOMDocument $dom + * @return array + */ + protected function parseShareRequest(\DOMDocument $dom) { + + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER); + $xpath->registerNamespace('d', 'urn:DAV'); + + $set = array(); + $elems = $xpath->query('cs:set'); + + for($i=0; $i < $elems->length; $i++) { + + $xset = $elems->item($i); + $set[] = array( + 'href' => $xpath->evaluate('string(d:href)', $xset), + 'commonName' => $xpath->evaluate('string(cs:common-name)', $xset), + 'summary' => $xpath->evaluate('string(cs:summary)', $xset), + 'readOnly' => $xpath->evaluate('boolean(cs:read)', $xset)!==false + ); + + } + + $remove = array(); + $elems = $xpath->query('cs:remove'); + + for($i=0; $i < $elems->length; $i++) { + + $xremove = $elems->item($i); + $remove[] = $xpath->evaluate('string(d:href)', $xremove); + + } + + return array($set, $remove); + + } + + /** + * Parses the 'invite-reply' POST request. + * + * This method returns an array, containing the following properties: + * * href - The sharee who is replying + * * status - One of the self::STATUS_* constants + * * calendarUri - The url of the shared calendar + * * inReplyTo - The unique id of the share invitation. + * * summary - Optional description of the reply. + * + * @param \DOMDocument $dom + * @return array + */ + protected function parseInviteReplyRequest(\DOMDocument $dom) { + + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER); + $xpath->registerNamespace('d', 'urn:DAV'); + + $hostHref = $xpath->evaluate('string(cs:hosturl/d:href)'); + if (!$hostHref) { + throw new DAV\Exception\BadRequest('The {' . Plugin::NS_CALENDARSERVER . '}hosturl/{DAV:}href element is required'); + } + + return array( + 'href' => $xpath->evaluate('string(d:href)'), + 'calendarUri' => $this->server->calculateUri($hostHref), + 'inReplyTo' => $xpath->evaluate('string(cs:in-reply-to)'), + 'summary' => $xpath->evaluate('string(cs:summary)'), + 'status' => $xpath->evaluate('boolean(cs:invite-accepted)')?self::STATUS_ACCEPTED:self::STATUS_DECLINED + ); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php b/vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php new file mode 100644 index 000000000..a3be4fdd3 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php @@ -0,0 +1,342 @@ +caldavBackend = $caldavBackend; + $this->principalInfo = $principalInfo; + + } + + /** + * Returns the name of this object + * + * @return string + */ + public function getName() { + + list(,$name) = DAV\URLUtil::splitPath($this->principalInfo['uri']); + return $name; + + } + + /** + * Updates the name of this object + * + * @param string $name + * @return void + */ + public function setName($name) { + + throw new DAV\Exception\Forbidden(); + + } + + /** + * Deletes this object + * + * @return void + */ + public function delete() { + + throw new DAV\Exception\Forbidden(); + + } + + /** + * Returns the last modification date + * + * @return int + */ + public function getLastModified() { + + return null; + + } + + /** + * Creates a new file under this object. + * + * This is currently not allowed + * + * @param string $filename + * @param resource $data + * @return void + */ + public 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 + */ + public 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 + * @todo needs optimizing + * @return Calendar + */ + public function getChild($name) { + + foreach($this->getChildren() as $child) { + if ($name==$child->getName()) + return $child; + + } + throw new DAV\Exception\NotFound('Calendar with name \'' . $name . '\' could not be found'); + + } + + /** + * Checks if a calendar exists. + * + * @param string $name + * @todo needs optimizing + * @return bool + */ + public function childExists($name) { + + foreach($this->getChildren() as $child) { + if ($name==$child->getName()) + return true; + + } + return false; + + } + + /** + * Returns a list of calendars + * + * @return array + */ + public function getChildren() { + + $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); + $objs = array(); + 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); + } + } + $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']); + } + return $objs; + + } + + /** + * Creates a new calendar + * + * @param string $name + * @param array $resourceType + * @param array $properties + * @return void + */ + public function createExtendedCollection($name, array $resourceType, array $properties) { + + $isCalendar = false; + foreach($resourceType 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; + default : + throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt); + } + } + if (!$isCalendar) { + throw new DAV\Exception\InvalidResourceType('You can only create calendars in this collection'); + } + $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + public 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 + */ + public 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 + */ + public function getACL() { + + return array( + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'], + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->principalInfo['uri'], + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', + 'protected' => true, + ), + array( + '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 + */ + public 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 + */ + public 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 + */ + public 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); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Version.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Version.php new file mode 100644 index 000000000..5f305820a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/CalDAV/Version.php @@ -0,0 +1,24 @@ +