Initial Release of the calendar plugin

This commit is contained in:
Tobias Hößl 2012-06-03 18:19:28 +00:00
parent 45cc9885fc
commit 7115197a33
561 changed files with 189494 additions and 0 deletions

View file

@ -0,0 +1,277 @@
<?php
/**
* Abstract Calendaring backend. Extend this class to create your own backends.
*
* @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);
/**
* 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) {
return false;
}
/**
* 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.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre_CalDAV_CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly adviced to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on either VEVENT or VTODO.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* @param mixed $calendarId
* @param array $filters
* @return array
*/
public function calendarQuery($calendarId, array $filters) {
$result = array();
$objects = $this->getCalendarObjects($calendarId);
$validator = new Sabre_CalDAV_CalendarQueryValidator();
foreach($objects as $object) {
if ($this->validateFilterForObject($object, $filters)) {
$result[] = $object['uri'];
}
}
return $result;
}
/**
* This method validates if a filters (as passed to calendarQuery) matches
* the given object.
*
* @param array $object
* @param array $filter
* @return bool
*/
protected function validateFilterForObject(array $object, array $filters) {
// Unfortunately, setting the 'calendardata' here is optional. If
// it was excluded, we actually need another call to get this as
// well.
if (!isset($object['calendardata'])) {
$object = $this->getCalendarObject($object['calendarid'], $object['uri']);
}
$vObject = Sabre_VObject_Reader::read($object['calendardata']);
$validator = new Sabre_CalDAV_CalendarQueryValidator();
return $validator->validate($vObject, $filters);
}
}

View file

@ -0,0 +1,657 @@
<?php
/**
* PDO CalDAV backend
*
* This backend is used to store calendar-data in a PDO database, such as
* sqlite or MySQL
*
* @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_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
/**
* We need to specify a max date, because we need to stop *somewhere*
*/
const MAX_DATE = '2040-01-01';
/**
* pdo
*
* @var PDO
*/
protected $pdo;
/**
* The table name that will be used for calendars
*
* @var string
*/
protected $calendarTableName;
/**
* The table name that will be used for calendar objects
*
* @var string
*/
protected $calendarObjectTableName;
/**
* List of CalDAV properties, and how they map to database fieldnames
*
* Add your own properties by simply adding on to this array
*
* @var array
*/
public $propertyMap = array(
'{DAV:}displayname' => 'displayname',
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
);
/**
* Creates the backend
*
* @param PDO $pdo
* @param string $calendarTableName
* @param string $calendarObjectTableName
*/
public function __construct(PDO $pdo, $calendarTableName = 'calendars', $calendarObjectTableName = 'calendarobjects') {
$this->pdo = $pdo;
$this->calendarTableName = $calendarTableName;
$this->calendarObjectTableName = $calendarObjectTableName;
}
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* @param string $principalUri
* @return array
*/
public function getCalendarsForUser($principalUri) {
$fields = array_values($this->propertyMap);
$fields[] = 'id';
$fields[] = 'uri';
$fields[] = 'ctag';
$fields[] = 'components';
$fields[] = 'principaluri';
// Making fields a comma-delimited list
$fields = implode(', ', $fields);
$stmt = $this->pdo->prepare("SELECT " . $fields . " FROM ".$this->calendarTableName." WHERE principaluri = ? ORDER BY calendarorder ASC");
$stmt->execute(array($principalUri));
$calendars = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$components = array();
if ($row['components']) {
$components = explode(',',$row['components']);
}
$calendar = array(
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{' . 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),
);
foreach($this->propertyMap as $xmlName=>$dbName) {
$calendar[$xmlName] = $row[$dbName];
}
$calendars[] = $calendar;
}
return $calendars;
}
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return string
*/
public function createCalendar($principalUri, $calendarUri, array $properties) {
$fieldNames = array(
'principaluri',
'uri',
'ctag',
);
$values = array(
':principaluri' => $principalUri,
':uri' => $calendarUri,
':ctag' => 1,
);
// Default value
$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
$fieldNames[] = 'components';
if (!isset($properties[$sccs])) {
$values[':components'] = 'VEVENT,VTODO';
} else {
if (!($properties[$sccs] instanceof Sabre_CalDAV_Property_SupportedCalendarComponentSet)) {
throw new Sabre_DAV_Exception('The ' . $sccs . ' property must be of type: Sabre_CalDAV_Property_SupportedCalendarComponentSet');
}
$values[':components'] = implode(',',$properties[$sccs]->getValue());
}
foreach($this->propertyMap as $xmlName=>$dbName) {
if (isset($properties[$xmlName])) {
$values[':' . $dbName] = $properties[$xmlName];
$fieldNames[] = $dbName;
}
}
$stmt = $this->pdo->prepare("INSERT INTO ".$this->calendarTableName." (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")");
$stmt->execute($values);
return $this->pdo->lastInsertId();
}
/**
* Updates properties for a calendar.
*
* The mutations array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param string $calendarId
* @param array $mutations
* @return bool|array
*/
public function updateCalendar($calendarId, array $mutations) {
$newValues = array();
$result = array(
200 => array(), // Ok
403 => array(), // Forbidden
424 => array(), // Failed Dependency
);
$hasError = false;
foreach($mutations as $propertyName=>$propertyValue) {
// We don't know about this property.
if (!isset($this->propertyMap[$propertyName])) {
$hasError = true;
$result[403][$propertyName] = null;
unset($mutations[$propertyName]);
continue;
}
$fieldName = $this->propertyMap[$propertyName];
$newValues[$fieldName] = $propertyValue;
}
// If there were any errors we need to fail the request
if ($hasError) {
// Properties has the remaining properties
foreach($mutations as $propertyName=>$propertyValue) {
$result[424][$propertyName] = null;
}
// Removing unused statuscodes for cleanliness
foreach($result as $status=>$properties) {
if (is_array($properties) && count($properties)===0) unset($result[$status]);
}
return $result;
}
// Success
// Now we're generating the sql query.
$valuesSql = array();
foreach($newValues as $fieldName=>$value) {
$valuesSql[] = $fieldName . ' = ?';
}
$valuesSql[] = 'ctag = ctag + 1';
$stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ',$valuesSql) . " WHERE id = ?");
$newValues['id'] = $calendarId;
$stmt->execute(array_values($newValues));
return true;
}
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @return void
*/
public function deleteCalendar($calendarId) {
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
$stmt->execute(array($calendarId));
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?');
$stmt->execute(array($calendarId));
}
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * id - unique identifier which will be used for subsequent updates
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* ' "abcdef"')
* * calendarid - The calendarid as it was passed to this function.
* * size - The size of the calendar objects, in bytes.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param string $calendarId
* @return array
*/
public function getCalendarObjects($calendarId) {
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
$stmt->execute(array($calendarId));
$result = array();
foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$result[] = array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
);
}
return $result;
}
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* @param string $calendarId
* @param string $objectUri
* @return array
*/
public function getCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId, $objectUri));
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
if(!$row) return null;
return array(
'id' => $row['id'],
'uri' => $row['uri'],
'lastmodified' => $row['lastmodified'],
'etag' => '"' . $row['etag'] . '"',
'calendarid' => $row['calendarid'],
'size' => (int)$row['size'],
'calendardata' => $row['calendardata'],
);
}
/**
* Creates a new calendar object.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function createCalendarObject($calendarId,$objectUri,$calendarData) {
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence) VALUES (?,?,?,?,?,?,?,?,?)');
$stmt->execute(array(
$calendarId,
$objectUri,
$calendarData,
time(),
$extraData['etag'],
$extraData['size'],
$extraData['componentType'],
$extraData['firstOccurence'],
$extraData['lastOccurence'],
));
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
return '"' . $extraData['etag'] . '"';
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData) {
$extraData = $this->getDenormalizedData($calendarData);
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarData,time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'] ,$calendarId,$objectUri));
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
return '"' . $extraData['etag'] . '"';
}
/**
* Parses some information from calendar objects, used for optimized
* calendar-queries.
*
* Returns an array with the following keys:
* * etag
* * size
* * componentType
* * firstOccurence
* * lastOccurence
*
* @param string $calendarData
* @return array
*/
protected function getDenormalizedData($calendarData) {
$vObject = Sabre_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(Sabre_VObject_DateTimeParser::parse($component->DURATION->value));
$lastOccurence = $endDate->getTimeStamp();
} elseif ($component->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->modify('+1 day');
$lastOccurence = $endDate->getTimeStamp();
} else {
$lastOccurence = $firstOccurence;
}
} else {
$it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->UID);
$maxDate = new DateTime(self::MAX_DATE);
if ($it->isInfinite()) {
$lastOccurence = $maxDate->getTimeStamp();
} else {
$end = $it->getDtEnd();
while($it->valid() && $end < $maxDate) {
$end = $it->getDtEnd();
$it->next();
}
$lastOccurence = $end->getTimeStamp();
}
}
}
return array(
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => $firstOccurence,
'lastOccurence' => $lastOccurence,
);
}
/**
* Deletes an existing calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId,$objectUri));
$stmt = $this->pdo->prepare('UPDATE '. $this->calendarTableName .' SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
}
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre_CalDAV_CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly adviced to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on a VEVENT.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* This specific implementation (for the PDO) backend optimizes filters on
* specific components, and VEVENT time-ranges.
*
* @param string $calendarId
* @param array $filters
* @return array
*/
public function calendarQuery($calendarId, array $filters) {
$result = array();
$validator = new Sabre_CalDAV_CalendarQueryValidator();
$componentType = null;
$requirePostFilter = true;
$timeRange = null;
// if no filters were specified, we don't need to filter after a query
if (!$filters['prop-filters'] && !$filters['comp-filters']) {
$requirePostFilter = false;
}
// Figuring out if there's a component filter
if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
$componentType = $filters['comp-filters'][0]['name'];
// Checking if we need post-filters
if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
$requirePostFilter = false;
}
// There was a time-range filter
if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
$timeRange = $filters['comp-filters'][0]['time-range'];
}
}
if ($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;
}
}

