merge master into filesystem

remotes/origin/stable5
Robin Appelman 14 years ago
commit c964fe9f8d
  1. 26
      3rdparty/Sabre.includes.php
  2. 190
      3rdparty/Sabre/CalDAV/Backend/Abstract.php
  3. 231
      3rdparty/Sabre/CalDAV/Backend/BackendInterface.php
  4. 47
      3rdparty/Sabre/CalDAV/Backend/NotificationSupport.php
  5. 337
      3rdparty/Sabre/CalDAV/Backend/PDO.php
  6. 238
      3rdparty/Sabre/CalDAV/Backend/SharingSupport.php
  7. 63
      3rdparty/Sabre/CalDAV/Calendar.php
  8. 22
      3rdparty/Sabre/CalDAV/CalendarObject.php
  9. 12
      3rdparty/Sabre/CalDAV/CalendarQueryParser.php
  10. 28
      3rdparty/Sabre/CalDAV/CalendarQueryValidator.php
  11. 13
      3rdparty/Sabre/CalDAV/CalendarRootNode.php
  12. 32
      3rdparty/Sabre/CalDAV/Exception/InvalidComponentType.php
  13. 6
      3rdparty/Sabre/CalDAV/ICSExportPlugin.php
  14. 21
      3rdparty/Sabre/CalDAV/ICalendar.php
  15. 0
      3rdparty/Sabre/CalDAV/ICalendarObject.php
  16. 48
      3rdparty/Sabre/CalDAV/IShareableCalendar.php
  17. 22
      3rdparty/Sabre/CalDAV/ISharedCalendar.php
  18. 169
      3rdparty/Sabre/CalDAV/Notifications/Collection.php
  19. 22
      3rdparty/Sabre/CalDAV/Notifications/ICollection.php
  20. 38
      3rdparty/Sabre/CalDAV/Notifications/INode.php
  21. 43
      3rdparty/Sabre/CalDAV/Notifications/INotificationType.php
  22. 188
      3rdparty/Sabre/CalDAV/Notifications/Node.php
  23. 276
      3rdparty/Sabre/CalDAV/Notifications/Notification/Invite.php
  24. 216
      3rdparty/Sabre/CalDAV/Notifications/Notification/InviteReply.php
  25. 179
      3rdparty/Sabre/CalDAV/Notifications/Notification/SystemStatus.php
  26. 579
      3rdparty/Sabre/CalDAV/Plugin.php
  27. 0
      3rdparty/Sabre/CalDAV/Principal/Collection.php
  28. 0
      3rdparty/Sabre/CalDAV/Principal/ProxyRead.php
  29. 0
      3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php
  30. 0
      3rdparty/Sabre/CalDAV/Principal/User.php
  31. 72
      3rdparty/Sabre/CalDAV/Property/AllowedSharingModes.php
  32. 173
      3rdparty/Sabre/CalDAV/Property/Invite.php
  33. 99
      3rdparty/Sabre/CalDAV/Property/ScheduleCalendarTransp.php
  34. 0
      3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php
  35. 0
      3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php
  36. 0
      3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php
  37. 18
      3rdparty/Sabre/CalDAV/Schedule/IMip.php
  38. 0
      3rdparty/Sabre/CalDAV/Schedule/IOutbox.php
  39. 10
      3rdparty/Sabre/CalDAV/Schedule/Outbox.php
  40. 68
      3rdparty/Sabre/CalDAV/Server.php
  41. 72
      3rdparty/Sabre/CalDAV/ShareableCalendar.php
  42. 98
      3rdparty/Sabre/CalDAV/SharedCalendar.php
  43. 475
      3rdparty/Sabre/CalDAV/SharingPlugin.php
  44. 64
      3rdparty/Sabre/CalDAV/UserCalendars.php
  45. 2
      3rdparty/Sabre/CalDAV/Version.php
  46. 25
      3rdparty/Sabre/CalDAV/includes.php
  47. 4
      3rdparty/Sabre/CardDAV/AddressBook.php
  48. 0
      3rdparty/Sabre/CardDAV/AddressBookQueryParser.php
  49. 0
      3rdparty/Sabre/CardDAV/AddressBookRoot.php
  50. 0
      3rdparty/Sabre/CardDAV/Backend/Abstract.php
  51. 0
      3rdparty/Sabre/CardDAV/Backend/PDO.php
  52. 16
      3rdparty/Sabre/CardDAV/Card.php
  53. 0
      3rdparty/Sabre/CardDAV/IAddressBook.php
  54. 0
      3rdparty/Sabre/CardDAV/ICard.php
  55. 0
      3rdparty/Sabre/CardDAV/IDirectory.php
  56. 55
      3rdparty/Sabre/CardDAV/Plugin.php
  57. 0
      3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php
  58. 0
      3rdparty/Sabre/CardDAV/UserAddressBooks.php
  59. 107
      3rdparty/Sabre/CardDAV/VCFExportPlugin.php
  60. 2
      3rdparty/Sabre/CardDAV/Version.php
  61. 1
      3rdparty/Sabre/CardDAV/includes.php
  62. 0
      3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php
  63. 0
      3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php
  64. 0
      3rdparty/Sabre/DAV/Auth/Backend/Apache.php
  65. 0
      3rdparty/Sabre/DAV/Auth/Backend/File.php
  66. 0
      3rdparty/Sabre/DAV/Auth/Backend/PDO.php
  67. 0
      3rdparty/Sabre/DAV/Auth/IBackend.php
  68. 0
      3rdparty/Sabre/DAV/Auth/Plugin.php
  69. 0
      3rdparty/Sabre/DAV/Browser/GuessContentType.php
  70. 0
      3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php
  71. 2
      3rdparty/Sabre/DAV/Browser/Plugin.php
  72. 0
      3rdparty/Sabre/DAV/Browser/assets/favicon.ico
  73. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png
  74. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png
  75. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/card.png
  76. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/collection.png
  77. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/file.png
  78. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/parent.png
  79. 0
      3rdparty/Sabre/DAV/Browser/assets/icons/principal.png
  80. 69
      3rdparty/Sabre/DAV/Client.php
  81. 6
      3rdparty/Sabre/DAV/Collection.php
  82. 17
      3rdparty/Sabre/DAV/Directory.php
  83. 0
      3rdparty/Sabre/DAV/Exception.php
  84. 0
      3rdparty/Sabre/DAV/Exception/BadRequest.php
  85. 0
      3rdparty/Sabre/DAV/Exception/Conflict.php
  86. 0
      3rdparty/Sabre/DAV/Exception/ConflictingLock.php
  87. 0
      3rdparty/Sabre/DAV/Exception/FileNotFound.php
  88. 0
      3rdparty/Sabre/DAV/Exception/Forbidden.php
  89. 0
      3rdparty/Sabre/DAV/Exception/InsufficientStorage.php
  90. 0
      3rdparty/Sabre/DAV/Exception/InvalidResourceType.php
  91. 0
      3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php
  92. 0
      3rdparty/Sabre/DAV/Exception/Locked.php
  93. 0
      3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php
  94. 0
      3rdparty/Sabre/DAV/Exception/NotAuthenticated.php
  95. 0
      3rdparty/Sabre/DAV/Exception/NotFound.php
  96. 0
      3rdparty/Sabre/DAV/Exception/NotImplemented.php
  97. 0
      3rdparty/Sabre/DAV/Exception/PaymentRequired.php
  98. 0
      3rdparty/Sabre/DAV/Exception/PreconditionFailed.php
  99. 4
      3rdparty/Sabre/DAV/Exception/ReportNotSupported.php
  100. 0
      3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,26 +0,0 @@
<?php
/**
* Library include file
*
* This file is deprecated, don't use it!
* Instead, use the specific includes files that are in the sub-packages.
*
* Sabre/DAV/includes.php
* Sabre/HTTP/includes.php
*
* etc..
*
* This file contains all includes to the rest of the SabreDAV library
* Make sure the lib/ directory is in PHP's include_path.
*
* @package Sabre
* @deprecated Don't use this file, it will be remove in a future version
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
include 'Sabre/HTTP/includes.php';
include 'Sabre/DAV/includes.php';

@ -1,47 +1,19 @@
<?php
use Sabre\VObject;
/**
* Abstract Calendaring backend. Extend this class to create your own backends.
*
* Checkout the BackendInterface for all the methods that must be implemented.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_CalDAV_Backend_Abstract {
/**
* 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
*/
abstract function getCalendarsForUser($principalUri);
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
*/
abstract function createCalendar($principalUri,$calendarUri,array $properties);
abstract class Sabre_CalDAV_Backend_Abstract implements Sabre_CalDAV_Backend_BackendInterface {
/**
* Updates properties for a calendar.
@ -75,7 +47,7 @@ abstract class Sabre_CalDAV_Backend_Abstract {
* (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 mixed $calendarId
* @param array $mutations
* @return bool|array
*/
@ -86,83 +58,97 @@ abstract class Sabre_CalDAV_Backend_Abstract {
}
/**
* Delete a calendar and all it's objects
* Performs a calendar-query on the contents of this calendar.
*
* @param string $calendarId
* @return void
*/
abstract 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 string $calendarId
* @return array
*/
abstract function getCalendarObjects($calendarId);
/**
* Returns information from a single calendar object, based on it's object
* uri.
* 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.
*
* 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 provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* @param string $calendarId
* @param string $objectUri
* 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
*/
abstract function getCalendarObject($calendarId,$objectUri);
public function calendarQuery($calendarId, array $filters) {
/**
* Creates a new calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @param string $calendarData
* @return void
*/
abstract function createCalendarObject($calendarId,$objectUri,$calendarData);
$result = array();
$objects = $this->getCalendarObjects($calendarId);
/**
* Updates an existing calendarobject, based on it's uri.
*
* @param string $calendarId
* @param string $objectUri
* @param string $calendarData
* @return void
*/
abstract function updateCalendarObject($calendarId,$objectUri,$calendarData);
$validator = new Sabre_CalDAV_CalendarQueryValidator();
foreach($objects as $object) {
if ($this->validateFilterForObject($object, $filters)) {
$result[] = $object['uri'];
}
}
return $result;
}
/**
* Deletes an existing calendar object.
* This method validates if a filters (as passed to calendarQuery) matches
* the given object.
*
* @param string $calendarId
* @param string $objectUri
* @return void
* @param array $object
* @param array $filter
* @return bool
*/
abstract function deleteCalendarObject($calendarId,$objectUri);
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 Sabre_CalDAV_CalendarQueryValidator();
return $validator->validate($vObject, $filters);
}
}

