Heavily refactored, including multiple calendars per user and recurring events. Not in an installable state yet, though

This commit is contained in:
Tobias Hößl 2012-07-08 17:12:58 +00:00
parent 4a5e30ec84
commit fefee23e90
78 changed files with 8026 additions and 1205 deletions

View file

@ -3,45 +3,15 @@
/**
* 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/)
* @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.
@ -85,102 +55,6 @@ abstract class Sabre_CalDAV_Backend_Abstract {
}
/**
* Delete a calendar and all it's objects
*
* @param mixed $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 mixed $calendarId
* @return array
*/
abstract 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
*/
abstract 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
*/
abstract 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
*/
abstract function updateCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Deletes an existing calendar object.
*
* @param mixed $calendarId
* @param string $objectUri
* @return void
*/
abstract function deleteCalendarObject($calendarId,$objectUri);
/**
* Performs a calendar-query on the contents of this calendar.
*

View file

@ -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);
}

View file

@ -0,0 +1,44 @@
<?php
/**
* Adds caldav notification support to a backend.
*
* 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);
}

View file

@ -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,10 +39,10 @@ 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;

View file

@ -12,7 +12,7 @@
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;

View file

@ -304,28 +304,29 @@ class Sabre_CalDAV_CalendarQueryValidator {
// one is the first to trigger. Based on this, we can
// determine if we can 'give up' expanding events.
$firstAlarm = null;
foreach($expandedEvent->VALARM as $expandedAlarm) {
if ($expandedEvent->VALARM !== null) {
foreach($expandedEvent->VALARM as $expandedAlarm) {
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
if ($expandedAlarm->isInTimeRange($start, $end)) {
return true;
}
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
if ($expandedAlarm->isInTimeRange($start, $end)) {
return true;
}
if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
// This is an alarm with a non-relative trigger
// time, likely created by a buggy client. The
// implication is that every alarm in this
// recurring event trigger at the exact same
// time. It doesn't make sense to traverse
// further.
} else {
// We store the first alarm as a means to
// figure out when we can stop traversing.
if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
$firstAlarm = $effectiveTrigger;
if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
// This is an alarm with a non-relative trigger
// time, likely created by a buggy client. The
// implication is that every alarm in this
// recurring event trigger at the exact same
// time. It doesn't make sense to traverse
// further.
} else {
// We store the first alarm as a means to
// figure out when we can stop traversing.
if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
$firstAlarm = $effectiveTrigger;
}
}
}
}
if (is_null($firstAlarm)) {
// No alarm was found.

View file

@ -17,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;
@ -33,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;

View file

@ -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);
}
}

View file

@ -0,0 +1,80 @@
<?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
* @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 {
/**
* 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,
$notification
);
}
return $children;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
return 'notifications';
}
}

View file

@ -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 {
}

View file

@ -0,0 +1,29 @@
<?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();
}

View file

@ -0,0 +1,46 @@
<?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 {
/**
* 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
*/
function serialize(Sabre_DAV_Server $server, \DOMElement $node);
/**
* 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();
}

View file

@ -0,0 +1,68 @@
<?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_Node implements Sabre_CalDAV_Notifications_INode {
/**
* The notification backend
*
* @var Sabre_CalDAV_Backend_NotificationSupport
*/
protected $caldavBackend;
/**
* The actual notification
*
* @var Sabre_CalDAV_Notifications_INotificationType
*/
protected $notification;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
* @param Sabre_CalDAV_Notifications_INotificationType $notification
*/
public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, Sabre_CalDAV_Notifications_INotificationType $notification) {
$this->caldavBackend = $caldavBackend;
$this->notification = $notification;
}
/**
* Returns the path name for this notification
*
* @return id
*/
public function getName() {
return $this->notification->getId();
}
/**
* This method must return an xml element, using the
* Sabre_CalDAV_Notifications_INotificationType classes.
*
* @return Sabre_DAVNotification_INotificationType
*/
public function getNotificationType() {
return $this->notification;
}
}

View file