View file

@ -0,0 +1,366 @@
<?php
/**
* This object represents a CalDAV calendar.
*
* A calendar can contain multiple TODO and or Events. These are represented
* as Sabre_CalDAV_CalendarObject objects.
*
* @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_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProperties, Sabre_DAVACL_IACL {
/**
* This is an array with calendar information
*
* @var array
*/
protected $calendarInfo;
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
*/
protected $caldavBackend;
/**
* Principal backend
*
* @var Sabre_DAVACL_IPrincipalBackend
*/
protected $principalBackend;
/**
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $calendarInfo) {
$this->caldavBackend = $caldavBackend;
$this->principalBackend = $principalBackend;
$this->calendarInfo = $calendarInfo;
}
/**
* Returns the name of the calendar
*
* @return string
*/
public function getName() {
return $this->calendarInfo['uri'];
}
/**
* Updates properties such as the display name and description
*
* @param array $mutations
* @return array
*/
public function updateProperties($mutations) {
return $this->caldavBackend->updateCalendar($this->calendarInfo['id'],$mutations);
}
/**
* Returns the list of properties
*
* @param array $requestedProperties
* @return array
*/
public function getProperties($requestedProperties) {
$response = array();
foreach($requestedProperties as $prop) switch($prop) {
case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' :
$response[$prop] = new Sabre_CalDAV_Property_SupportedCalendarData();
break;
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;
}
return $response;
}
/**
* Returns a calendar object
*
* The contained calendar objects are for example Events or Todo's.
*
* @param string $name
* @return Sabre_CalDAV_ICalendarObject
*/
public function getChild($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
if (!$obj) throw new Sabre_DAV_Exception_NotFound('Calendar object not found');
return new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
}
/**
* Returns the full list of calendar objects
*
* @return array
*/
public function getChildren() {
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
$children = array();
foreach($objs as $obj) {
$children[] = new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
}
return $children;
}
/**
* Checks if a child-node exists.
*
* @param string $name
* @return bool
*/
public function childExists($name) {
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
if (!$obj)
return false;
else
return true;
}
/**
* Creates a new directory
*
* We actually block this, as subdirectories are not allowed in calendars.
*
* @param string $name
* @return void
*/
public function createDirectory($name) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating collections in calendar objects is not allowed');
}
/**
* Creates a new file
*
* The contents of the new file must be a valid ICalendar string.
*
* @param string $name
* @param resource $calendarData
* @return string|null
*/
public function createFile($name,$calendarData = null) {
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
}
return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData);
}
/**
* Deletes the calendar.
*
* @return void
*/
public function delete() {
$this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
}
/**
* Renames the calendar. Note that most calendars use the
* {DAV:}displayname to display a name to display a name.
*
* @param string $newName
* @return void
*/
public function setName($newName) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Renaming calendars is not yet supported');
}
/**
* Returns the last modification date as a unix timestamp.
*
* @return void
*/
public function getLastModified() {
return null;
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->calendarInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'protected' => true,
),
array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
'principal' => '{DAV:}authenticated',
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
$default = Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet();
// We need to inject 'read-free-busy' in the tree, aggregated under
// {DAV:}read.
foreach($default['aggregates'] as &$agg) {
if ($agg['privilege'] !== '{DAV:}read') continue;
$agg['aggregates'][] = array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
);
}
return $default;
}
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre_CalDAV_CalendarQueryParser.
*
* @param array $filters
* @return array
*/
public function calendarQuery(array $filters) {
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
}
}

View file