@ -0,0 +1,231 @@
<?php
/**
* Every CalDAV backend must at least implement this interface.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Backend_BackendInterface {
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which 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);
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
*/
public function createCalendar($principalUri,$calendarUri,array $properties);
/**
* 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 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.
*
* @param mixed $calendarId
* @param string $objectUri
* @return array
*/
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);
}

@ -0,0 +1,47 @@
<?php
/**
* Adds caldav notification support to a backend.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* Notifications are defined at:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
*
* These notifications are basically a list of server-generated notifications
* displayed to the user. Users can dismiss notifications by deleting them.
*
* The primary usecase is to allow for calendar-sharing.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Backend_NotificationSupport extends Sabre_CalDAV_Backend_BackendInterface {
/**
* Returns a list of notifications for a given principal url.
*
* The returned array should only consist of implementations of
* Sabre_CalDAV_Notifications_INotificationType.
*
* @param string $principalUri
* @return array
*/
public function getNotificationsForPrincipal($principalUri);
/**
* This deletes a specific notifcation.
*
* This may be called by a client once it deems a notification handled.
*
* @param string $principalUri
* @param Sabre_CalDAV_Notifications_INotificationType $notification
* @return void
*/
public function deleteNotification($principalUri, Sabre_CalDAV_Notifications_INotificationType $notification);
}

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* PDO CalDAV backend
*
@ -9,11 +11,21 @@
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
/**
* We need to specify a max date, because we need to stop *somewhere*
*
* On 32 bit system the maximum for a signed integer is 2147483647, so
* MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
* in 2038-01-19 to avoid problems when the date is converted
* to a unix timestamp.
*/
const MAX_DATE = '2038-01-01';
/**
* pdo
*
@ -37,8 +49,9 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
/**
* List of CalDAV properties, and how they map to database fieldnames
* Add your own properties by simply adding on to this array.
*
* Add your own properties by simply adding on to this array
* Note that only string-based properties are supported here.
*
* @var array
*/
@ -90,6 +103,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
$fields[] = 'ctag';
$fields[] = 'components';
$fields[] = 'principaluri';
$fields[] = 'transparent';
// Making fields a comma-delimited list
$fields = implode(', ', $fields);
@ -110,6 +124,7 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
'principaluri' => $row['principaluri'],
'{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $row['ctag']?$row['ctag']:'0',
'{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet($components),
'{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-calendar-transp' => new Sabre_CalDAV_Property_ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
);
@ -142,11 +157,13 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
'principaluri',
'uri',
'ctag',
'transparent',
);
$values = array(
':principaluri' => $principalUri,
':uri' => $calendarUri,
':ctag' => 1,
':transparent' => 0,
);
// Default value
@ -160,6 +177,10 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
}
$values[':components'] = implode(',',$properties[$sccs]->getValue());
}
$transp = '{' . Sabre_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])) {
@ -225,17 +246,25 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
foreach($mutations as $propertyName=>$propertyValue) {
// We don't know about this property.
if (!isset($this->propertyMap[$propertyName])) {
$hasError = true;
$result[403][$propertyName] = null;
unset($mutations[$propertyName]);
continue;
switch($propertyName) {
case '{' . Sabre_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;
}
$fieldName = $this->propertyMap[$propertyName];
$newValues[$fieldName] = $propertyValue;
}
// If there were any errors we need to fail the request
@ -316,9 +345,22 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
*/
public function getCalendarObjects($calendarId) {
$stmt = $this->pdo->prepare('SELECT * FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
$stmt->execute(array($calendarId));
return $stmt->fetchAll();
$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;
}
@ -336,44 +378,166 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
*/
public function getCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('SELECT * FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId, $objectUri));
return $stmt->fetch();
$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.
*
* @param string $calendarId
* 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 void
* @return string|null
*/
public function createCalendarObject($calendarId,$objectUri,$calendarData) {
$stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified) VALUES (?,?,?,?)');
$stmt->execute(array($calendarId,$objectUri,$calendarData,time()));
$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.
*
* @param string $calendarId
* 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 void
* @return string|null
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData) {
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ? WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarData,time(),$calendarId,$objectUri));
$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->value));
$lastOccurence = $endDate->getTimeStamp();
} elseif ($component->DTSTART->getDateType()===VObject\Property\DateTime::DATE) {
$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,
);
}
/**
@ -392,5 +556,132 @@ class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
}
/**
* 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;
}
}

@ -0,0 +1,238 @@
<?php
/**
* Adds support for sharing features to a CalDAV server.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* Early warning: Currently SabreDAV provides no implementation for this. This
* is, because in it's current state there is no elegant way to do this.
* The problem lies in the fact that a real CalDAV server with sharing support
* would first need email support (with invite notifications), and really also
* a browser-frontend that allows people to accept or reject these shares.
*
* In addition, the CalDAV backends are currently kept as independent as
* possible, and should not be aware of principals, email addresses or
* accounts.
*
* Adding an implementation for Sharing to standard-sabredav would contradict
* these goals, so for this reason this is currently not implemented, although
* it may very well in the future; but probably not before SabreDAV 2.0.
*
* The interface works however, so if you implement all this, and do it
* correctly sharing _will_ work. It's not particularly easy, and I _urge you_
* to make yourself acquainted with the following document first:
*
* https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* An overview
* ===========
*
* Implementing this interface will allow a user to share his or her calendars
* to other users. Effectively, when a calendar is shared the calendar will
* show up in both the Sharer's and Sharee's calendar-home root.
* This interface adds a few methods that ensure that this happens, and there
* are also a number of new requirements in the base-class you must now follow.
*
*
* How it works
* ============
*
* When a user shares a calendar, the addShare() method will be called with a
* list of sharees that are now added, and a list of sharees that have been
* removed.
* Removal is instant, but when a sharee is added the sharee first gets a
* chance to accept or reject the invitation for a share.
*
* After a share is accepted, the calendar will be returned from
* getUserCalendars for both the sharer, and the sharee.
*
* If the sharee deletes the calendar, only their share gets deleted. When the
* owner deletes a calendar, it will be removed for everybody.
*
*
* Notifications
* =============
*
* During all these sharing operations, a lot of notifications are sent back
* and forward.
*
* Whenever the list of sharees for a calendar has been changed (they have been
* added, removed or modified) all sharees should get a notification for this
* change.
* This notification is always represented by:
*
* Sabre_CalDAV_Notifications_Notification_Invite
*
* In the case of an invite, the sharee may reply with an 'accept' or
* 'decline'. These are always represented by:
*
* Sabre_CalDAV_Notifications_Notification_Invite
*
*
* Calendar access by sharees
* ==========================
*
* As mentioned earlier, shared calendars must now also be returned for
* getCalendarsForUser for sharees. A few things change though.
*
* The following properties must be specified:
*
* 1. {http://calendarserver.org/ns/}shared-url
*
* This property MUST contain the url to the original calendar, that is.. the
* path to the calendar from the owner.
*
* 2. {http://sabredav.org/ns}owner-principal
*
* This is a url to to the principal who is sharing the calendar.
*
* 3. {http://sabredav.org/ns}read-only
*
* This should be either 0 or 1, depending on if the user has read-only or
* read-write access to the calendar.
*
* Only when this is done, the calendar will correctly be marked as a calendar
* that's shared to him, thus allowing clients to display the correct interface
* and ACL enforcement.
*
* If a sharee deletes their calendar, only their instance of the calendar
* should be deleted, the original should still exists.
* Pretty much any 'dead' WebDAV properties on these shared calendars should be
* specific to a user. This means that if the displayname is changed by a
* sharee, the original is not affected. This is also true for:
* * The description
* * The color
* * The order
* * And any other dead properties.
*
* Properties like a ctag should not be different for multiple instances of the
* calendar.
*
* Lastly, objects *within* calendars should also have user-specific data. The
* two things that are user-specific are:
* * VALARM objects
* * The TRANSP property
*
* This _also_ implies that if a VALARM is deleted by a sharee for some event,
* this has no effect on the original VALARM.
*
* Understandably, the this last requirement is one of the hardest.
* Realisticly, I can see people ignoring this part of the spec, but that could
* cause a different set of issues.
*
*
* Publishing
* ==========
*
* When a user publishes a url, the server should generate a 'publish url'.
* This is a read-only url, anybody can use to consume the calendar feed.
*
* Calendars are in one of two states:
* * published
* * unpublished
*
* If a calendar is published, the following property should be returned
* for each calendar in getCalendarsForPrincipal.
*
* {http://calendarserver.org/ns/}publish-url
*
* This element should contain a {DAV:}href element, which points to the
* public url that does not require authentication. Unlike every other href,
* this url must be absolute.
*
* Ideally, the following property is always returned
*
* {http://calendarserver.org/ns/}pre-publish-url
*
* This property should contain the url that the calendar _would_ have, if it
* were to be published. iCal uses this to display the url, before the user
* will actually publish it.
*
*
* Selectively disabling publish or share feature
* ==============================================
*
* If Sabre_CalDAV_Property_AllowedSharingModes is returned from
* getCalendarsByUser, this allows the server to specify wether either sharing,
* or publishing is supported.
*
* This allows a client to determine in advance which features are available,
* and update the interface appropriately. If this property is not returned by
* the backend, the SharingPlugin automatically injects it and assumes both
* features are available.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Backend_SharingSupport extends Sabre_CalDAV_Backend_NotificationSupport {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* Note that if the calendar is currently marked as 'not shared' by and
* this method is called, the calendar should be 'upgraded' to a shared
* calendar.
*
* @param mixed $calendarId
* @param array $add
* @param array $remove
* @return void
*/
function updateShares($calendarId, array $add, array $remove);
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre_CalDAV_SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @param mixed $calendarId
* @return array
*/
function getShares($calendarId);
/**
* This method is called when a user replied to a request to share.
*
* If the user chose to accept the share, this method should return the
* newly created calendar url.
*
* @param string href The sharee who is replying (often a mailto: address)
* @param int status One of the SharingPlugin::STATUS_* constants
* @param string $calendarUri The url to the calendar thats being shared
* @param string $inReplyTo The unique id this message is a response to
* @param string $summary A description of the reply
* @return null|string
*/
function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);
/**
* Publishes a calendar
*
* @param mixed $calendarId
* @param bool $value
* @return void
*/
function setPublishStatus($calendarId, $value);
}