@ -0,0 +1,158 @@
<?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;
/**
* Creates the notification.
*
* Some kind of unique id should be provided. This is used to generate a
* url.
*
* @param string $id
* @param int $type
* @param string $description
* @param string $href
*/
public function __construct($id, $type = self::TYPE_HIGH, $description = null, $href = null) {
$this->id = $id;
$this->type = $type;
$this->description = $description;
$this->href = $href;
}
/**
* 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;
}
}

View file

@ -162,6 +162,7 @@ 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';
@ -172,6 +173,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
$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 . '}notifications';
$server->resourceTypeMapping['Sabre_CalDAV_Notifications_INode'] = '{' . self::NS_CALENDARSERVER . '}notification';
array_push($server->protectedProperties,
@ -195,7 +198,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'
);
}
@ -380,8 +385,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'
@ -648,7 +676,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if (!$node instanceof Sabre_CalDAV_ICalendarObject)
return;
$this->validateICalendar($data);
$this->validateICalendar($data, $path);
}
@ -668,7 +696,49 @@ 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;
$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->sendStatus(200);
$this->server->httpResponse->sendBody($dom->saveXML());
return false;
}
@ -678,9 +748,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)) {
@ -704,6 +775,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) {
@ -715,6 +791,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');
}
@ -756,7 +835,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests');
}
if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) {
if (!preg_match('/^mailto:(.*)@(.*)$/i', $originator)) {
throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address');
}
$originator = substr($originator,7);
@ -765,7 +844,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
foreach($recipients as $k=>$recipient) {
$recipient = trim($recipient);
if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) {
if (!preg_match('/^mailto:(.*)@(.*)$/i', $recipient)) {
throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address');
}
$recipient = substr($recipient, 7);
@ -813,9 +892,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') {
$this->iMIPMessage($originator, $recipients, $vObject, $principal);
$result = $this->iMIPMessage($originator, $recipients, $vObject, $principal);
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->sendBody('Messages sent');
$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');
}
@ -825,18 +905,81 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* Sends an iMIP message by email.
*
* This method must return an array with status codes per recipient.
* This should look something like:
*
* array(
* 'user1@example.org' => '2.0;Success'
* )
*
* Formatting for this status code can be found at:
* https://tools.ietf.org/html/rfc5545#section-3.8.8.3
*
* A list of valid status codes can be found at:
* https://tools.ietf.org/html/rfc5546#section-3.6
*
* @param string $originator
* @param array $recipients
* @param Sabre_VObject_Component $vObject
* @param string $principal Principal url
* @return void
* @return array
*/
protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject, $principal) {
if (!$this->imipHandler) {
throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.');
$resultStatus = '5.2;This server does not support this operation';
} else {
$this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
$resultStatus = '2.0;Success';
}
$this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
$result = array();
foreach($recipients as $recipient) {
$result[$recipient] = $resultStatus;
}
return $result;
}
/**
* Generates a schedule-response XML body
*
* The recipients array is a key->value list, containing email addresses
* and iTip status codes. See the iMIPMessage method for a description of
* the value.
*
* @param array $recipients
* @return string
*/
public function generateScheduleResponse(array $recipients) {
$dom = new DOMDocument('1.0','utf-8');
$dom->formatOutput = true;
$xscheduleResponse = $dom->createElement('cal:schedule-response');
$dom->appendChild($xscheduleResponse);
foreach($this->server->xmlNamespaces as $namespace=>$prefix) {
$xscheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
}
foreach($recipients as $recipient=>$status) {
$xresponse = $dom->createElement('cal:response');
$xrecipient = $dom->createElement('cal:recipient');
$xrecipient->appendChild($dom->createTextNode($recipient));
$xresponse->appendChild($xrecipient);
$xrequestStatus = $dom->createElement('cal:request-status');
$xrequestStatus->appendChild($dom->createTextNode($status));
$xresponse->appendChild($xrequestStatus);
$xscheduleResponse->appendChild($xresponse);
}
return $dom->saveXML();
}

View file

@ -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;
@ -171,6 +171,11 @@ class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre
$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;
}