@ -0,0 +1,273 @@
<?php
/**
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
*
* @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_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV_ICalendarObject, Sabre_DAVACL_IACL {
/**
* Sabre_CalDAV_Backend_Abstract
*
* @var array
*/
protected $caldavBackend;
/**
* Array with information about this CalendarObject
*
* @var array
*/
protected $objectData;
/**
* Array with information about the containing calendar
*
* @var array
*/
protected $calendarInfo;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param array $calendarInfo
* @param array $objectData
*/
public function __construct(Sabre_CalDAV_Backend_Abstract $caldavBackend,array $calendarInfo,array $objectData) {
$this->caldavBackend = $caldavBackend;
if (!isset($objectData['calendarid'])) {
throw new InvalidArgumentException('The objectData argument must contain a \'calendarid\' property');
}
if (!isset($objectData['uri'])) {
throw new InvalidArgumentException('The objectData argument must contain an \'uri\' property');
}
$this->calendarInfo = $calendarInfo;
$this->objectData = $objectData;
}
/**
* Returns the uri for this object
*
* @return string
*/
public function getName() {
return $this->objectData['uri'];
}
/**
* Returns the ICalendar-formatted object
*
* @return string
*/
public function get() {
// Pre-populating the 'calendardata' is optional, if we don't have it
// already we fetch it from the backend.
if (!isset($this->objectData['calendardata'])) {
$this->objectData = $this->caldavBackend->getCalendarObject($this->objectData['calendarid'], $this->objectData['uri']);
}
return $this->objectData['calendardata'];
}
/**
* Updates the ICalendar-formatted object
*
* @param string|resource $calendarData
* @return string
*/
public function put($calendarData) {
if (is_resource($calendarData)) {
$calendarData = stream_get_contents($calendarData);
}
$etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData);
$this->objectData['calendardata'] = $calendarData;
$this->objectData['etag'] = $etag;
return $etag;
}
/**
* Deletes the calendar object
*
* @return void
*/
public function delete() {
$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'],$this->objectData['uri']);
}
/**
* Returns the mime content-type
*
* @return string
*/
public function getContentType() {
return 'text/calendar; charset=utf-8';
}
/**
* Returns an ETag for this object.
*
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* @return string
*/
public function getETag() {
if (isset($this->objectData['etag'])) {
return $this->objectData['etag'];
} else {
return '"' . md5($this->get()). '"';
}
}
/**
* Returns the last modification date as a unix timestamp
*
* @return int
*/
public function getLastModified() {
return $this->objectData['lastmodified'];
}
/**
* Returns the size of this object in bytes
*
* @return int
*/
public function getSize() {
if (array_key_exists('size',$this->objectData)) {
return $this->objectData['size'];
} else {
return strlen($this->get());
}
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->calendarInfo['principaluri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -0,0 +1,296 @@
<?php
/**
* Parses the calendar-query report request body.
*
* Whoever designed this format, and the CalDAV equivalent even more so,
* has no feel for design.
*
* @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_CalendarQueryParser {
/**
* List of requested properties the client wanted
*
* @var array
*/
public $requestedProperties;
/**
* List of property/component filters.
*
* @var array
*/
public $filters;
/**
* This property will contain null if CALDAV:expand was not specified,
* otherwise it will contain an array with 2 elements (start, end). Each
* contain a DateTime object.
*
* If expand is specified, recurring calendar objects are to be expanded
* into their individual components, and only the components that fall
* within the specified time-range are to be returned.
*
* For more details, see rfc4791, section 9.6.5.
*
* @var null|array
*/
public $expand;
/**
* DOM Document
*
* @var DOMDocument
*/
protected $dom;
/**
* DOM XPath object
*
* @var DOMXPath
*/
protected $xpath;
/**
* Creates the parser
*
* @param DOMDocument $dom
*/
public function __construct(DOMDocument $dom) {
$this->dom = $dom;
$this->xpath = new DOMXPath($dom);
$this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
$this->xpath->registerNameSpace('dav','urn:DAV');
}
/**
* Parses the request.
*
* @return void
*/
public function parse() {
$filterNode = null;
$filter = $this->xpath->query('/cal:calendar-query/cal:filter');
if ($filter->length !== 1) {
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
}
$compFilters = $this->parseCompFilters($filter->item(0));
if (count($compFilters)!==1) {
throw new Sabre_DAV_Exception_BadRequest('There must be exactly 1 top-level comp-filter.');
}
$this->filters = $compFilters[0];
$this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
$expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
if ($expand->length>0) {
$this->expand = $this->parseExpand($expand->item(0));
}
}
/**
* Parses all the 'comp-filter' elements from a node
*
* @param DOMElement $parentNode
* @return array
*/
protected function parseCompFilters(DOMElement $parentNode) {
$compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
$result = array();
for($ii=0; $ii < $compFilterNodes->length; $ii++) {
$compFilterNode = $compFilterNodes->item($ii);
$compFilter = array();
$compFilter['name'] = $compFilterNode->getAttribute('name');
$compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
$compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
$compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
$compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
'VEVENT',
'VTODO',
'VJOURNAL',
'VFREEBUSY',
'VALARM',
))) {
throw new Sabre_DAV_Exception_BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
};
$result[] = $compFilter;
}
return $result;
}
/**
* Parses all the prop-filter elements from a node
*
* @param DOMElement $parentNode
* @return array
*/
protected function parsePropFilters(DOMElement $parentNode) {
$propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
$result = array();
for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
$propFilterNode = $propFilterNodes->item($ii);
$propFilter = array();
$propFilter['name'] = $propFilterNode->getAttribute('name');
$propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
$propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
$propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
$propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
$result[] = $propFilter;
}
return $result;
}
/**
* Parses the param-filter element
*
* @param DOMElement $parentNode
* @return array
*/
protected function parseParamFilters(DOMElement $parentNode) {
$paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
$result = array();
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
$paramFilterNode = $paramFilterNodes->item($ii);
$paramFilter = array();
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
$paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
$paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
$result[] = $paramFilter;
}
return $result;
}
/**
* Parses the text-match element
*
* @param DOMElement $parentNode
* @return array|null
*/
protected function parseTextMatch(DOMElement $parentNode) {
$textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
if ($textMatchNodes->length === 0)
return null;
$textMatchNode = $textMatchNodes->item(0);
$negateCondition = $textMatchNode->getAttribute('negate-condition');
$negateCondition = $negateCondition==='yes';
$collation = $textMatchNode->getAttribute('collation');
if (!$collation) $collation = 'i;ascii-casemap';
return array(
'negate-condition' => $negateCondition,
'collation' => $collation,
'value' => $textMatchNode->nodeValue
);
}
/**
* Parses the time-range element
*
* @param DOMElement $parentNode
* @return array|null
*/
protected function parseTimeRange(DOMElement $parentNode) {
$timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
if ($timeRangeNodes->length === 0) {
return null;
}
$timeRangeNode = $timeRangeNodes->item(0);
if ($start = $timeRangeNode->getAttribute('start')) {
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
} else {
$start = null;
}
if ($end = $timeRangeNode->getAttribute('end')) {
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
} else {
$end = null;
}
if (!is_null($start) && !is_null($end) && $end <= $start) {
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter');
}
return array(
'start' => $start,
'end' => $end,
);
}
/**
* Parses the CALDAV:expand element
*
* @param DOMElement $parentNode
* @return void
*/
protected function parseExpand(DOMElement $parentNode) {
$start = $parentNode->getAttribute('start');
if(!$start) {
throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element');
}
$start = Sabre_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);
if ($end <= $start) {
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
}
return array(
'start' => $start,
'end' => $end,
);
}
}

View file