@ -9,7 +9,7 @@
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProperties, Sabre_DAVACL_IACL {
@ -24,7 +24,7 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
* @var Sabre_CalDAV_Backend_BackendInterface
*/
protected $caldavBackend;
@ -39,16 +39,15 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $calendarInfo) {
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $calendarInfo) {
$this->caldavBackend = $caldavBackend;
$this->principalBackend = $principalBackend;
$this->calendarInfo = $calendarInfo;
}
/**
@ -92,9 +91,6 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' :
$response[$prop] = new Sabre_CalDAV_Property_SupportedCollationSet();
break;
case '{DAV:}owner' :
$response[$prop] = new Sabre_DAVACL_Property_Principal(Sabre_DAVACL_Property_Principal::HREF,$this->calendarInfo['principaluri']);
break;
default :
if (isset($this->calendarInfo[$prop])) $response[$prop] = $this->calendarInfo[$prop];
break;
@ -110,12 +106,21 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
* The contained calendar objects are for example Events or Todo's.
*
* @param string $name
* @return Sabre_DAV_ICalendarObject
* @return Sabre_CalDAV_ICalendarObject
*/
public function getChild($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
if (!$obj) throw new Sabre_DAV_Exception_NotFound('Calendar object not found');
$obj['acl'] = $this->getACL();
// Removing the irrelivant
foreach($obj['acl'] as $key=>$acl) {
if ($acl['privilege'] === '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy') {
unset($obj['acl'][$key]);
}
}
return new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
}
@ -130,6 +135,13 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
$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'] === '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy') {
unset($obj['acl'][$key]);
}
}
$children[] = new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
}
return $children;
@ -262,27 +274,27 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'principal' => $this->getOwner() . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'principal' => $this->getOwner() . '/calendar-proxy-read',
'protected' => true,
),
array(
@ -340,4 +352,27 @@ class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProper
}
/**
* 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);
}
}

@ -6,13 +6,13 @@
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV_ICalendarObject, Sabre_DAVACL_IACL {
/**
* Sabre_CalDAV_Backend_Abstract
* Sabre_CalDAV_Backend_BackendInterface
*
* @var array
*/
@ -35,11 +35,11 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param array $calendarInfo
* @param array $objectData
*/
public function __construct(Sabre_CalDAV_Backend_Abstract $caldavBackend,array $calendarInfo,array $objectData) {
public function __construct(Sabre_CalDAV_Backend_BackendInterface $caldavBackend,array $calendarInfo,array $objectData) {
$this->caldavBackend = $caldavBackend;
@ -85,8 +85,8 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV
/**
* Updates the ICalendar-formatted object
*
* @param string $calendarData
* @return void
* @param string|resource $calendarData
* @return string
*/
public function put($calendarData) {
@ -119,7 +119,7 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV
*/
public function getContentType() {
return 'text/calendar';
return 'text/calendar; charset=utf-8';
}
@ -143,7 +143,7 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV
/**
* Returns the last modification date as a unix timestamp
*
* @return time
* @return int
*/
public function getLastModified() {
@ -206,6 +206,12 @@ class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV
*/
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',

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* Parses the calendar-query report request body.
*
@ -68,7 +70,7 @@ class Sabre_CalDAV_CalendarQueryParser {
$this->xpath = new DOMXPath($dom);
$this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
$this->xpath->registerNameSpace('dav','urn:DAV');
$this->xpath->registerNameSpace('dav','DAV:');
}
@ -241,12 +243,12 @@ class Sabre_CalDAV_CalendarQueryParser {
$timeRangeNode = $timeRangeNodes->item(0);
if ($start = $timeRangeNode->getAttribute('start')) {
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
$start = VObject\DateTimeParser::parseDateTime($start);
} else {
$start = null;
}
if ($end = $timeRangeNode->getAttribute('end')) {
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
$end = VObject\DateTimeParser::parseDateTime($end);
} else {
$end = null;
}
@ -274,13 +276,13 @@ class Sabre_CalDAV_CalendarQueryParser {
if(!$start) {
throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element');
}
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
$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 = Sabre_VObject_DateTimeParser::parseDateTime($end);
$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.');

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* CalendarQuery Validator
*
@ -22,11 +24,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
*
* The list of filters must be formatted as parsed by Sabre_CalDAV_CalendarQueryParser
*
* @param Sabre_VObject_Component $vObject
* @param VObject\Component $vObject
* @param array $filters
* @return bool
*/
public function validate(Sabre_VObject_Component $vObject,array $filters) {
public function validate(VObject\Component $vObject,array $filters) {
// The top level object is always a component filter.
// We'll parse it manually, as it's pretty simple.
@ -48,11 +50,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
* component we're checking should be specified, not the component to check
* itself.
*
* @param Sabre_VObject_Component $parent
* @param VObject\Component $parent
* @param array $filters
* @return bool
*/
protected function validateCompFilters(Sabre_VObject_Component $parent, array $filters) {
protected function validateCompFilters(VObject\Component $parent, array $filters) {
foreach($filters as $filter) {
@ -117,11 +119,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
* property we're checking should be specified, not the property to check
* itself.
*
* @param Sabre_VObject_Component $parent
* @param VObject\Component $parent
* @param array $filters
* @return bool
*/
protected function validatePropFilters(Sabre_VObject_Component $parent, array $filters) {
protected function validatePropFilters(VObject\Component $parent, array $filters) {
foreach($filters as $filter) {
@ -187,11 +189,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
* parameter we're checking should be specified, not the parameter to check
* itself.
*
* @param Sabre_VObject_Property $parent
* @param VObject\Property $parent
* @param array $filters
* @return bool
*/
protected function validateParamFilters(Sabre_VObject_Property $parent, array $filters) {
protected function validateParamFilters(VObject\Property $parent, array $filters) {
foreach($filters as $filter) {
@ -243,11 +245,11 @@ class Sabre_CalDAV_CalendarQueryValidator {
* A single text-match should be specified as well as the specific property
* or parameter we need to validate.
*
* @param Sabre_VObject_Node $parent
* @param VObject\Node $parent
* @param array $textMatch
* @return bool
*/
protected function validateTextMatch(Sabre_VObject_Node $parent, array $textMatch) {
protected function validateTextMatch(VObject\Node $parent, array $textMatch) {
$value = (string)$parent;
@ -263,12 +265,12 @@ class Sabre_CalDAV_CalendarQueryValidator {
* This is all based on the rules specified in rfc4791, which are quite
* complex.
*
* @param Sabre_VObject_Node $component
* @param VObject\Node $component
* @param DateTime $start
* @param DateTime $end
* @return bool
*/
protected function validateTimeRange(Sabre_VObject_Node $component, $start, $end) {
protected function validateTimeRange(VObject\Node $component, $start, $end) {
if (is_null($start)) {
$start = new DateTime('1900-01-01');
@ -296,7 +298,7 @@ class Sabre_CalDAV_CalendarQueryValidator {
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
// Fire up the iterator!
$it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
$it = new VObject\RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
while($it->valid()) {
$expandedEvent = $it->getEventObject();

@ -1,14 +1,15 @@
<?php
/**
* Users collection
* Calendars collection
*
* This object is responsible for generating a collection of users.
* This object is responsible for generating a list of calendar-homes for each
* user.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollection {
@ -16,7 +17,7 @@ class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollec
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
* @var Sabre_CalDAV_Backend_BackendInterface
*/
protected $caldavBackend;
@ -32,10 +33,10 @@ class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollec
*
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param string $principalPrefix
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_Abstract $caldavBackend, $principalPrefix = 'principals') {
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $principalPrefix = 'principals') {
parent::__construct($principalBackend, $principalPrefix);
$this->caldavBackend = $caldavBackend;

@ -0,0 +1,32 @@
<?php
/**
* Sabre_CalDAV_Exception_InvalidComponentType
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Exception_InvalidComponentType extends Sabre_DAV_Exception_Forbidden {
/**
* Adds in extra information in the xml response.
*
* This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
$doc = $errorNode->ownerDocument;
$np = $doc->createElementNS(Sabre_CalDAV_Plugin::NS_CALDAV,'cal:supported-calendar-component');
$errorNode->appendChild($np);
}
}

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* ICS Exporter
*
@ -82,7 +84,7 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin {
*/
public function generateICS(array $nodes) {
$calendar = new Sabre_VObject_Component('vcalendar');
$calendar = new VObject\Component('vcalendar');
$calendar->version = '2.0';
if (Sabre_DAV_Server::$exposeVersion) {
$calendar->prodid = '-//SabreDAV//SabreDAV ' . Sabre_DAV_Version::VERSION . '//EN';
@ -103,7 +105,7 @@ class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin {
}
$nodeData = $node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'];
$nodeComp = Sabre_VObject_Reader::read($nodeData);
$nodeComp = VObject\Reader::read($nodeData);
foreach($nodeComp->children() as $child) {

@ -8,11 +8,28 @@
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_ICalendar extends Sabre_DAV_ICollection {
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre_CalDAV_CalendarQueryParser.
*
* @param array $filters
* @return array
*/
public function calendarQuery(array $filters);
}

@ -0,0 +1,48 @@
<?php
/**
* This interface represents a Calendar that can be shared with other users.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_IShareableCalendar extends Sabre_CalDAV_ICalendar {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
function updateShares(array $add, array $remove);
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre_CalDAV_SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
function getShares();
}

@ -0,0 +1,22 @@
<?php
/**
* This interface represents a Calendar that is shared by a different user.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_ISharedCalendar extends Sabre_CalDAV_ICalendar {
/**
* This method should return the url of the owners' copy of the shared
* calendar.
*
* @return string
*/
function getSharedUrl();
}

@ -0,0 +1,169 @@
<?php
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications plugin to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre_CalDAV_Notifications_INode nodes as
* its children.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Collection extends Sabre_DAV_Collection implements Sabre_CalDAV_Notifications_ICollection, Sabre_DAVACL_IACL {
/**
* The notification backend
*
* @var Sabre_CalDAV_Backend_NotificationSupport
*/
protected $caldavBackend;
/**
* Principal uri
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
* @param string $principalUri
*/
public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, $principalUri) {
$this->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 Sabre_CalDAV_Notifications_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 Sabre_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;
}
}

@ -0,0 +1,22 @@
<?php
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications plugin to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre_CalDAV_Notifications_INode nodes as
* its children.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Notifications_ICollection extends Sabre_DAV_ICollection {
}

@ -0,0 +1,38 @@
<?php
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
*
* For a complete example, check out the Notification class, which contains
* some helper functions.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Notifications_INode {
/**
* This method must return an xml element, using the
* Sabre_CalDAV_Notifications_INotificationType classes.
*
* @return Sabre_DAVNotification_INotificationType
*/
function getNotificationType();
/**
* Returns the etag for the notification.
*
* The etag must be surrounded by litteral double-quotes.
*
* @return string
*/
function getETag();
}

@ -0,0 +1,43 @@
<?php
/**
* This interface reflects a single notification type.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Notifications_INotificationType extends Sabre_DAV_PropertyInterface {
/**
* This method serializes the entire notification, as it is used in the
* response body.
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
function serializeBody(Sabre_DAV_Server $server, \DOMElement $node);
/**
* Returns a unique id for this notification
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
function getId();
/**
* Returns the ETag for this notification.
*
* The ETag must be surrounded by literal double-quotes.
*
* @return string
*/
function getETag();
}

@ -0,0 +1,188 @@
<?php
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Node extends Sabre_DAV_File implements Sabre_CalDAV_Notifications_INode, Sabre_DAVACL_IACL {
/**
* The notification backend
*
* @var Sabre_CalDAV_Backend_NotificationSupport
*/
protected $caldavBackend;
/**
* The actual notification
*
* @var Sabre_CalDAV_Notifications_INotificationType
*/
protected $notification;
/**
* Owner principal of the notification
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
* @param string $principalUri
* @param Sabre_CalDAV_Notifications_INotificationType $notification
*/
public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, $principalUri, Sabre_CalDAV_Notifications_INotificationType $notification) {
$this->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 Sabre_DAVNotification_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 Sabre_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;
}
}

@ -0,0 +1,276 @@
<?php
use Sabre_CalDAV_SharingPlugin as SharingPlugin;
/**
* This class represents the cs:invite-notification notification element.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Notification_Invite extends Sabre_DAV_Property implements Sabre_CalDAV_Notifications_INotificationType {
/**
* A unique id for the message
*
* @var string
*/
protected $id;
/**
* Timestamp of the notification
*
* @var DateTime
*/
protected $dtStamp;
/**
* A url to the recipient of the notification. This can be an email
* address (mailto:), or a principal url.
*
* @var string
*/
protected $href;
/**
* The type of message, see the SharingPlugin::STATUS_* constants.
*
* @var int
*/
protected $type;
/**
* True if access to a calendar is read-only.
*
* @var bool
*/
protected $readOnly;
/**
* A url to the shared calendar.
*
* @var string
*/
protected $hostUrl;
/**
* Url to the sharer of the calendar
*
* @var string
*/
protected $organizer;
/**
* The name of the sharer.
*
* @var string
*/
protected $commonName;
/**
* A description of the share request
*
* @var string
*/
protected $summary;
/**
* The Etag for the notification
*
* @var string
*/
protected $etag;
/**
* The list of supported components
*
* @var Sabre_CalDAV_Property_SupportedCalendarComponentSet
*/
protected $supportedComponents;
/**
* Creates the Invite notification.
*
* This constructor receives an array with the following elements:
*
* * id - A unique id
* * etag - The etag
* * dtStamp - A DateTime object with a timestamp for the notification.
* * type - The type of notification, see SharingPlugin::STATUS_*
* constants for details.
* * readOnly - This must be set to true, if this is an invite for
* read-only access to a calendar.
* * hostUrl - A url to the shared calendar.
* * organizer - Url to the sharer principal.
* * commonName - The real name of the sharer (optional).
* * summary - Description of the share, can be the same as the
* calendar, but may also be modified (optional).
* * supportedComponents - An instance of
* Sabre_CalDAV_Property_SupportedCalendarComponentSet.
* This allows the client to determine which components
* will be supported in the shared calendar. This is
* also optional.
*
* @param array $values All the options
*/
public function __construct(array $values) {
$required = array(
'id',
'etag',
'href',
'dtStamp',
'type',
'readOnly',
'hostUrl',
'organizer',
);
foreach($required as $item) {
if (!isset($values[$item])) {
throw new InvalidArgumentException($item . ' is a required constructor option');
}
}
foreach($values as $key=>$value) {
if (!property_exists($this, $key)) {
throw new InvalidArgumentException('Unknown option: ' . $key);
}
$this->$key = $value;
}
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_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 Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serializeBody(Sabre_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);
$organizerHref = $doc->createElement('d:href', $server->getBaseUri() . $this->organizer);
$organizerUrl = $doc->createElement('cs:organizer');
if ($this->commonName) {
$commonName = $doc->createElement('cs:common-name');
$commonName->appendChild($doc->createTextNode($this->commonName));
$organizerUrl->appendChild($commonName);
}
$organizerUrl->appendChild($organizerHref);
$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;
}
}

@ -0,0 +1,216 @@
<?php
use Sabre_CalDAV_SharingPlugin as SharingPlugin;
/**
* This class represents the cs:invite-reply notification element.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Notification_InviteReply extends Sabre_DAV_Property implements Sabre_CalDAV_Notifications_INotificationType {
/**
* A unique id for the message
*
* @var string
*/
protected $id;
/**
* Timestamp of the notification
*
* @var DateTime
*/
protected $dtStamp;
/**
* The unique id of the notification this was a reply to.
*
* @var string
*/
protected $inReplyTo;
/**
* A url to the recipient of the original (!) notification.
*
* @var string
*/
protected $href;
/**
* The type of message, see the SharingPlugin::STATUS_ constants.
*
* @var int
*/
protected $type;
/**
* A url to the shared calendar.
*
* @var string
*/
protected $hostUrl;
/**
* A description of the share request
*
* @var string
*/
protected $summary;
/**
* Notification Etag
*
* @var string
*/
protected $etag;
/**
* Creates the Invite Reply Notification.
*
* This constructor receives an array with the following elements:
*
* * id - A unique id
* * etag - The etag
* * dtStamp - A DateTime object with a timestamp for the notification.
* * inReplyTo - This should refer to the 'id' of the notification
* this is a reply to.
* * type - The type of notification, see SharingPlugin::STATUS_*
* constants for details.
* * hostUrl - A url to the shared calendar.
* * summary - Description of the share, can be the same as the
* calendar, but may also be modified (optional).
*/
public function __construct(array $values) {
$required = array(
'id',
'etag',
'href',
'dtStamp',
'inReplyTo',
'type',
'hostUrl',
);
foreach($required as $item) {
if (!isset($values[$item])) {
throw new InvalidArgumentException($item . ' is a required constructor option');
}
}
foreach($values as $key=>$value) {
if (!property_exists($this, $key)) {
throw new InvalidArgumentException('Unknown option: ' . $key);
}
$this->$key = $value;
}
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_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 Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serializeBody(Sabre_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;
}
}

@ -0,0 +1,179 @@
<?php
/**
* SystemStatus notification
*
* This notification can be used to indicate to the user that the system is
* down.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Notification_SystemStatus extends Sabre_DAV_Property implements Sabre_CalDAV_Notifications_INotificationType {
const TYPE_LOW = 1;
const TYPE_MEDIUM = 2;
const TYPE_HIGH = 3;
/**
* A unique id
*
* @var string
*/
protected $id;
/**
* The type of alert. This should be one of the TYPE_ constants.
*
* @var int
*/
protected $type;
/**
* A human-readable description of the problem.
*
* @var string
*/
protected $description;
/**
* A url to a website with more information for the user.
*
* @var string
*/
protected $href;
/**
* Notification Etag
*
* @var string
*/
protected $etag;
/**
* Creates the notification.
*
* Some kind of unique id should be provided. This is used to generate a
* url.
*
* @param string $id
* @param string $etag
* @param int $type
* @param string $description
* @param string $href
*/
public function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null) {
$this->id = $id;
$this->type = $type;
$this->description = $description;
$this->href = $href;
$this->etag = $etag;
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_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 Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serializeBody(Sabre_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;
}
}

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* CalDAV plugin
*
@ -24,16 +26,6 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
*/
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
/**
* The following constants are used to differentiate
* the various filters for the calendar-query report
*/
const FILTER_COMPFILTER = 1;
const FILTER_TIMERANGE = 3;
const FILTER_PROPFILTER = 4;
const FILTER_PARAMFILTER = 5;
const FILTER_TEXTMATCH = 6;
/**
* The hardcoded root for calendar objects. It is unfortunate
* that we're stuck with it, but it will have to do for now
@ -172,16 +164,19 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
$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_ProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
$server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
$server->resourceTypeMapping['Sabre_CalDAV_Notifications_ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';
array_push($server->protectedProperties,
@ -205,7 +200,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
// CalendarServer extensions
'{' . self::NS_CALENDARSERVER . '}getctag',
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for',
'{' . self::NS_CALENDARSERVER . '}notification-URL',
'{' . self::NS_CALENDARSERVER . '}notificationtype'
);
}
@ -226,6 +223,13 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
// 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);
@ -235,7 +239,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if (!$node instanceof Sabre_CalDAV_Schedule_IOutbox)
return;
$this->outboxRequest($node);
$this->outboxRequest($node, $uri);
return false;
}
@ -348,7 +352,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if (in_array($calProp,$requestedProperties)) {
$addresses = $node->getAlternateUriSet();
$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl();
$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
unset($requestedProperties[$calProp]);
$returnedProperties[200][$calProp] = new Sabre_DAV_Property_HrefList($addresses, false);
@ -390,8 +394,31 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
// 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 Sabre_DAV_Property_Href($calendarHomePath);
}
} // instanceof IPrincipal
if ($node instanceof Sabre_CalDAV_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 Sabre_CalDAV_ICalendarObject) {
// The calendar-data property is not supposed to be a 'real'
@ -424,11 +451,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
public function calendarMultiGetReport($dom) {
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
$hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
$hrefElems = $dom->getElementsByTagNameNS('DAV:','href');
$xpath = new DOMXPath($dom);
$xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
$xpath->registerNameSpace('dav','urn:DAV');
$xpath->registerNameSpace('dav','DAV:');
$expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand');
if ($expand->length>0) {
@ -438,8 +465,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if(!$start || !$end) {
throw new Sabre_DAV_Exception_BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element');
}
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
$start = VObject\DateTimeParser::parseDateTime($start);
$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.');
@ -458,7 +485,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
list($objProps) = $this->server->getPropertiesForPath($uri,$properties);
if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
$vObject = Sabre_VObject_Reader::read($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();
}
@ -467,9 +494,12 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
$prefer = $this->server->getHTTPPRefer();
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
$this->server->httpResponse->setHeader('Vary','Brief,Prefer');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList, $prefer['return-minimal']));
}
@ -487,54 +517,95 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
$parser = new Sabre_CalDAV_CalendarQueryParser($dom);
$parser->parse();
$requestedCalendarData = true;
$requestedProperties = $parser->requestedProperties;
$node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
$depth = $this->server->getHTTPDepth(0);
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
// The default result is an empty array
$result = array();
// We always retrieve calendar-data, as we need it for filtering.
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
// The calendarobject was requested directly. In this case we handle
// this locally.
if ($depth == 0 && $node instanceof Sabre_CalDAV_ICalendarObject) {
// If calendar-data wasn't explicitly requested, we need to remove
// it after processing.
$requestedCalendarData = false;
}
$requestedCalendarData = true;
$requestedProperties = $parser->requestedProperties;
// These are the list of nodes that potentially match the requirement
$candidateNodes = $this->server->getPropertiesForPath(
$this->server->getRequestUri(),
$requestedProperties,
$this->server->getHTTPDepth(0)
);
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
$verifiedNodes = array();
// We always retrieve calendar-data, as we need it for filtering.
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
$validator = new Sabre_CalDAV_CalendarQueryValidator();
// If calendar-data wasn't explicitly requested, we need to remove
// it after processing.
$requestedCalendarData = false;
}
foreach($candidateNodes as $node) {
$properties = $this->server->getPropertiesForPath(
$this->server->getRequestUri(),
$requestedProperties,
0
);
// If the node didn't have a calendar-data property, it must not be a calendar object
if (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']))
continue;
// This array should have only 1 element, the first calendar
// object.
$properties = current($properties);
$vObject = Sabre_VObject_Reader::read($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
if ($validator->validate($vObject,$parser->filters)) {
// 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 Sabre_CalDAV_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 (!$requestedCalendarData) {
unset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
}
}
}
// If we're dealing with a calendar, the calendar itself is responsible
// for the calendar-query.
if ($node instanceof Sabre_CalDAV_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']);
$node[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
}
$verifiedNodes[] = $node;
$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->sendBody($this->server->generateMultiStatus($verifiedNodes));
$this->server->httpResponse->setHeader('Vary','Brief,Prefer');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal']));
}
@ -561,10 +632,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
if ($start) {
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
$start = VObject\DateTimeParser::parseDateTime($start);
}
if ($end) {
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
$end = VObject\DateTimeParser::parseDateTime($end);
}
if (!$start && !$end) {
@ -583,15 +654,33 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
throw new Sabre_DAV_Exception_NotImplemented('The free-busy-query REPORT is only implemented on calendars');
}
$objects = array_map(function($child) {
$obj = $child->get();
if (is_resource($obj)) {
$obj = stream_get_contents($obj);
}
// 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;
}, $calendar->getChildren());
}, $urls);
$generator = new Sabre_VObject_FreeBusyGenerator();
$generator = new VObject\FreeBusyGenerator();
$generator->setObjects($objects);
$generator->setTimeRange($start, $end);
$result = $generator->getResult();
@ -620,7 +709,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if (!$node instanceof Sabre_CalDAV_ICalendarObject)
return;
$this->validateICalendar($data);
$this->validateICalendar($data, $path);
}
@ -640,7 +729,52 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if (!$parentNode instanceof Sabre_CalDAV_Calendar)
return;
$this->validateICalendar($data);
$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 (Sabre_DAV_Exception_NotFound $e) {
return;
}
if (!$node instanceof Sabre_CalDAV_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;
}
@ -650,9 +784,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
* An exception is thrown if it's not.
*
* @param resource|string $data
* @param string $path
* @return void
*/
protected function validateICalendar(&$data) {
protected function validateICalendar(&$data, $path) {
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
@ -664,9 +799,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
try {
$vobj = Sabre_VObject_Reader::read($data);
$vobj = VObject\Reader::read($data);
} catch (Sabre_VObject_ParseException $e) {
} catch (VObject\ParseException $e) {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
@ -676,6 +811,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support iCalendar objects.');
}
// Get the Supported Components for the target calendar
list($parentPath,$object) = Sabre_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) {
@ -687,6 +827,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
case 'VJOURNAL' :
if (is_null($foundType)) {
$foundType = $component->name;
if (!in_array($foundType, $supportedComponents)) {
throw new Sabre_CalDAV_Exception_InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
}
if (!isset($component->UID)) {
throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' component must have an UID');
}
@ -711,12 +854,81 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
/**
* This method handles POST requests to the schedule-outbox
* 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 Sabre_CalDAV_Schedule_IOutbox $outboxNode
* @param string $outboxUri
* @return void
*/
public function outboxRequest(Sabre_CalDAV_Schedule_IOutbox $outboxNode, $outboxUri) {
// Parsing the request body
try {
$vObject = VObject\Reader::read($this->server->httpRequest->getBody(true));
} catch (VObject\ParseException $e) {
throw new Sabre_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 Sabre_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 Sabre_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,'{' . Sabre_CalDAV_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,'{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-post-vevent');
$this->handleEventNotification($outboxNode, $vObject);
} else {
throw new Sabre_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
*/
public function outboxRequest(Sabre_CalDAV_Schedule_IOutbox $outboxNode) {
protected function handleEventNotification(Sabre_CalDAV_Schedule_IOutbox $outboxNode, VObject\Component $vObject) {
$originator = $this->server->httpRequest->getHeader('Originator');
$recipients = $this->server->httpRequest->getHeader('Recipient');
@ -760,38 +972,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
throw new Sabre_DAV_Exception_Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header');
}
try {
$vObject = Sabre_VObject_Reader::read($this->server->httpRequest->getBody(true));
} catch (Sabre_VObject_ParseException $e) {
throw new Sabre_DAV_Exception_BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
}
// Checking for the object type
$componentType = null;
foreach($vObject->getComponents() as $component) {
if ($component->name !== 'VTIMEZONE') {
$componentType = $component->name;
break;
}
}
if (is_null($componentType)) {
throw new Sabre_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 Sabre_DAV_Exception_BadRequest('A METHOD property must be specified in iTIP messages');
}
if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') {
$result = $this->iMIPMessage($originator, $recipients, $vObject);
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->setHeader('Content-Type','application/xml');
$this->server->httpResponse->sendBody($this->generateScheduleResponse($result));
} else {
throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented');
}
$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));
}
@ -813,15 +997,15 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
*
* @param string $originator
* @param array $recipients
* @param Sabre_VObject_Component $vObject
* @param Sabre\VObject\Component $vObject
* @return array
*/
protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject) {
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);
$this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
$resultStatus = '2.0;Success';
}
@ -832,7 +1016,6 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
return $result;
}
/**
@ -877,6 +1060,204 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
/**
* This method is responsible for parsing a free-busy query request and
* returning it's result.
*
* @param Sabre_CalDAV_Schedule_IOutbox $outbox
* @param string $request
* @return string
*/
protected function handleFreeBusyRequest(Sabre_CalDAV_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 = '{' . Sabre_CalDAV_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 Sabre_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 Sabre_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 Sabre_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 Sabre_VObject_Component $request
* @return Sabre_VObject_Component
*/
protected function getFreeBusyForEmail($email, DateTime $start, DateTime $end, VObject\Component $request) {
$caldavNS = '{' . Sabre_CalDAV_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 Sabre_CalDAV_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 = VObject\Component::create('VCALENDAR');
$vcalendar->VERSION = '2.0';
$vcalendar->METHOD = 'REPLY';
$vcalendar->CALSCALE = 'GREGORIAN';
$vcalendar->PRODID = '-//SabreDAV//SabreDAV ' . Sabre_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
* Sabre_DAV_Browser_Plugin. This allows us to generate an interface users

@ -0,0 +1,72 @@
<?php
/**
* AllowedSharingModes
*
* This property encodes the 'allowed-sharing-modes' property, as defined by
* the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
* namespace.
*
* This property is a representation of the supported-calendar_component-set
* property in the CalDAV namespace. It simply requires an array of components,
* such as VEVENT, VTODO
*
* @package Sabre
* @subpackage CalDAV
* @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Property_AllowedSharingModes extends Sabre_DAV_Property {
/**
* Whether or not a calendar can be shared with another user
*
* @var bool
*/
protected $canBeShared;
/**
* Whether or not the calendar can be placed on a public url.
*
* @var bool
*/
protected $canBePublished;
/**
* Constructor
*
* @param bool $canBeShared
* @param bool $canBePublished
* @return void
*/
public function __construct($canBeShared, $canBePublished) {
$this->canBeShared = $canBeShared;
$this->canBePublished = $canBePublished;
}
/**
* Serializes the property in a DOMDocument
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_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);
}
}
}

@ -0,0 +1,173 @@
<?php
use Sabre_CalDAV_SharingPlugin as SharingPlugin;
/**
* Invite property
*
* This property encodes the 'invite' property, as defined by
* the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
* namespace.
*
* @package Sabre
* @subpackage CalDAV
* @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Property_Invite extends Sabre_DAV_Property {
/**
* The list of users a calendar has been shared to.
*
* @var array
*/
protected $users;
/**
* Creates the property.
*
* Users is an array. Each element of the array has the following
* properties:
*
* * href - Often a mailto: address
* * commonName - Optional, for example a first and lastname for a user.
* * status - One of the SharingPlugin::STATUS_* constants.
* * readOnly - true or false
* * summary - Optional, description of the share
*
* @param array $users
*/
public function __construct(array $users) {
$this->users = $users;
}
/**
* 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 Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $node) {
$doc = $node->ownerDocument;
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 Sabre_DAV_IProperty
*/
static function unserialize(DOMElement $prop) {
$xpath = new \DOMXPath($prop->ownerDocument);
$xpath->registerNamespace('cs', Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
$xpath->registerNamespace('d', '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 Sabre_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);
}
}

@ -0,0 +1,99 @@
<?php
/**
* schedule-calendar-transp property.
*
* This property is a representation of the schedule-calendar-transp property.
* This property is defined in RFC6638 (caldav scheduling).
*
* Its values are either 'transparent' or 'opaque'. If it's transparent, it
* means that this calendar will not be taken into consideration when a
* different user queries for free-busy information. If it's 'opaque', it will.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Property_ScheduleCalendarTransp extends Sabre_DAV_Property {
const TRANSPARENT = 'transparent';
const OPAQUE = 'opaque';
protected $value;
/**
* Creates the property
*
* @param string $value
*/
public function __construct($value) {
if ($value !== self::TRANSPARENT && $value !== self::OPAQUE) {
throw new \InvalidArgumentException('The value must either be specified as "transparent" or "opaque"');
}
$this->value = $value;
}
/**
* Returns the current value
*
* @return string
*/
public function getValue() {
return $this->value;
}
/**
* Serializes the property in a DOMDocument
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_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 Sabre_CalDAV_Property_ScheduleCalendarTransp
*/
static function unserialize(DOMElement $node) {
$value = null;
foreach($node->childNodes as $childNode) {
switch(Sabre_DAV_XMLUtil::toClarkNotation($childNode)) {
case '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}opaque' :
$value = self::OPAQUE;
break;
case '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}transparent' :
$value = self::TRANSPARENT;
break;
}
}
if (is_null($value))
return null;
return new self($value);
}
}

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* iMIP handler.
*
@ -42,12 +44,13 @@ class Sabre_CalDAV_Schedule_IMip {
/**
* Sends one or more iTip messages through email.
*
* @param string $originator
* @param array $recipients
* @param Sabre_VObject_Component $vObject
* @param string $originator Originator Email
* @param array $recipients Array of email addresses
* @param Sabre\VObject\Component $vObject
* @param string $principal Principal Url of the originator
* @return void
*/
public function sendMessage($originator, array $recipients, Sabre_VObject_Component $vObject) {
public function sendMessage($originator, array $recipients, VObject\Component $vObject, $principal) {
foreach($recipients as $recipient) {
@ -83,6 +86,9 @@ class Sabre_CalDAV_Schedule_IMip {
}
// @codeCoverageIgnoreStart
// This is deemed untestable in a reasonable manner
/**
* This function is reponsible for sending the actual email.
*
@ -94,11 +100,11 @@ class Sabre_CalDAV_Schedule_IMip {
*/
protected function mail($to, $subject, $body, array $headers) {
mail($to, $subject, $body, implode("\r\n", $headers));
}
// @codeCoverageIgnoreEnd
}
?>

@ -13,7 +13,7 @@
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Schedule_Outbox extends Sabre_DAV_Directory implements Sabre_CalDAV_Schedule_IOutbox {
class Sabre_CalDAV_Schedule_Outbox extends Sabre_DAV_Collection implements Sabre_CalDAV_Schedule_IOutbox {
/**
* The principal Uri
@ -103,6 +103,11 @@ class Sabre_CalDAV_Schedule_Outbox extends Sabre_DAV_Directory implements Sabre_
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-post-vevent',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
@ -144,6 +149,9 @@ class Sabre_CalDAV_Schedule_Outbox extends Sabre_DAV_Directory implements Sabre_
$default['aggregates'][] = array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy',
);
$default['aggregates'][] = array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-post-vevent',
);
return $default;

@ -1,68 +0,0 @@
<?php
/**
* CalDAV server
*
* Deprecated! Warning: This class is now officially deprecated
*
* This script is a convenience script. It quickly sets up a WebDAV server
* with caldav and ACL support, and it creates the root 'principals' and
* 'calendars' collections.
*
* Note that if you plan to do anything moderately complex, you are advised to
* not subclass this server, but use Sabre_DAV_Server directly instead. This
* class is nothing more than an 'easy setup'.
*
* @package Sabre
* @subpackage CalDAV
* @deprecated Don't use this class anymore, it will be removed in version 1.7.
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Server extends Sabre_DAV_Server {
/**
* The authentication realm
*
* Note that if this changes, the hashes in the auth backend must also
* be recalculated.
*
* @var string
*/
public $authRealm = 'SabreDAV';
/**
* Sets up the object. A PDO object must be passed to setup all the backends.
*
* @param PDO $pdo
*/
public function __construct(PDO $pdo) {
/* Backends */
$authBackend = new Sabre_DAV_Auth_Backend_PDO($pdo);
$calendarBackend = new Sabre_CalDAV_Backend_PDO($pdo);
$principalBackend = new Sabre_DAVACL_PrincipalBackend_PDO($pdo);
/* Directory structure */
$tree = array(
new Sabre_CalDAV_Principal_Collection($principalBackend),
new Sabre_CalDAV_CalendarRootNode($principalBackend, $calendarBackend),
);
/* Initializing server */
parent::__construct($tree);
/* Server Plugins */
$authPlugin = new Sabre_DAV_Auth_Plugin($authBackend,$this->authRealm);
$this->addPlugin($authPlugin);
$aclPlugin = new Sabre_DAVACL_Plugin();
$this->addPlugin($aclPlugin);
$caldavPlugin = new Sabre_CalDAV_Plugin();
$this->addPlugin($caldavPlugin);
}
}

@ -0,0 +1,72 @@
<?php
/**
* This object represents a CalDAV calendar that can be shared with other
* users.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_ShareableCalendar extends Sabre_CalDAV_Calendar implements Sabre_CalDAV_IShareableCalendar {
/**
* Updates the list of shares.
*
* The first array is a list of people that are to be added to the
* calendar.
*
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
* * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
* @param array $add
* @param array $remove
* @return void
*/
public function updateShares(array $add, array $remove) {
$this->caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove);
}
/**
* Returns the list of people whom this calendar is shared with.
*
* Every element in this array should have the following properties:
* * href - Often a mailto: address
* * commonName - Optional, for example a first + last name
* * status - See the Sabre_CalDAV_SharingPlugin::STATUS_ constants.
* * readOnly - boolean
* * summary - Optional, a description for the share
*
* @return array
*/
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);
}
}

@ -0,0 +1,98 @@
<?php
/**
* This object represents a CalDAV calendar that is shared by a different user.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_SharedCalendar extends Sabre_CalDAV_Calendar implements Sabre_CalDAV_ISharedCalendar {
/**
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $calendarInfo) {
$required = array(
'{http://calendarserver.org/ns/}shared-url',
'{http://sabredav.org/ns}owner-principal',
'{http://sabredav.org/ns}read-only',
);
foreach($required as $r) {
if (!isset($calendarInfo[$r])) {
throw new InvalidArgumentException('The ' . $r . ' property must be specified for SharedCalendar(s)');
}
}
parent::__construct($principalBackend, $caldavBackend, $calendarInfo);
}
/**
* This method should return the url of the owners' copy of the shared
* calendar.
*
* @return string
*/
public function getSharedUrl() {
return $this->calendarInfo['{http://calendarserver.org/ns/}shared-url'];
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
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;
}
}

@ -0,0 +1,475 @@
<?php
/**
* This plugin implements support for caldav sharing.
*
* This spec is defined at:
* http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
*
* See:
* Sabre_CalDAV_Backend_SharingSupport for all the documentation.
*
* Note: This feature is experimental, and may change in between different
* SabreDAV versions.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_SharingPlugin extends Sabre_DAV_ServerPlugin {
/**
* These are the various status constants used by sharing-messages.
*/
const STATUS_ACCEPTED = 1;
const STATUS_DECLINED = 2;
const STATUS_DELETED = 3;
const STATUS_NORESPONSE = 4;
const STATUS_INVALID = 5;
/**
* Reference to SabreDAV server object.
*
* @var Sabre_DAV_Server
*/
protected $server;
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
public function getFeatures() {
return array('calendarserver-sharing');
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using Sabre_DAV_Server::getPlugin
*
* @return string
*/
public function getPluginName() {
return 'caldav-sharing';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre_DAV_Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
//$server->resourceTypeMapping['Sabre_CalDAV_IShareableCalendar'] = '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}shared-owner';
$server->resourceTypeMapping['Sabre_CalDAV_ISharedCalendar'] = '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}shared';
array_push(
$this->server->protectedProperties,
'{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}invite',
'{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
'{' . Sabre_CalDAV_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 Sabre_DAV_INode $node
* @param array $requestedProperties
* @param array $returnedProperties
* @return void
*/
public function beforeGetProperties($path, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) {
if ($node instanceof Sabre_CalDAV_IShareableCalendar) {
if (($index = array_search('{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) {
unset($requestedProperties[$index]);
$returnedProperties[200]['{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}invite'] =
new Sabre_CalDAV_Property_Invite(
$node->getShares()
);
}
}
if ($node instanceof Sabre_CalDAV_ISharedCalendar) {
if (($index = array_search('{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}shared-url', $requestedProperties))!==false) {
unset($requestedProperties[$index]);
$returnedProperties[200]['{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}shared-url'] =
new Sabre_DAV_Property_Href(
$node->getSharedUrl()
);
}
}
}
/**
* 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 Sabre_DAV_INode $node
* @return void
*/
public function afterGetProperties($path, &$properties, Sabre_DAV_INode $node) {
if ($node instanceof Sabre_CalDAV_IShareableCalendar) {
if (isset($properties[200]['{DAV:}resourcetype'])) {
if (count($node->getShares())>0) {
$properties[200]['{DAV:}resourcetype']->add(
'{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}shared-owner'
);
}
}
$propName = '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes';
if (array_key_exists($propName, $properties[404])) {
unset($properties[404][$propName]);
$properties[200][$propName] = new Sabre_CalDAV_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 Sabre_DAV_INode $node
* @return void
*/
public function updateProperties(array &$mutations, array &$result, Sabre_DAV_INode $node) {
if (!$node instanceof Sabre_CalDAV_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('{' . Sabre_CalDAV_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 (Sabre_DAV_Exception_NotFound $e) {
return;
}
$dom = Sabre_DAV_XMLUtil::loadDOMDocument($this->server->httpRequest->getBody(true));
$documentType = Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild);
switch($documentType) {
// Dealing with the 'share' document, which modified invitees on a
// calendar.
case '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}share' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof Sabre_CalDAV_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 '{'. Sabre_CalDAV_Plugin::NS_CALENDARSERVER.'}invite-reply' :
// This only works on the calendar-home-root node.
if (!$node instanceof Sabre_CalDAV_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 Sabre_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 '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}publish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof Sabre_CalDAV_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 '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :
// We can only deal with IShareableCalendar objects
if (!$node instanceof Sabre_CalDAV_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', Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
$xpath->registerNamespace('d', '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', Sabre_CalDAV_Plugin::NS_CALENDARSERVER);
$xpath->registerNamespace('d', 'DAV:');
$hostHref = $xpath->evaluate('string(cs:hosturl/d:href)');
if (!$hostHref) {
throw new Sabre_DAV_Exception_BadRequest('The {' . Sabre_CalDAV_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
);
}
}

@ -6,7 +6,7 @@
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
@ -21,7 +21,7 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
* @var Sabre_CalDAV_Backend_BackendInterface
*/
protected $caldavBackend;
@ -36,10 +36,10 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param mixed $userUri
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $userUri) {
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $userUri) {
$this->principalBackend = $principalBackend;
$this->caldavBackend = $caldavBackend;
@ -168,9 +168,22 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
$objs = array();
foreach($calendars as $calendar) {
$objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar);
if ($this->caldavBackend instanceof Sabre_CalDAV_Backend_SharingSupport) {
if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) {
$objs[] = new Sabre_CalDAV_SharedCalendar($this->principalBackend, $this->caldavBackend, $calendar);
} else {
$objs[] = new Sabre_CalDAV_ShareableCalendar($this->principalBackend, $this->caldavBackend, $calendar);
}
} else {
$objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar);
}
}
$objs[] = new Sabre_CalDAV_Schedule_Outbox($this->principalInfo['uri']);
// We're adding a notifications node, if it's supported by the backend.
if ($this->caldavBackend instanceof Sabre_CalDAV_Backend_NotificationSupport) {
$objs[] = new Sabre_CalDAV_Notifications_Collection($this->caldavBackend, $this->principalInfo['uri']);
}
return $objs;
}
@ -185,8 +198,22 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
*/
public function createExtendedCollection($name, array $resourceType, array $properties) {
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar',$resourceType) || count($resourceType)!==2) {
throw new Sabre_DAV_Exception_InvalidResourceType('Unknown resourceType for this collection');
$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 Sabre_DAV_Exception_InvalidResourceType('Unknown resourceType: ' . $rt);
}
}
if (!$isCalendar) {
throw new Sabre_DAV_Exception_InvalidResourceType('You can only create calendars in this collection');
}
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
@ -295,4 +322,27 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
}
/**
* 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 Sabre_CalDAV_Backend_SharingSupport) {
throw new Sabre_DAV_Exception_NotImplemented('Sharing support is not implemented by this backend.');
}
return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);
}
}

@ -14,7 +14,7 @@ class Sabre_CalDAV_Version {
/**
* Full version number
*/
const VERSION = '1.6.4';
const VERSION = '1.7.0';
/**
* Stability : alpha, beta, stable

@ -16,28 +16,47 @@
*/
// Begin includes
include __DIR__ . '/Backend/Abstract.php';
include __DIR__ . '/Backend/PDO.php';
include __DIR__ . '/Backend/BackendInterface.php';
include __DIR__ . '/Backend/NotificationSupport.php';
include __DIR__ . '/Backend/SharingSupport.php';
include __DIR__ . '/CalendarQueryParser.php';
include __DIR__ . '/CalendarQueryValidator.php';
include __DIR__ . '/CalendarRootNode.php';
include __DIR__ . '/Exception/InvalidComponentType.php';
include __DIR__ . '/ICalendar.php';
include __DIR__ . '/ICalendarObject.php';
include __DIR__ . '/ICSExportPlugin.php';
include __DIR__ . '/IShareableCalendar.php';
include __DIR__ . '/ISharedCalendar.php';
include __DIR__ . '/Notifications/ICollection.php';
include __DIR__ . '/Notifications/INode.php';
include __DIR__ . '/Notifications/INotificationType.php';
include __DIR__ . '/Notifications/Node.php';
include __DIR__ . '/Notifications/Notification/Invite.php';
include __DIR__ . '/Notifications/Notification/InviteReply.php';
include __DIR__ . '/Notifications/Notification/SystemStatus.php';
include __DIR__ . '/Plugin.php';
include __DIR__ . '/Principal/Collection.php';
include __DIR__ . '/Principal/ProxyRead.php';
include __DIR__ . '/Principal/ProxyWrite.php';
include __DIR__ . '/Principal/User.php';
include __DIR__ . '/Property/AllowedSharingModes.php';
include __DIR__ . '/Property/Invite.php';
include __DIR__ . '/Property/ScheduleCalendarTransp.php';
include __DIR__ . '/Property/SupportedCalendarComponentSet.php';
include __DIR__ . '/Property/SupportedCalendarData.php';
include __DIR__ . '/Property/SupportedCollationSet.php';
include __DIR__ . '/Schedule/IMip.php';
include __DIR__ . '/Schedule/IOutbox.php';
include __DIR__ . '/Schedule/Outbox.php';
include __DIR__ . '/Server.php';
include __DIR__ . '/SharingPlugin.php';
include __DIR__ . '/UserCalendars.php';
include __DIR__ . '/Version.php';
include __DIR__ . '/Backend/Abstract.php';
include __DIR__ . '/Backend/PDO.php';
include __DIR__ . '/Calendar.php';
include __DIR__ . '/CalendarObject.php';
include __DIR__ . '/Notifications/Collection.php';
include __DIR__ . '/ShareableCalendar.php';
include __DIR__ . '/SharedCalendar.php';
// End includes

@ -55,7 +55,7 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca
* Returns a card
*
* @param string $name
* @return Sabre_DAV_Card
* @return Sabre_CardDAV_ICard
*/
public function getChild($name) {
@ -104,7 +104,7 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca
*
* @param string $name
* @param resource $vcardData
* @return void|null
* @return string|null
*/
public function createFile($name,$vcardData = null) {

@ -6,7 +6,7 @@
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, Sabre_DAVACL_IACL {
@ -78,7 +78,7 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard,
* Updates the VCard-formatted object
*
* @param string $cardData
* @return void
* @return string|null
*/
public function put($cardData) {
@ -114,7 +114,7 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard,
*/
public function getContentType() {
return 'text/x-vcard';
return 'text/x-vcard; charset=utf-8';
}
@ -128,7 +128,13 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard,
if (isset($this->cardData['etag'])) {
return $this->cardData['etag'];
} else {
return '"' . md5($this->get()) . '"';
$data = $this->get();
if (is_string($data)) {
return '"' . md5($data) . '"';
} else {
// We refuse to calculate the md5 if it's a stream.
return null;
}
}
}
@ -136,7 +142,7 @@ class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard,
/**
* Returns the last modification date as a unix timestamp
*
* @return time
* @return int
*/
public function getLastModified() {

@ -1,5 +1,7 @@
<?php
use Sabre\VObject;
/**
* CardDAV plugin
*
@ -48,6 +50,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
/* Events */
$server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
$server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties'));
$server->subscribeEvent('updateProperties', array($this, 'updateProperties'));
$server->subscribeEvent('report', array($this,'report'));
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
@ -153,10 +156,6 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
if (is_resource($val))
$val = stream_get_contents($val);
// Taking out \r to not screw up the xml output
//$returnedProperties[200][$addressDataProp] = str_replace("\r","", $val);
// The stripping of \r breaks the Mail App in OSX Mountain Lion
// this is fixed in master, but not backported. /Tanghus
$returnedProperties[200][$addressDataProp] = $val;
}
@ -190,7 +189,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
* @param array $mutations
* @param array $result
* @param Sabre_DAV_INode $node
* @return void
* @return bool
*/
public function updateProperties(&$mutations, &$result, $node) {
@ -272,7 +271,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
$hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
$hrefElems = $dom->getElementsByTagNameNS('DAV:','href');
$propertyList = array();
foreach($hrefElems as $elem) {
@ -282,9 +281,12 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
$prefer = $this->server->getHTTPPRefer();
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
$this->server->httpResponse->setHeader('Vary','Brief,Prefer');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList, $prefer['return-minimal']));
}
@ -348,9 +350,9 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
try {
$vobj = Sabre_VObject_Reader::read($data);
$vobj = VObject\Reader::read($data);
} catch (Sabre_VObject_ParseException $e) {
} catch (VObject\ParseException $e) {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
@ -360,6 +362,10 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.');
}
if (!isset($vobj->UID)) {
throw new Sabre_DAV_Exception_BadRequest('Every vcard must have an UID.');
}
}
@ -424,9 +430,12 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
$prefer = $this->server->getHTTPPRefer();
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($result));
$this->server->httpResponse->setHeader('Vary','Brief,Prefer');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal']));
}
@ -440,7 +449,7 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
*/
public function validateFilters($vcardData, array $filters, $test) {
$vcard = Sabre_VObject_Reader::read($vcardData);
$vcard = VObject\Reader::read($vcardData);
if (!$filters) return true;
@ -615,6 +624,30 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
/**
* This event is triggered after webdav-properties have been retrieved.
*
* @return bool
*/
public function afterGetProperties($uri, &$properties) {
// If the request was made using the SOGO connector, we must rewrite
// the content-type property. By default SabreDAV will send back
// text/x-vcard; charset=utf-8, but for SOGO we must strip that last
// part.
if (!isset($properties[200]['{DAV:}getcontenttype']))
return;
if (strpos($this->server->httpRequest->getHeader('User-Agent'),'Thunderbird')===false) {
return;
}
if (strpos($properties[200]['{DAV:}getcontenttype'],'text/x-vcard')===0) {
$properties[200]['{DAV:}getcontenttype'] = 'text/x-vcard';
}
}
/**
* This method is used to generate HTML output for the
* Sabre_DAV_Browser_Plugin. This allows us to generate an interface users

@ -0,0 +1,107 @@
<?php
use Sabre\VObject;
/**
* VCF Exporter
*
* This plugin adds the ability to export entire address books as .vcf files.
* This is useful for clients that don't support CardDAV yet. They often do
* support vcf files.
*
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @author Thomas Tanghus (http://tanghus.net/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CardDAV_VCFExportPlugin extends Sabre_DAV_ServerPlugin {
/**
* Reference to Server class
*
* @var Sabre_DAV_Server
*/
private $server;
/**
* Initializes the plugin and registers event handlers
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->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 Sabre_CardDAV_IAddressBook)) return;
// Checking ACL, if available.
if ($aclPlugin = $this->server->getPlugin('acl')) {
$aclPlugin->checkPrivileges($uri, '{DAV:}read');
}
$this->server->httpResponse->setHeader('Content-Type','text/directory');
$this->server->httpResponse->sendStatus(200);
$nodes = $this->server->getPropertiesForPath($uri, array(
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}address-data',
),1);
$this->server->httpResponse->sendBody($this->generateVCF($nodes));
// Returning false to break the event chain
return false;
}
/**
* Merges all vcard objects, and builds one big vcf export
*
* @param array $nodes
* @return string
*/
public function generateVCF(array $nodes) {
$output = "";
foreach($nodes as $node) {
if (!isset($node[200]['{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}address-data'])) {
continue;
}
$nodeData = $node[200]['{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}address-data'];
// Parsing this node so VObject can clean up the output.
$output .=
VObject\Reader::read($nodeData)->serialize();
}
return $output;
}
}

@ -16,7 +16,7 @@ class Sabre_CardDAV_Version {
/**
* Full version number
*/
const VERSION = '1.6.3';
const VERSION = '1.7.0';
/**
* Stability : alpha, beta, stable

@ -26,6 +26,7 @@ include __DIR__ . '/IDirectory.php';
include __DIR__ . '/Plugin.php';
include __DIR__ . '/Property/SupportedAddressData.php';
include __DIR__ . '/UserAddressBooks.php';
include __DIR__ . '/VCFExportPlugin.php';
include __DIR__ . '/Version.php';
include __DIR__ . '/AddressBook.php';
include __DIR__ . '/Card.php';

@ -338,7 +338,7 @@ class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin {
$icon = '';
if ($this->enableAssets) {
$node = $parent->getChild($name);
$node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name);
foreach(array_reverse($this->iconMap) as $class=>$iconName) {
if ($node instanceof $class) {

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

@ -16,12 +16,25 @@
*/
class Sabre_DAV_Client {
/**
* The propertyMap is a key-value array.
*
* If you use the propertyMap, any {DAV:}multistatus responses with the
* proeprties listed in this array, will automatically be mapped to a
* respective class.
*
* The {DAV:}resourcetype property is automatically added. This maps to
* Sabre_DAV_Property_ResourceType
*
* @var array
*/
public $propertyMap = array();
protected $baseUri;
protected $userName;
protected $password;
protected $proxy;
protected $trustedCertificates;
/**
* Basic authentication
@ -87,6 +100,18 @@ class Sabre_DAV_Client {
}
/**
* Add trusted root certificates to the webdav client.
*
* The parameter certificates should be a absulute path to a file
* which contains all trusted certificates
*
* @param string $certificates
*/
public function addTrustedCertificates($certificates) {
$this->trustedCertificates = $certificates;
}
/**
* Does a PROPFIND request
*
@ -143,13 +168,13 @@ class Sabre_DAV_Client {
if ($depth===0) {
reset($result);
$result = current($result);
return $result[200];
return isset($result[200])?$result[200]:array();
}
$newResult = array();
foreach($result as $href => $statusList) {
$newResult[$href] = $statusList[200];
$newResult[$href] = isset($statusList[200])?$statusList[200]:array();
}
@ -279,6 +304,10 @@ class Sabre_DAV_Client {
CURLOPT_MAXREDIRS => 5,
);
if($this->trustedCertificates) {
$curlSettings[CURLOPT_CAINFO] = $this->trustedCertificates;
}
switch ($method) {
case 'HEAD' :
@ -363,10 +392,30 @@ class Sabre_DAV_Client {
if ($response['statusCode']>=400) {
switch ($response['statusCode']) {
case 400 :
throw new Sabre_DAV_Exception_BadRequest('Bad request');
case 401 :
throw new Sabre_DAV_Exception_NotAuthenticated('Not authenticated');
case 402 :
throw new Sabre_DAV_Exception_PaymentRequired('Payment required');
case 403 :
throw new Sabre_DAV_Exception_Forbidden('Forbidden');
case 404:
throw new Sabre_DAV_Exception_NotFound('Resource ' . $url . ' not found.');
break;
throw new Sabre_DAV_Exception_NotFound('Resource not found.');
case 405 :
throw new Sabre_DAV_Exception_MethodNotAllowed('Method not allowed');
case 409 :
throw new Sabre_DAV_Exception_Conflict('Conflict');
case 412 :
throw new Sabre_DAV_Exception_PreconditionFailed('Precondition failed');
case 416 :
throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('Requested Range Not Satisfiable');
case 500 :
throw new Sabre_DAV_Exception('Internal server error');
case 501 :
throw new Sabre_DAV_Exception_NotImplemented('Not Implemented');
case 507 :
throw new Sabre_DAV_Exception_InsufficientStorage('Insufficient storage');
default:
throw new Sabre_DAV_Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')');
}
@ -386,6 +435,7 @@ class Sabre_DAV_Client {
* @param array $settings
* @return array
*/
// @codeCoverageIgnoreStart
protected function curlRequest($url, $settings) {
$curl = curl_init($url);
@ -399,6 +449,7 @@ class Sabre_DAV_Client {
);
}
// @codeCoverageIgnoreEnd
/**
* Returns the full url based on the given url (which may be relative). All
@ -453,19 +504,17 @@ class Sabre_DAV_Client {
*/
public function parseMultiStatus($body) {
$body = Sabre_DAV_XMLUtil::convertDAVNamespace($body);
$responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA);
if ($responseXML===false) {
throw new InvalidArgumentException('The passed data is not valid XML');
}
$responseXML->registerXPathNamespace('d', 'urn:DAV');
$responseXML->registerXPathNamespace('d', 'DAV:');
$propResult = array();
foreach($responseXML->xpath('d:response') as $response) {
$response->registerXPathNamespace('d', 'urn:DAV');
$response->registerXPathNamespace('d', 'DAV:');
$href = $response->xpath('d:href');
$href = (string)$href[0];
@ -473,7 +522,7 @@ class Sabre_DAV_Client {
foreach($response->xpath('d:propstat') as $propStat) {
$propStat->registerXPathNamespace('d', 'urn:DAV');
$propStat->registerXPathNamespace('d', 'DAV:');
$status = $propStat->xpath('d:status');
list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3);

@ -17,9 +17,13 @@ abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_
/**
* Returns a child object, by its name.
*
* This method makes use of the getChildren method to grab all the child nodes, and compares the name.
* This method makes use of the getChildren method to grab all the child
* nodes, and compares the name.
* Generally its wise to override this, as this can usually be optimized
*
* This method must throw Sabre_DAV_Exception_NotFound if the node does not
* exist.
*
* @param string $name
* @throws Sabre_DAV_Exception_NotFound
* @return Sabre_DAV_INode

@ -1,17 +0,0 @@
<?php
/**
* Directory class
*
* This class is now deprecated in favor of the Sabre_DAV_Collection class.
*
* @package Sabre
* @subpackage DAV
* @deprecated Use Sabre_DAV_Collection instead
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_Directory extends Sabre_DAV_Collection {
}

@ -1,7 +1,7 @@
<?php
/**
* ReportNotImplemented
* ReportNotSupported
*
* This exception is thrown when the client requested an unknown report through the REPORT method
*
@ -11,7 +11,7 @@
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_DAV_Exception_ReportNotImplemented extends Sabre_DAV_Exception_NotImplemented {
class Sabre_DAV_Exception_ReportNotSupported extends Sabre_DAV_Exception_Forbidden {
/**
* This method allows the exception to include additional information into the WebDAV error response

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save