@ -0,0 +1,369 @@
<?php
/**
* CalendarQuery Validator
*
* This class is responsible for checking if an iCalendar object matches a set
* of filters. The main function to do this is 'validate'.
*
* This is used to determine which icalendar objects should be returned for a
* calendar-query REPORT request.
*
* @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_CalendarQueryValidator {
/**
* Verify if a list of filters applies to the calendar data object
*
* The list of filters must be formatted as parsed by Sabre_CalDAV_CalendarQueryParser
*
* @param Sabre_VObject_Component $vObject
* @param array $filters
* @return bool
*/
public function validate(Sabre_VObject_Component $vObject,array $filters) {
// The top level object is always a component filter.
// We'll parse it manually, as it's pretty simple.
if ($vObject->name !== $filters['name']) {
return false;
}
return
$this->validateCompFilters($vObject, $filters['comp-filters']) &&
$this->validatePropFilters($vObject, $filters['prop-filters']);
}
/**
* This method checks the validity of comp-filters.
*
* A list of comp-filters needs to be specified. Also the parent of the
* component we're checking should be specified, not the component to check
* itself.
*
* @param Sabre_VObject_Component $parent
* @param array $filters
* @return bool
*/
protected function validateCompFilters(Sabre_VObject_Component $parent, array $filters) {
foreach($filters as $filter) {
$isDefined = isset($parent->$filter['name']);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if ($filter['time-range']) {
foreach($parent->$filter['name'] as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
}
return false;
}
if (!$filter['comp-filters'] && !$filter['prop-filters']) {
continue;
}
// If there are sub-filters, we need to find at least one component
// for which the subfilters hold true.
foreach($parent->$filter['name'] as $subComponent) {
if (
$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
// We had a match, so this comp-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-comp-filters or
// sub-prop-filters and there was no match. This means this filter
// needs to return false.
return false;
}
// If we got here it means we got through all comp-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of prop-filters.
*
* A list of prop-filters needs to be specified. Also the parent of the
* property we're checking should be specified, not the property to check
* itself.
*
* @param Sabre_VObject_Component $parent
* @param array $filters
* @return bool
*/
protected function validatePropFilters(Sabre_VObject_Component $parent, array $filters) {
foreach($filters as $filter) {
$isDefined = isset($parent->$filter['name']);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if ($filter['time-range']) {
foreach($parent->$filter['name'] as $subComponent) {
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
continue 2;
}
}
return false;
}
if (!$filter['param-filters'] && !$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one property
// for which the subfilters hold true.
foreach($parent->$filter['name'] as $subComponent) {
if(
$this->validateParamFilters($subComponent, $filter['param-filters']) &&
(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
) {
// We had a match, so this prop-filter succeeds
continue 2;
}
}
// If we got here it means there were sub-param-filters or
// text-match filters and there was no match. This means the
// filter needs to return false.
return false;
}
// If we got here it means we got through all prop-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of param-filters.
*
* A list of param-filters needs to be specified. Also the parent of the
* parameter we're checking should be specified, not the parameter to check
* itself.
*
* @param Sabre_VObject_Property $parent
* @param array $filters
* @return bool
*/
protected function validateParamFilters(Sabre_VObject_Property $parent, array $filters) {
foreach($filters as $filter) {
$isDefined = isset($parent[$filter['name']]);
if ($filter['is-not-defined']) {
if ($isDefined) {
return false;
} else {
continue;
}
}
if (!$isDefined) {
return false;
}
if (!$filter['text-match']) {
continue;
}
// If there are sub-filters, we need to find at least one parameter
// for which the subfilters hold true.
foreach($parent[$filter['name']] as $subParam) {
if($this->validateTextMatch($subParam,$filter['text-match'])) {
// We had a match, so this param-filter succeeds
continue 2;
}
}
// If we got here it means there was a text-match filter and there
// were no matches. This means the filter needs to return false.
return false;
}
// If we got here it means we got through all param-filters alive so the
// filters were all true.
return true;
}
/**
* This method checks the validity of a text-match.
*
* A single text-match should be specified as well as the specific property
* or parameter we need to validate.
*
* @param Sabre_VObject_Node $parent
* @param array $textMatch
* @return bool
*/
protected function validateTextMatch(Sabre_VObject_Node $parent, array $textMatch) {
$value = (string)$parent;
$isMatching = Sabre_DAV_StringUtil::textMatch($value, $textMatch['value'], $textMatch['collation']);
return ($textMatch['negate-condition'] xor $isMatching);
}
/**
* Validates if a component matches the given time range.
*
* This is all based on the rules specified in rfc4791, which are quite
* complex.
*
* @param Sabre_VObject_Node $component
* @param DateTime $start
* @param DateTime $end
* @return bool
*/
protected function validateTimeRange(Sabre_VObject_Node $component, $start, $end) {
if (is_null($start)) {
$start = new DateTime('1900-01-01');
}
if (is_null($end)) {
$end = new DateTime('3000-01-01');
}
switch($component->name) {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
return $component->isInTimeRange($start, $end);
case 'VALARM' :
// If the valarm is wrapped in a recurring event, we need to
// expand the recursions, and validate each.
//
// Our datamodel doesn't easily allow us to do this straight
// in the VALARM component code, so this is a hack, and an
// expensive one too.
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
// Fire up the iterator!
$it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
while($it->valid()) {
$expandedEvent = $it->getEventObject();
// We need to check from these expanded alarms, which
// one is the first to trigger. Based on this, we can
// determine if we can 'give up' expanding events.
$firstAlarm = null;
foreach($expandedEvent->VALARM as $expandedAlarm) {
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
if ($expandedAlarm->isInTimeRange($start, $end)) {
return true;
}
if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
// This is an alarm with a non-relative trigger
// time, likely created by a buggy client. The
// implication is that every alarm in this
// recurring event trigger at the exact same
// time. It doesn't make sense to traverse
// further.
} else {
// We store the first alarm as a means to
// figure out when we can stop traversing.
if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
$firstAlarm = $effectiveTrigger;
}
}
}
if (is_null($firstAlarm)) {
// No alarm was found.
//
// Or technically: No alarm that will change for
// every instance of the recurrence was found,
// which means we can assume there was no match.
return false;
}
if ($firstAlarm > $end) {
return false;
}
$it->next();
}
return false;
} else {
return $component->isInTimeRange($start, $end);
}
case 'VFREEBUSY' :
throw new Sabre_DAV_Exception_NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
case 'COMPLETED' :
case 'CREATED' :
case 'DTEND' :
case 'DTSTAMP' :
case 'DTSTART' :
case 'DUE' :
case 'LAST-MODIFIED' :
return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
default :
throw new Sabre_DAV_Exception_BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
}
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* Calendars collection
*
* 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/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollection {
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
*/
protected $caldavBackend;
/**
* Constructor
*
* This constructor needs both an authentication and a caldav backend.
*
* By default this class will show a list of calendar collections for
* principals in the 'principals' collection. If your main principals are
* actually located in a different path, use the $principalPrefix argument
* to override this.
*
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param string $principalPrefix
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_Abstract $caldavBackend, $principalPrefix = 'principals') {
parent::__construct($principalBackend, $principalPrefix);
$this->caldavBackend = $caldavBackend;
}
/**
* Returns the nodename
*
* We're overriding this, because the default will be the 'principalPrefix',
* and we want it to be Sabre_CalDAV_Plugin::CALENDAR_ROOT
*
* @return string
*/
public function getName() {
return Sabre_CalDAV_Plugin::CALENDAR_ROOT;
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principal
* @return Sabre_DAV_INode
*/
public function getChildForPrincipal(array $principal) {
return new Sabre_CalDAV_UserCalendars($this->principalBackend, $this->caldavBackend, $principal['uri']);
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* ICS Exporter
*
* This plugin adds the ability to export entire calendars as .ics files.
* This is useful for clients that don't support CalDAV yet. They often do
* support ics files.
*
* @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_ICSExportPlugin 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_CalDAV_Calendar)) return;
// Checking ACL, if available.
if ($aclPlugin = $this->server->getPlugin('acl')) {
$aclPlugin->checkPrivileges($uri, '{DAV:}read');
}
$this->server->httpResponse->setHeader('Content-Type','text/calendar');
$this->server->httpResponse->sendStatus(200);
$nodes = $this->server->getPropertiesForPath($uri, array(
'{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data',
),1);
$this->server->httpResponse->sendBody($this->generateICS($nodes));
// Returning false to break the event chain
return false;
}
/**
* Merges all calendar objects, and builds one big ics export
*
* @param array $nodes
* @return string
*/
public function generateICS(array $nodes) {
$calendar = new Sabre_VObject_Component('vcalendar');
$calendar->version = '2.0';
if (Sabre_DAV_Server::$exposeVersion) {
$calendar->prodid = '-//SabreDAV//SabreDAV ' . Sabre_DAV_Version::VERSION . '//EN';
} else {
$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
}
$calendar->calscale = 'GREGORIAN';
$collectedTimezones = array();
$timezones = array();
$objects = array();
foreach($nodes as $node) {
if (!isset($node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'])) {
continue;
}
$nodeData = $node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'];
$nodeComp = Sabre_VObject_Reader::read($nodeData);
foreach($nodeComp->children() as $child) {
switch($child->name) {
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
$objects[] = $child;
break;
// VTIMEZONE is special, because we need to filter out the duplicates
case 'VTIMEZONE' :
// Naively just checking tzid.
if (in_array((string)$child->TZID, $collectedTimezones)) continue;
$timezones[] = $child;
$collectedTimezones[] = $child->TZID;
break;
}
}
}
foreach($timezones as $tz) $calendar->add($tz);
foreach($objects as $obj) $calendar->add($obj);
return $calendar->serialize();
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* Calendar interface
*
* Implement this interface to allow a node to be recognized as an calendar.
*
* @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_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);
}

View file

@ -0,0 +1,20 @@
<?php
/**
* CalendarObject interface
/**
* Extend the ICalendarObject interface to allow your custom nodes to be picked up as
* CalendarObjects.
*
* Calendar objects are resources such as Events, Todo's or Journals.
*
* @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_ICalendarObject extends Sabre_DAV_IFile {
}

View file

@ -0,0 +1,894 @@
<?php
/**
* CalDAV plugin
*
* This plugin provides functionality added by CalDAV (RFC 4791)
* It implements new reports, and the MKCALENDAR method.
*
* @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_Plugin extends Sabre_DAV_ServerPlugin {
/**
* This is the official CalDAV namespace
*/
const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
/**
* This is the namespace for the proprietary calendarserver extensions
*/
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
/**
* The hardcoded root for calendar objects. It is unfortunate
* that we're stuck with it, but it will have to do for now
*/
const CALENDAR_ROOT = 'calendars';
/**
* Reference to server object
*
* @var Sabre_DAV_Server
*/
private $server;
/**
* The email handler for invites and other scheduling messages.
*
* @var Sabre_CalDAV_Schedule_IMip
*/
protected $imipHandler;
/**
* Sets the iMIP handler.
*
* iMIP = The email transport of iCalendar scheduling messages. Setting
* this is optional, but if you want the server to allow invites to be sent
* out, you must set a handler.
*
* Specifically iCal will plain assume that the server supports this. If
* the server doesn't, iCal will display errors when inviting people to
* events.
*
* @param Sabre_CalDAV_Schedule_IMip $imipHandler
* @return void
*/
public function setIMipHandler(Sabre_CalDAV_Schedule_IMip $imipHandler) {
$this->imipHandler = $imipHandler;
}
/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* @param string $uri
* @return array
*/
public function getHTTPMethods($uri) {
// The MKCALENDAR is only available on unmapped uri's, whose
// parents extend IExtendedCollection
list($parent, $name) = Sabre_DAV_URLUtil::splitPath($uri);
$node = $this->server->tree->getNodeForPath($parent);
if ($node instanceof Sabre_DAV_IExtendedCollection) {
try {
$node->getChild($name);
} catch (Sabre_DAV_Exception_NotFound $e) {
return array('MKCALENDAR');
}
}
return array();
}
/**
* Returns a list of features for the DAV: HTTP header.
*
* @return array
*/
public function getFeatures() {
return array('calendar-access', 'calendar-proxy');
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using Sabre_DAV_Server::getPlugin
*
* @return string
*/
public function getPluginName() {
return 'caldav';
}
/**
* Returns a list of reports this plugin supports.
*
* This will be used in the {DAV:}supported-report-set property.
* Note that you still need to subscribe to the 'report' event to actually
* implement them
*
* @param string $uri
* @return array
*/
public function getSupportedReportSet($uri) {
$node = $this->server->tree->getNodeForPath($uri);
$reports = array();
if ($node instanceof Sabre_CalDAV_ICalendar || $node instanceof Sabre_CalDAV_ICalendarObject) {
$reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
$reports[] = '{' . self::NS_CALDAV . '}calendar-query';
}
if ($node instanceof Sabre_CalDAV_ICalendar) {
$reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
}
return $reports;
}
/**
* Initializes the plugin
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
$server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
//$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000);
$server->subscribeEvent('report',array($this,'report'));
$server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties'));
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
$server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
$server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
$server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
$server->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->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';
array_push($server->protectedProperties,
'{' . self::NS_CALDAV . '}supported-calendar-component-set',
'{' . self::NS_CALDAV . '}supported-calendar-data',
'{' . self::NS_CALDAV . '}max-resource-size',
'{' . self::NS_CALDAV . '}min-date-time',
'{' . self::NS_CALDAV . '}max-date-time',
'{' . self::NS_CALDAV . '}max-instances',
'{' . self::NS_CALDAV . '}max-attendees-per-instance',
'{' . self::NS_CALDAV . '}calendar-home-set',
'{' . self::NS_CALDAV . '}supported-collation-set',
'{' . self::NS_CALDAV . '}calendar-data',
// scheduling extension
'{' . self::NS_CALDAV . '}schedule-inbox-URL',
'{' . self::NS_CALDAV . '}schedule-outbox-URL',
'{' . self::NS_CALDAV . '}calendar-user-address-set',
'{' . self::NS_CALDAV . '}calendar-user-type',
// CalendarServer extensions
'{' . self::NS_CALENDARSERVER . '}getctag',
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
);
}
/**
* This function handles support for the MKCALENDAR method
*
* @param string $method
* @param string $uri
* @return bool
*/
public function unknownMethod($method, $uri) {
switch ($method) {
case 'MKCALENDAR' :
$this->httpMkCalendar($uri);
// false is returned to stop the propagation of the
// unknownMethod event.
return false;
case 'POST' :
// Checking if we're talking to an outbox
try {
$node = $this->server->tree->getNodeForPath($uri);
} catch (Sabre_DAV_Exception_NotFound $e) {
return;
}
if (!$node instanceof Sabre_CalDAV_Schedule_IOutbox)
return;
$this->outboxRequest($node);
return false;
}
}
/**
* This functions handles REPORT requests specific to CalDAV
*
* @param string $reportName
* @param DOMNode $dom
* @return bool
*/
public function report($reportName,$dom) {
switch($reportName) {
case '{'.self::NS_CALDAV.'}calendar-multiget' :
$this->calendarMultiGetReport($dom);
return false;
case '{'.self::NS_CALDAV.'}calendar-query' :
$this->calendarQueryReport($dom);
return false;
case '{'.self::NS_CALDAV.'}free-busy-query' :
$this->freeBusyQueryReport($dom);
return false;
}
}
/**
* This function handles the MKCALENDAR HTTP method, which creates
* a new calendar.
*
* @param string $uri
* @return void
*/
public function httpMkCalendar($uri) {
// Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
// for clients matching iCal in the user agent
//$ua = $this->server->httpRequest->getHeader('User-Agent');
//if (strpos($ua,'iCal/')!==false) {
// throw new Sabre_DAV_Exception_Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.');
//}
$body = $this->server->httpRequest->getBody(true);
$properties = array();
if ($body) {
$dom = Sabre_DAV_XMLUtil::loadDOMDocument($body);
foreach($dom->firstChild->childNodes as $child) {
if (Sabre_DAV_XMLUtil::toClarkNotation($child)!=='{DAV:}set') continue;
foreach(Sabre_DAV_XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) {
$properties[$k] = $prop;
}
}
}
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');
$this->server->createCollection($uri,$resourceType,$properties);
$this->server->httpResponse->sendStatus(201);
$this->server->httpResponse->setHeader('Content-Length',0);
}
/**
* beforeGetProperties
*
* This method handler is invoked before any after properties for a
* resource are fetched. This allows us to add in any CalDAV specific
* properties.
*
* @param string $path
* @param 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_DAVACL_IPrincipal) {
// calendar-home-set property
$calHome = '{' . self::NS_CALDAV . '}calendar-home-set';
if (in_array($calHome,$requestedProperties)) {
$principalId = $node->getName();
$calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/';
unset($requestedProperties[$calHome]);
$returnedProperties[200][$calHome] = new Sabre_DAV_Property_Href($calendarHomePath);
}
// schedule-outbox-URL property
$scheduleProp = '{' . self::NS_CALDAV . '}schedule-outbox-URL';
if (in_array($scheduleProp,$requestedProperties)) {
$principalId = $node->getName();
$outboxPath = self::CALENDAR_ROOT . '/' . $principalId . '/outbox';
unset($requestedProperties[$scheduleProp]);
$returnedProperties[200][$scheduleProp] = new Sabre_DAV_Property_Href($outboxPath);
}
// calendar-user-address-set property
$calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set';
if (in_array($calProp,$requestedProperties)) {
$addresses = $node->getAlternateUriSet();
$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl();
unset($requestedProperties[$calProp]);
$returnedProperties[200][$calProp] = new Sabre_DAV_Property_HrefList($addresses, false);
}
// These two properties are shortcuts for ical to easily find
// other principals this principal has access to.
$propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
$propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
if (in_array($propRead,$requestedProperties) || in_array($propWrite,$requestedProperties)) {
$membership = $node->getGroupMembership();
$readList = array();
$writeList = array();
foreach($membership as $group) {
$groupNode = $this->server->tree->getNodeForPath($group);
// If the node is either ap proxy-read or proxy-write
// group, we grab the parent principal and add it to the
// list.
if ($groupNode instanceof Sabre_CalDAV_Principal_ProxyRead) {
list($readList[]) = Sabre_DAV_URLUtil::splitPath($group);
}
if ($groupNode instanceof Sabre_CalDAV_Principal_ProxyWrite) {
list($writeList[]) = Sabre_DAV_URLUtil::splitPath($group);
}
}
if (in_array($propRead,$requestedProperties)) {
unset($requestedProperties[$propRead]);
$returnedProperties[200][$propRead] = new Sabre_DAV_Property_HrefList($readList);
}
if (in_array($propWrite,$requestedProperties)) {
unset($requestedProperties[$propWrite]);
$returnedProperties[200][$propWrite] = new Sabre_DAV_Property_HrefList($writeList);
}
}
} // instanceof IPrincipal
if ($node instanceof Sabre_CalDAV_ICalendarObject) {
// The calendar-data property is not supposed to be a 'real'
// property, but in large chunks of the spec it does act as such.
// Therefore we simply expose it as a property.
$calDataProp = '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data';
if (in_array($calDataProp, $requestedProperties)) {
unset($requestedProperties[$calDataProp]);
$val = $node->get();
if (is_resource($val))
$val = stream_get_contents($val);
// Taking out \r to not screw up the xml output
$returnedProperties[200][$calDataProp] = str_replace("\r","", $val);
}
}
}
/**
* This function handles the calendar-multiget REPORT.
*
* This report is used by the client to fetch the content of a series
* of urls. Effectively avoiding a lot of redundant requests.
*
* @param DOMNode $dom
* @return void
*/
public function calendarMultiGetReport($dom) {
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
$hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
$xpath = new DOMXPath($dom);
$xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
$xpath->registerNameSpace('dav','urn:DAV');
$expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand');
if ($expand->length>0) {
$expandElem = $expand->item(0);
$start = $expandElem->getAttribute('start');
$end = $expandElem->getAttribute('end');
if(!$start || !$end) {
throw new 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);
if ($end <= $start) {
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
}
$expand = true;
} else {
$expand = false;
}
foreach($hrefElems as $elem) {
$uri = $this->server->calculateUri($elem->nodeValue);
list($objProps) = $this->server->getPropertiesForPath($uri,$properties);
if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
$vObject = Sabre_VObject_Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
$vObject->expand($start, $end);
$objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
}
$propertyList[]=$objProps;
}
$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 function handles the calendar-query REPORT
*
* This report is used by clients to request calendar objects based on
* complex conditions.
*
* @param DOMNode $dom
* @return void
*/
public function calendarQueryReport($dom) {
$parser = new Sabre_CalDAV_CalendarQueryParser($dom);
$parser->parse();
$node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
$depth = $this->server->getHTTPDepth(0);
// The default result is an empty array
$result = array();
// The calendarobject was requested directly. In this case we handle
// this locally.
if ($depth == 0 && $node instanceof Sabre_CalDAV_ICalendarObject) {
$requestedCalendarData = true;
$requestedProperties = $parser->requestedProperties;
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
// We always retrieve calendar-data, as we need it for filtering.
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
// If calendar-data wasn't explicitly requested, we need to remove
// it after processing.
$requestedCalendarData = false;
}
$properties = $this->server->getPropertiesForPath(
$this->server->getRequestUri(),
$requestedProperties,
0
);
// This array should have only 1 element, the first calendar
// object.
$properties = current($properties);
// If there wasn't any calendar-data returned somehow, we ignore
// this.
if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
$validator = new Sabre_CalDAV_CalendarQueryValidator();
$vObject = Sabre_VObject_Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
if ($validator->validate($vObject,$parser->filters)) {
// If the client didn't require the calendar-data property,
// we won't give it back.
if (!$requestedCalendarData) {
unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
} else {
if ($parser->expand) {
$vObject->expand($parser->expand['start'], $parser->expand['end']);
$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
}
}
$result = array($properties);
}
}
}
// If we're dealing with a calendar, the calendar itself is responsible
// for the calendar-query.
if ($node instanceof 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 = Sabre_VObject_Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
$vObject->expand($parser->expand['start'], $parser->expand['end']);
$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
}
$result[] = $properties;
}
}
$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 method is responsible for parsing the request and generating the
* response for the CALDAV:free-busy-query REPORT.
*
* @param DOMNode $dom
* @return void
*/
protected function freeBusyQueryReport(DOMNode $dom) {
$start = null;
$end = null;
foreach($dom->firstChild->childNodes as $childNode) {
$clark = Sabre_DAV_XMLUtil::toClarkNotation($childNode);
if ($clark == '{' . self::NS_CALDAV . '}time-range') {
$start = $childNode->getAttribute('start');
$end = $childNode->getAttribute('end');
break;
}
}
if ($start) {
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
}
if ($end) {
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
}
if (!$start && !$end) {
throw new Sabre_DAV_Exception_BadRequest('The freebusy report must have a time-range filter');
}
$acl = $this->server->getPlugin('acl');
if (!$acl) {
throw new Sabre_DAV_Exception('The ACL plugin must be loaded for free-busy queries to work');
}
$uri = $this->server->getRequestUri();
$acl->checkPrivileges($uri,'{' . self::NS_CALDAV . '}read-free-busy');
$calendar = $this->server->tree->getNodeForPath($uri);
if (!$calendar instanceof Sabre_CalDAV_ICalendar) {
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);
}
return $obj;
}, $calendar->getChildren());
$generator = new Sabre_VObject_FreeBusyGenerator();
$generator->setObjects($objects);
$generator->setTimeRange($start, $end);
$result = $generator->getResult();
$result = $result->serialize();
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
$this->server->httpResponse->setHeader('Content-Length', strlen($result));
$this->server->httpResponse->sendBody($result);
}
/**
* This method is triggered before a file gets updated with new content.
*
* This plugin uses this method to ensure that CalDAV objects receive
* valid calendar data.
*
* @param string $path
* @param Sabre_DAV_IFile $node
* @param resource $data
* @return void
*/
public function beforeWriteContent($path, Sabre_DAV_IFile $node, &$data) {
if (!$node instanceof Sabre_CalDAV_ICalendarObject)
return;
$this->validateICalendar($data);
}
/**
* This method is triggered before a new file is created.
*
* This plugin uses this method to ensure that newly created calendar
* objects contain valid calendar data.
*
* @param string $path
* @param resource $data
* @param Sabre_DAV_ICollection $parentNode
* @return void
*/
public function beforeCreateFile($path, &$data, Sabre_DAV_ICollection $parentNode) {
if (!$parentNode instanceof Sabre_CalDAV_Calendar)
return;
$this->validateICalendar($data);
}
/**
* Checks if the submitted iCalendar data is in fact, valid.
*
* An exception is thrown if it's not.
*
* @param resource|string $data
* @return void
*/
protected function validateICalendar(&$data) {
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Converting the data to unicode, if needed.
$data = Sabre_DAV_StringUtil::ensureUTF8($data);
try {
$vobj = Sabre_VObject_Reader::read($data);
} catch (Sabre_VObject_ParseException $e) {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCALENDAR') {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support iCalendar objects.');
}
$foundType = null;
$foundUID = null;
foreach($vobj->getComponents() as $component) {
switch($component->name) {
case 'VTIMEZONE' :
continue 2;
case 'VEVENT' :
case 'VTODO' :
case 'VJOURNAL' :
if (is_null($foundType)) {
$foundType = $component->name;
if (!isset($component->UID)) {
throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' component must have an UID');
}
$foundUID = (string)$component->UID;
} else {
if ($foundType !== $component->name) {
throw new Sabre_DAV_Exception_BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
}
if ($foundUID !== (string)$component->UID) {
throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
}
}
break;
default :
throw new Sabre_DAV_Exception_BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
}
}
if (!$foundType)
throw new Sabre_DAV_Exception_BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
}
/**
* This method handles POST requests to the schedule-outbox
*
* @param Sabre_CalDAV_Schedule_IOutbox $outboxNode
* @return void
*/
public function outboxRequest(Sabre_CalDAV_Schedule_IOutbox $outboxNode) {
$originator = $this->server->httpRequest->getHeader('Originator');
$recipients = $this->server->httpRequest->getHeader('Recipient');
if (!$originator) {
throw new Sabre_DAV_Exception_BadRequest('The Originator: header must be specified when making POST requests');
}
if (!$recipients) {
throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests');
}
if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) {
throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address');
}
$originator = substr($originator,7);
$recipients = explode(',',$recipients);
foreach($recipients as $k=>$recipient) {
$recipient = trim($recipient);
if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) {
throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address');
}
$recipient = substr($recipient, 7);
$recipients[$k] = $recipient;
}
// We need to make sure that 'originator' matches one of the email
// addresses of the selected principal.
$principal = $outboxNode->getOwner();
$props = $this->server->getProperties($principal,array(
'{' . self::NS_CALDAV . '}calendar-user-address-set',
));
$addresses = array();
if (isset($props['{' . self::NS_CALDAV . '}calendar-user-address-set'])) {
$addresses = $props['{' . self::NS_CALDAV . '}calendar-user-address-set']->getHrefs();
}
if (!in_array('mailto:' . $originator, $addresses)) {
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') {
$this->iMIPMessage($originator, $recipients, $vObject, $principal);
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->sendBody('Messages sent');
} else {
throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented');
}
}
/**
* Sends an iMIP message by email.
*
* @param string $originator
* @param array $recipients
* @param Sabre_VObject_Component $vObject
* @param string $principal Principal url
* @return void
*/
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.');
}
$this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
}
/**
* This method is used to generate HTML output for the
* Sabre_DAV_Browser_Plugin. This allows us to generate an interface users
* can use to create new calendars.
*
* @param Sabre_DAV_INode $node
* @param string $output
* @return bool
*/
public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
if (!$node instanceof Sabre_CalDAV_UserCalendars)
return;
$output.= '<tr><td colspan="2"><form method="post" action="">
<h3>Create new calendar</h3>
<input type="hidden" name="sabreAction" value="mkcalendar" />
<label>Name (uri):</label> <input type="text" name="name" /><br />
<label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
<input type="submit" value="create" />
</form>
</td></tr>';
return false;
}
/**
* This method allows us to intercept the 'mkcalendar' sabreAction. This
* action enables the user to create new calendars from the browser plugin.
*
* @param string $uri
* @param string $action
* @param array $postVars
* @return bool
*/
public function browserPostAction($uri, $action, array $postVars) {
if ($action!=='mkcalendar')
return;
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');
$properties = array();
if (isset($postVars['{DAV:}displayname'])) {
$properties['{DAV:}displayname'] = $postVars['{DAV:}displayname'];
}
$this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties);
return false;
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* Principal collection
*
* This is an alternative collection to the standard ACL principal collection.
* This collection adds support for the calendar-proxy-read and
* calendar-proxy-write sub-principals, as defined by the caldav-proxy
* specification.
*
* @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_Principal_Collection extends Sabre_DAVACL_AbstractPrincipalCollection {
/**
* Returns a child object based on principal information
*
* @param array $principalInfo
* @return Sabre_CalDAV_Principal_User
*/
public function getChildForPrincipal(array $principalInfo) {
return new Sabre_CalDAV_Principal_User($this->principalBackend, $principalInfo);
}
}

View file

@ -0,0 +1,178 @@
<?php
/**
* ProxyRead principal
*
* This class represents a principal group, hosted under the main principal.
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by Sabre_CalDAV_Principal_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_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal {
/**
* Principal information from the parent principal.
*
* @var array
*/
protected $principalInfo;
/**
* Principal backend
*
* @var Sabre_DAVACL_IPrincipalBackend
*/
protected $principalBackend;
/**
* Creates the object.
*
* Note that you MUST supply the parent principal information.
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param array $principalInfo
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalInfo) {
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
}
/**
* Returns this principals name.
*
* @return string
*/
public function getName() {
return 'calendar-proxy-read';
}
/**
* Returns the last modification time
*
* @return null
*/
public function getLastModified() {
return null;
}
/**
* Deletes the current node
*
* @throws Sabre_DAV_Exception_Forbidden
* @return void
*/
public function delete() {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to delete node');
}
/**
* Renames the node
*
* @throws Sabre_DAV_Exception_Forbidden
* @param string $name The new name
* @return void
*/
public function setName($name) {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to rename file');
}
/**
* Returns a list of alternative urls for a principal
*
* This can for example be an email address, or ldap url.
*
* @return array
*/
public function getAlternateUriSet() {
return array();
}
/**
* Returns the full principal url
*
* @return string
*/
public function getPrincipalUrl() {
return $this->principalInfo['uri'] . '/' . $this->getName();
}
/**
* Returns the list of group members
*
* If this principal is a group, this function should return
* all member principal uri's for the group.
*
* @return array
*/
public function getGroupMemberSet() {
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
}
/**
* Returns the list of groups this principal is member of
*
* If this principal is a member of a (list of) groups, this function
* should return a list of principal uri's for it's members.
*
* @return array
*/
public function getGroupMembership() {
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
}
/**
* Sets a list of group members
*
* If this principal is a group, this method sets all the group members.
* The list of members is always overwritten, never appended to.
*
* This method should throw an exception if the members could not be set.
*
* @param array $principals
* @return void
*/
public function setGroupMemberSet(array $principals) {
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
}
/**
* Returns the displayname
*
* This should be a human readable name for the principal.
* If none is available, return the nodename.
*
* @return string
*/
public function getDisplayName() {
return $this->getName();
}
}

View file

@ -0,0 +1,178 @@
<?php
/**
* ProxyWrite principal
*
* This class represents a principal group, hosted under the main principal.
* This is needed to implement 'Calendar delegation' support. This class is
* instantiated by Sabre_CalDAV_Principal_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_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal {
/**
* Parent principal information
*
* @var array
*/
protected $principalInfo;
/**
* Principal Backend
*
* @var Sabre_DAVACL_IPrincipalBackend
*/
protected $principalBackend;
/**
* Creates the object
*
* Note that you MUST supply the parent principal information.
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param array $principalInfo
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalInfo) {
$this->principalInfo = $principalInfo;
$this->principalBackend = $principalBackend;
}
/**
* Returns this principals name.
*
* @return string
*/
public function getName() {
return 'calendar-proxy-write';
}
/**
* Returns the last modification time
*
* @return null
*/
public function getLastModified() {
return null;
}
/**
* Deletes the current node
*
* @throws Sabre_DAV_Exception_Forbidden
* @return void
*/
public function delete() {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to delete node');
}
/**
* Renames the node
*
* @throws Sabre_DAV_Exception_Forbidden
* @param string $name The new name
* @return void
*/
public function setName($name) {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to rename file');
}
/**
* Returns a list of alternative urls for a principal
*
* This can for example be an email address, or ldap url.
*
* @return array
*/
public function getAlternateUriSet() {
return array();
}
/**
* Returns the full principal url
*
* @return string
*/
public function getPrincipalUrl() {
return $this->principalInfo['uri'] . '/' . $this->getName();
}
/**
* Returns the list of group members
*
* If this principal is a group, this function should return
* all member principal uri's for the group.
*
* @return array
*/
public function getGroupMemberSet() {
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
}
/**
* Returns the list of groups this principal is member of
*
* If this principal is a member of a (list of) groups, this function
* should return a list of principal uri's for it's members.
*
* @return array
*/
public function getGroupMembership() {
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
}
/**
* Sets a list of group members
*
* If this principal is a group, this method sets all the group members.
* The list of members is always overwritten, never appended to.
*
* This method should throw an exception if the members could not be set.
*
* @param array $principals
* @return void
*/
public function setGroupMemberSet(array $principals) {
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
}
/**
* Returns the displayname
*
* This should be a human readable name for the principal.
* If none is available, return the nodename.
*
* @return string
*/
public function getDisplayName() {
return $this->getName();
}
}

View file

@ -0,0 +1,132 @@
<?php
/**
* CalDAV principal
*
* This is a standard user-principal for CalDAV. This principal is also a
* collection and returns the caldav-proxy-read and caldav-proxy-write child
* principals.
*
* @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_Principal_User extends Sabre_DAVACL_Principal implements Sabre_DAV_ICollection {
/**
* Creates a new file in the directory
*
* @param string $name Name of the file
* @param resource $data Initial payload, passed as a readable stream resource.
* @throws Sabre_DAV_Exception_Forbidden
* @return void
*/
public function createFile($name, $data = null) {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to create file (filename ' . $name . ')');
}
/**
* Creates a new subdirectory
*
* @param string $name
* @throws Sabre_DAV_Exception_Forbidden
* @return void
*/
public function createDirectory($name) {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to create directory');
}
/**
* Returns a specific child node, referenced by its name
*
* @param string $name
* @return Sabre_DAV_INode
*/
public function getChild($name) {
$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
if (!$principal) {
throw new Sabre_DAV_Exception_NotFound('Node with name ' . $name . ' was not found');
}
if ($name === 'calendar-proxy-read')
return new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties);
if ($name === 'calendar-proxy-write')
return new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties);
throw new Sabre_DAV_Exception_NotFound('Node with name ' . $name . ' was not found');
}
/**
* Returns an array with all the child nodes
*
* @return Sabre_DAV_INode[]
*/
public function getChildren() {
$r = array();
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
$r[] = new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties);
}
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
$r[] = new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties);
}
return $r;
}
/**
* Returns whether or not the child node exists
*
* @param string $name
* @return bool
*/
public function childExists($name) {
try {
$this->getChild($name);
return true;
} catch (Sabre_DAV_Exception_NotFound $e) {
return false;
}
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
$acl = parent::getACL();
$acl[] = array(
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
'protected' => true,
);
$acl[] = array(
'privilege' => '{DAV:}read',
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
'protected' => true,
);
return $acl;
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* Supported component set property
*
* 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
* @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_SupportedCalendarComponentSet extends Sabre_DAV_Property {
/**
* List of supported components, such as "VEVENT, VTODO"
*
* @var array
*/
private $components;
/**
* Creates the property
*
* @param array $components
*/
public function __construct(array $components) {
$this->components = $components;
}
/**
* Returns the list of supported components
*
* @return array
*/
public function getValue() {
return $this->components;
}
/**
* 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->components as $component) {
$xcomp = $doc->createElement('cal:comp');
$xcomp->setAttribute('name',$component);
$node->appendChild($xcomp);
}
}
/**
* Unserializes the DOMElement back into a Property class.
*
* @param DOMElement $node
* @return Sabre_CalDAV_Property_SupportedCalendarComponentSet
*/
static function unserialize(DOMElement $node) {
$components = array();
foreach($node->childNodes as $childNode) {
if (Sabre_DAV_XMLUtil::toClarkNotation($childNode)==='{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}comp') {
$components[] = $childNode->getAttribute('name');
}
}
return new self($components);
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* Supported-calendar-data property
*
* This property is a representation of the supported-calendar-data property
* in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0
* so the value is currently hardcoded.
*
* @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_SupportedCalendarData extends Sabre_DAV_Property {
/**
* 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;
$prefix = isset($server->xmlNamespaces[Sabre_CalDAV_Plugin::NS_CALDAV])?$server->xmlNamespaces[Sabre_CalDAV_Plugin::NS_CALDAV]:'cal';
$caldata = $doc->createElement($prefix . ':calendar-data');
$caldata->setAttribute('content-type','text/calendar');
$caldata->setAttribute('version','2.0');
$node->appendChild($caldata);
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* supported-collation-set property
*
* This property is a representation of the supported-collation-set property
* in the CalDAV namespace.
*
* @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_SupportedCollationSet extends Sabre_DAV_Property {
/**
* Serializes the property in a DOM document
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $node) {
$doc = $node->ownerDocument;
$prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:caldav');
if (!$prefix) $prefix = 'cal';
$node->appendChild(
$doc->createElement($prefix . ':supported-collation','i;ascii-casemap')
);
$node->appendChild(
$doc->createElement($prefix . ':supported-collation','i;octet')
);
$node->appendChild(
$doc->createElement($prefix . ':supported-collation','i;unicode-casemap')
);
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* iMIP handler.
*
* This class is responsible for sending out iMIP messages. iMIP is the
* email-based transport for iTIP. iTIP deals with scheduling operations for
* iCalendar objects.
*
* If you want to customize the email that gets sent out, you can do so by
* extending this class and overriding the sendMessage method.
*
* @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_Schedule_IMip {
/**
* Email address used in From: header.
*
* @var string
*/
protected $senderEmail;
/**
* Creates the email handler.
*
* @param string $senderEmail. The 'senderEmail' is the email that shows up
* in the 'From:' address. This should
* generally be some kind of no-reply email
* address you own.
*/
public function __construct($senderEmail) {
$this->senderEmail = $senderEmail;
}
/**
* Sends one or more iTip messages through email.
*
* @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, $principal) {
foreach($recipients as $recipient) {
$to = $recipient;
$replyTo = $originator;
$subject = 'SabreDAV iTIP message';
switch(strtoupper($vObject->METHOD)) {
case 'REPLY' :
$subject = 'Response for: ' . $vObject->VEVENT->SUMMARY;
break;
case 'REQUEST' :
$subject = 'Invitation for: ' .$vObject->VEVENT->SUMMARY;
break;
case 'CANCEL' :
$subject = 'Cancelled event: ' . $vObject->VEVENT->SUMMARY;
break;
}
$headers = array();
$headers[] = 'Reply-To: ' . $replyTo;
$headers[] = 'From: ' . $this->senderEmail;
$headers[] = 'Content-Type: text/calendar; method=' . (string)$vObject->method . '; charset=utf-8';
if (Sabre_DAV_Server::$exposeVersion) {
$headers[] = 'X-Sabre-Version: ' . Sabre_DAV_Version::VERSION . '-' . Sabre_DAV_Version::STABILITY;
}
$vcalBody = $vObject->serialize();
$this->mail($to, $subject, $vcalBody, $headers);
}
}
/**
* This function is reponsible for sending the actual email.
*
* @param string $to Recipient email address
* @param string $subject Subject of the email
* @param string $body iCalendar body
* @param array $headers List of headers
* @return void
*/
protected function mail($to, $subject, $body, array $headers) {
mail($to, $subject, $body, implode("\r\n", $headers));
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* Implement this interface to have a node be recognized as a CalDAV scheduling
* outbox.
*
* @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_Schedule_IOutbox extends Sabre_DAV_ICollection, Sabre_DAVACL_IACL {
}

View file

@ -0,0 +1,152 @@
<?php
/**
* The CalDAV scheduling outbox
*
* The outbox is mainly used as an endpoint in the tree for a client to do
* free-busy requests. This functionality is completely handled by the
* Scheduling plugin, so this object is actually mostly static.
*
* @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_Schedule_Outbox extends Sabre_DAV_Collection implements Sabre_CalDAV_Schedule_IOutbox {
/**
* The principal Uri
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param string $principalUri
*/
public function __construct($principalUri) {
$this->principalUri = $principalUri;
}
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
public function getName() {
return 'outbox';
}
/**
* Returns an array with all the child nodes
*
* @return Sabre_DAV_INode[]
*/
public function getChildren() {
return array();
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalUri;
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy',
'principal' => $this->getOwner(),
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->getOwner(),
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('You\'re not allowed to update the ACL');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
$default = Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet();
$default['aggregates'][] = array(
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy',
);
return $default;
}
}

View file

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

View file

@ -0,0 +1,298 @@
<?php
/**
* The UserCalenders class contains all calendars associated to one 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_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
/**
* Principal backend
*
* @var Sabre_DAVACL_IPrincipalBackend
*/
protected $principalBackend;
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
*/
protected $caldavBackend;
/**
* Principal information
*
* @var array
*/
protected $principalInfo;
/**
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param mixed $userUri
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $userUri) {
$this->principalBackend = $principalBackend;
$this->caldavBackend = $caldavBackend;
$this->principalInfo = $principalBackend->getPrincipalByPath($userUri);
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
list(,$name) = Sabre_DAV_URLUtil::splitPath($this->principalInfo['uri']);
return $name;
}
/**
* Updates the name of this object
*
* @param string $name
* @return void
*/
public function setName($name) {
throw new Sabre_DAV_Exception_Forbidden();
}
/**
* Deletes this object
*
* @return void
*/
public function delete() {
throw new Sabre_DAV_Exception_Forbidden();
}
/**
* Returns the last modification date
*
* @return int
*/
public function getLastModified() {
return null;
}
/**
* Creates a new file under this object.
*
* This is currently not allowed
*
* @param string $filename
* @param resource $data
* @return void
*/
public function createFile($filename, $data=null) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating new files in this collection is not supported');
}
/**
* Creates a new directory under this object.
*
* This is currently not allowed.
*
* @param string $filename
* @return void
*/
public function createDirectory($filename) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating new collections in this collection is not supported');
}
/**
* Returns a single calendar, by name
*
* @param string $name
* @todo needs optimizing
* @return Sabre_CalDAV_Calendar
*/
public function getChild($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return $child;
}
throw new Sabre_DAV_Exception_NotFound('Calendar with name \'' . $name . '\' could not be found');
}
/**
* Checks if a calendar exists.
*
* @param string $name
* @todo needs optimizing
* @return bool
*/
public function childExists($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return true;
}
return false;
}
/**
* Returns a list of calendars
*
* @return array
*/
public function getChildren() {
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
$objs = array();
foreach($calendars as $calendar) {
$objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar);
}
$objs[] = new Sabre_CalDAV_Schedule_Outbox($this->principalInfo['uri']);
return $objs;
}
/**
* Creates a new calendar
*
* @param string $name
* @param array $resourceType
* @param array $properties
* @return void
*/
public function createExtendedCollection($name, array $resourceType, array $properties) {
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');
}
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
}
/**
* Returns the owner principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getOwner() {
return $this->principalInfo['uri'];
}
/**
* Returns a group principal
*
* This must be a url to a principal, or null if there's no owner
*
* @return string|null
*/
public function getGroup() {
return null;
}
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL() {
return array(
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
'protected' => true,
),
array(
'privilege' => '{DAV:}read',
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
'protected' => true,
),
);
}
/**
* Updates the ACL
*
* This method will receive a list of new ACE's.
*
* @param array $acl
* @return void
*/
public function setACL(array $acl) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
}
/**
* Returns the list of supported privileges for this node.
*
* The returned data structure is a list of nested privileges.
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
* standard structure.
*
* If null is returned from this method, the default privilege set is used,
* which is fine for most common usecases.
*
* @return array|null
*/
public function getSupportedPrivilegeSet() {
return null;
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* This class contains the Sabre_CalDAV version constants.
*
* @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_Version {
/**
* Full version number
*/
const VERSION = '1.7.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'alpha';
}

View file

@ -0,0 +1,43 @@
<?php
/**
* Sabre_CalDAV includes file
*
* Including this file will automatically include all files from the
* Sabre_CalDAV package.
*
* This often allows faster loadtimes, as autoload-speed is often quite slow.
*
* @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
*/
// Begin includes
include __DIR__ . '/Backend/Abstract.php';
include __DIR__ . '/Backend/PDO.php';
include __DIR__ . '/CalendarQueryParser.php';
include __DIR__ . '/CalendarQueryValidator.php';
include __DIR__ . '/CalendarRootNode.php';
include __DIR__ . '/ICalendar.php';
include __DIR__ . '/ICalendarObject.php';
include __DIR__ . '/ICSExportPlugin.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/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__ . '/UserCalendars.php';
include __DIR__ . '/Version.php';
include __DIR__ . '/Calendar.php';
include __DIR__ . '/CalendarObject.php';
// End includes