moved deprecated communityhome, dav and yourls to the deprecated-addons repository

This commit is contained in:
Tobias Diekershoff 2018-06-02 11:23:11 +02:00
commit 31520f804d
675 changed files with 195144 additions and 0 deletions

View file

@ -0,0 +1,153 @@
<?php
use Sabre\VObject;
/**
* Abstract Calendaring backend. Extend this class to create your own backends.
*
* Checkout the BackendInterface for all the methods that must be implemented.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_CalDAV_Backend_Abstract implements Sabre_CalDAV_Backend_BackendInterface {
/**
* 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;
}
/**
* 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 = VObject\Reader::read($object['calendardata']);
$validator = new Sabre_CalDAV_CalendarQueryValidator();
return $validator->validate($vObject, $filters);
}
}

View file

@ -0,0 +1,231 @@
<?php
/**
* Every CalDAV backend must at least implement this interface.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
interface Sabre_CalDAV_Backend_BackendInterface {
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* @param string $principalUri
* @return array
*/
public function getCalendarsForUser($principalUri);
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
*/
public function createCalendar($principalUri,$calendarUri,array $properties);
/**
* Updates properties for a calendar.
*
* The mutations array uses the propertyName in clark-notation as key,
* and the array value for the property value. In the case a property
* should be deleted, the property value will be null.
*
* This method must be atomic. If one property cannot be changed, the
* entire operation must fail.
*
* If the operation was successful, true can be returned.
* If the operation failed, false can be returned.
*
* Deletion of a non-existent property is always successful.
*
* Lastly, it is optional to return detailed information about any
* failures. In this case an array should be returned with the following
* structure:
*
* array(
* 403 => array(
* '{DAV:}displayname' => null,
* ),
* 424 => array(
* '{DAV:}owner' => null,
* )
* )
*
* In this example it was forbidden to update {DAV:}displayname.
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
* (424 Failed Dependency) because the request needs to be atomic.
*
* @param mixed $calendarId
* @param array $mutations
* @return bool|array
*/
public function updateCalendar($calendarId, array $mutations);
/**
* Delete a calendar and all it's objects
*
* @param mixed $calendarId
* @return void
*/
public function deleteCalendar($calendarId);
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * id - unique identifier which will be used for subsequent updates
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* ' "abcdef"')
* * calendarid - The calendarid as it was passed to this function.
* * size - The size of the calendar objects, in bytes.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param mixed $calendarId
* @return array
*/
public function getCalendarObjects($calendarId);
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* @param mixed $calendarId
* @param string $objectUri
* @return array
*/
public function getCalendarObject($calendarId,$objectUri);
/**
* Creates a new calendar object.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function createCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Updates an existing calendarobject, based on it's uri.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Deletes an existing calendar object.
*
* @param mixed $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId,$objectUri);
/**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
* calendar-query it is possible for a client to request a specific set of
* object, based on contents of iCalendar properties, date-ranges and
* iCalendar component types (VTODO, VEVENT).
*
* This method should just return a list of (relative) urls that match this
* query.
*
* The list of filters are specified as an array. The exact array is
* documented by Sabre_CalDAV_CalendarQueryParser.
*
* Note that it is extremely likely that getCalendarObject for every path
* returned from this method will be called almost immediately after. You
* may want to anticipate this to speed up these requests.
*
* This method provides a default implementation, which parses *all* the
* iCalendar objects in the specified calendar.
*
* This default may well be good enough for personal use, and calendars
* that aren't very large. But if you anticipate high usage, big calendars
* or high loads, you are strongly adviced to optimize certain paths.
*
* The best way to do so is override this method and to optimize
* specifically for 'common filters'.
*
* Requests that are extremely common are:
* * requests for just VEVENTS
* * requests for just VTODO
* * requests with a time-range-filter on either VEVENT or VTODO.
*
* ..and combinations of these requests. It may not be worth it to try to
* handle every possible situation and just rely on the (relatively
* easy to use) CalendarQueryValidator to handle the rest.
*
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
* A good example of how to interprete all these filters can also simply
* be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
*
* @param mixed $calendarId
* @param array $filters
* @return array
*/
public function calendarQuery($calendarId, array $filters);
}

View file

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

View file

@ -0,0 +1,664 @@
<?php
use Sabre\VObject;
/**
* 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*
*
* On 32 bit system the maximum for a signed integer is 2147483647, so
* MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
* in 2038-01-19 to avoid problems when the date is converted
* to a unix timestamp.
*/
const MAX_DATE = '2038-01-01';
/**
* pdo
*
* @var PDO
*/
protected $pdo;
/**
* The table name that will be used for calendars
*
* @var string
*/
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 = VObject\Reader::read($calendarData);
$componentType = null;
$component = null;
$firstOccurence = null;
$lastOccurence = null;
foreach($vObject->getComponents() as $component) {
if ($component->name!=='VTIMEZONE') {
$componentType = $component->name;
break;
}
}
if (!$componentType) {
throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
}
if ($componentType === 'VEVENT') {
$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
// Finding the last occurence is a bit harder
if (!isset($component->RRULE)) {
if (isset($component->DTEND)) {
$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
} elseif (isset($component->DURATION)) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->add(VObject\DateTimeParser::parse($component->DURATION->value));
$lastOccurence = $endDate->getTimeStamp();
} elseif ($component->DTSTART->getDateType()===VObject\Property\DateTime::DATE) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->modify('+1 day');
$lastOccurence = $endDate->getTimeStamp();
} else {
$lastOccurence = $firstOccurence;
}
} else {
$it = new VObject\RecurrenceIterator($vObject, (string)$component->UID);
$maxDate = new DateTime(self::MAX_DATE);
if ($it->isInfinite()) {
$lastOccurence = $maxDate->getTimeStamp();
} else {
$end = $it->getDtEnd();
while($it->valid() && $end < $maxDate) {
$end = $it->getDtEnd();
$it->next();
}
$lastOccurence = $end->getTimeStamp();
}
}
}
return array(
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => $firstOccurence,
'lastOccurence' => $lastOccurence,
);
}
/**
* 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_BackendInterface
*/
protected $caldavBackend;
/**
* Principal backend
*
* @var Sabre_DAVACL_IPrincipalBackend
*/
protected $principalBackend;
/**
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param array $calendarInfo
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $calendarInfo) {
$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_BackendInterface
*
* @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_BackendInterface $caldavBackend
* @param array $calendarInfo
* @param array $objectData
*/
public function __construct(Sabre_CalDAV_Backend_BackendInterface $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,298 @@
<?php
use Sabre\VObject;
/**
* 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','DAV:');
}
/**
* Parses the request.
*
* @return void
*/
public function parse() {
$filterNode = null;
$filter = $this->xpath->query('/cal:calendar-query/cal:filter');
if ($filter->length !== 1) {
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
}
$compFilters = $this->parseCompFilters($filter->item(0));
if (count($compFilters)!==1) {
throw new Sabre_DAV_Exception_BadRequest('There must be exactly 1 top-level comp-filter.');
}
$this->filters = $compFilters[0];
$this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
$expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
if ($expand->length>0) {
$this->expand = $this->parseExpand($expand->item(0));
}
}
/**
* Parses all the 'comp-filter' elements from a node
*
* @param DOMElement $parentNode
* @return array
*/
protected function parseCompFilters(DOMElement $parentNode) {
$compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
$result = array();
for($ii=0; $ii < $compFilterNodes->length; $ii++) {
$compFilterNode = $compFilterNodes->item($ii);
$compFilter = array();
$compFilter['name'] = $compFilterNode->getAttribute('name');
$compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
$compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
$compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
$compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
'VEVENT',
'VTODO',
'VJOURNAL',
'VFREEBUSY',
'VALARM',
))) {
throw new Sabre_DAV_Exception_BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
};
$result[] = $compFilter;
}
return $result;
}
/**
* Parses all the prop-filter elements from a node
*
* @param DOMElement $parentNode
* @return array
*/
protected function parsePropFilters(DOMElement $parentNode) {
$propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
$result = array();
for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
$propFilterNode = $propFilterNodes->item($ii);
$propFilter = array();
$propFilter['name'] = $propFilterNode->getAttribute('name');
$propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
$propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
$propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
$propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
$result[] = $propFilter;
}
return $result;
}
/**
* Parses the param-filter element
*
* @param DOMElement $parentNode
* @return array
*/
protected function parseParamFilters(DOMElement $parentNode) {
$paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
$result = array();
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
$paramFilterNode = $paramFilterNodes->item($ii);
$paramFilter = array();
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
$paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
$paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
$result[] = $paramFilter;
}
return $result;
}
/**
* Parses the text-match element
*
* @param DOMElement $parentNode
* @return array|null
*/
protected function parseTextMatch(DOMElement $parentNode) {
$textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
if ($textMatchNodes->length === 0)
return null;
$textMatchNode = $textMatchNodes->item(0);
$negateCondition = $textMatchNode->getAttribute('negate-condition');
$negateCondition = $negateCondition==='yes';
$collation = $textMatchNode->getAttribute('collation');
if (!$collation) $collation = 'i;ascii-casemap';
return array(
'negate-condition' => $negateCondition,
'collation' => $collation,
'value' => $textMatchNode->nodeValue
);
}
/**
* Parses the time-range element
*
* @param DOMElement $parentNode
* @return array|null
*/
protected function parseTimeRange(DOMElement $parentNode) {
$timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
if ($timeRangeNodes->length === 0) {
return null;
}
$timeRangeNode = $timeRangeNodes->item(0);
if ($start = $timeRangeNode->getAttribute('start')) {
$start = VObject\DateTimeParser::parseDateTime($start);
} else {
$start = null;
}
if ($end = $timeRangeNode->getAttribute('end')) {
$end = VObject\DateTimeParser::parseDateTime($end);
} else {
$end = null;
}
if (!is_null($start) && !is_null($end) && $end <= $start) {
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter');
}
return array(
'start' => $start,
'end' => $end,
);
}
/**
* Parses the CALDAV:expand element
*
* @param DOMElement $parentNode
* @return void
*/
protected function parseExpand(DOMElement $parentNode) {
$start = $parentNode->getAttribute('start');
if(!$start) {
throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element');
}
$start = VObject\DateTimeParser::parseDateTime($start);
$end = $parentNode->getAttribute('end');
if(!$end) {
throw new Sabre_DAV_Exception_BadRequest('The "end" attribute is required for the CALDAV:expand element');
}
$end = VObject\DateTimeParser::parseDateTime($end);
if ($end <= $start) {
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
}
return array(
'start' => $start,
'end' => $end,
);
}
}

View file

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

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_BackendInterface
*/
protected $caldavBackend;
/**
* Constructor
*
* This constructor needs both an authentication and a caldav backend.
*
* By default this class will show a list of calendar collections for
* principals in the 'principals' collection. If your main principals are
* actually located in a different path, use the $principalPrefix argument
* to override this.
*
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param string $principalPrefix
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_BackendInterface $caldavBackend, $principalPrefix = 'principals') {
parent::__construct($principalBackend, $principalPrefix);
$this->caldavBackend = $caldavBackend;
}
/**
* Returns the nodename
*
* We're overriding this, because the default will be the 'principalPrefix',
* and we want it to be Sabre_CalDAV_Plugin::CALENDAR_ROOT
*
* @return string
*/
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,32 @@
<?php
/**
* Sabre_CalDAV_Exception_InvalidComponentType
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Exception_InvalidComponentType extends Sabre_DAV_Exception_Forbidden {
/**
* Adds in extra information in the xml response.
*
* This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
$doc = $errorNode->ownerDocument;
$np = $doc->createElementNS(Sabre_CalDAV_Plugin::NS_CALDAV,'cal:supported-calendar-component');
$errorNode->appendChild($np);
}
}

View file

@ -0,0 +1,141 @@
<?php
use Sabre\VObject;
/**
* ICS Exporter
*
* This addon 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 addon 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 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 = 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,80 @@
<?php
/**
* This node represents a list of notifications.
*
* It provides no additional functionality, but you must implement this
* interface to allow the Notifications addon to mark the collection
* as a notifications collection.
*
* This collection should only return Sabre_CalDAV_Notifications_INode nodes as
* its children.
*
* @package Sabre
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Collection extends Sabre_DAV_Collection implements Sabre_CalDAV_Notifications_ICollection {
/**
* The notification backend
*
* @var Sabre_CalDAV_Backend_NotificationSupport
*/
protected $caldavBackend;
/**
* Principal uri
*
* @var string
*/
protected $principalUri;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
* @param string $principalUri
*/
public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, $principalUri) {
$this->caldavBackend = $caldavBackend;
$this->principalUri = $principalUri;
}
/**
* Returns all notifications for a principal
*
* @return array
*/
public function getChildren() {
$children = array();
$notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);
foreach($notifications as $notification) {
$children[] = new Sabre_CalDAV_Notifications_Node(
$this->caldavBackend,
$notification
);
}
return $children;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
return 'notifications';
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,68 @@
<?php
/**
* This node represents a single notification.
*
* The signature is mostly identical to that of Sabre_DAV_IFile, but the get() method
* MUST return an xml document that matches the requirements of the
* 'caldav-notifications.txt' spec.
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Node extends Sabre_DAV_Node implements Sabre_CalDAV_Notifications_INode {
/**
* The notification backend
*
* @var Sabre_CalDAV_Backend_NotificationSupport
*/
protected $caldavBackend;
/**
* The actual notification
*
* @var Sabre_CalDAV_Notifications_INotificationType
*/
protected $notification;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_NotificationSupport $caldavBackend
* @param Sabre_CalDAV_Notifications_INotificationType $notification
*/
public function __construct(Sabre_CalDAV_Backend_NotificationSupport $caldavBackend, Sabre_CalDAV_Notifications_INotificationType $notification) {
$this->caldavBackend = $caldavBackend;
$this->notification = $notification;
}
/**
* Returns the path name for this notification
*
* @return id
*/
public function getName() {
return $this->notification->getId();
}
/**
* This method must return an xml element, using the
* Sabre_CalDAV_Notifications_INotificationType classes.
*
* @return Sabre_DAVNotification_INotificationType
*/
public function getNotificationType() {
return $this->notification;
}
}

View file

@ -0,0 +1,158 @@
<?php
/**
* SystemStatus notification
*
* This notification can be used to indicate to the user that the system is
* down.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Notifications_Notification_SystemStatus extends Sabre_DAV_Property implements Sabre_CalDAV_Notifications_INotificationType {
const TYPE_LOW = 1;
const TYPE_MEDIUM = 2;
const TYPE_HIGH = 3;
/**
* A unique id
*
* @var string
*/
protected $id;
/**
* The type of alert. This should be one of the TYPE_ constants.
*
* @var int
*/
protected $type;
/**
* A human-readable description of the problem.
*
* @var string
*/
protected $description;
/**
* A url to a website with more information for the user.
*
* @var string
*/
protected $href;
/**
* Creates the notification.
*
* Some kind of unique id should be provided. This is used to generate a
* url.
*
* @param string $id
* @param int $type
* @param string $description
* @param string $href
*/
public function __construct($id, $type = self::TYPE_HIGH, $description = null, $href = null) {
$this->id = $id;
$this->type = $type;
$this->description = $description;
$this->href = $href;
}
/**
* Serializes the notification as a single property.
*
* You should usually just encode the single top-level element of the
* notification.
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serialize(Sabre_DAV_Server $server, \DOMElement $node) {
switch($this->type) {
case self::TYPE_LOW :
$type = 'low';
break;
case self::TYPE_MEDIUM :
$type = 'medium';
break;
default :
case self::TYPE_HIGH :
$type = 'high';
break;
}
$prop = $node->ownerDocument->createElement('cs:systemstatus');
$prop->setAttribute('type', $type);
$node->appendChild($prop);
}
/**
* This method serializes the entire notification, as it is used in the
* response body.
*
* @param Sabre_DAV_Server $server
* @param DOMElement $node
* @return void
*/
public function serializeBody(Sabre_DAV_Server $server, \DOMElement $node) {
switch($this->type) {
case self::TYPE_LOW :
$type = 'low';
break;
case self::TYPE_MEDIUM :
$type = 'medium';
break;
default :
case self::TYPE_HIGH :
$type = 'high';
break;
}
$prop = $node->ownerDocument->createElement('cs:systemstatus');
$prop->setAttribute('type', $type);
if ($this->description) {
$text = $node->ownerDocument->createTextNode($this->description);
$desc = $node->ownerDocument->createElement('cs:description');
$desc->appendChild($text);
$prop->appendChild($desc);
}
if ($this->href) {
$text = $node->ownerDocument->createTextNode($this->href);
$href = $node->ownerDocument->createElement('d:href');
$href->appendChild($text);
$prop->appendChild($href);
}
$node->appendChild($prop);
}
/**
* Returns a unique id for this notification
*
* This is just the base url. This should generally be some kind of unique
* id.
*
* @return string
*/
public function getId() {
return $this->id;
}
}

File diff suppressed because it is too large Load diff

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,105 @@
<?php
use Sabre\VObject;
/**
* 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, 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 addon, 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,303 @@
<?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_BackendInterface
*/
protected $caldavBackend;
/**
* Principal information
*
* @var array
*/
protected $principalInfo;
/**
* Constructor
*
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
* @param Sabre_CalDAV_Backend_BackendInterface $caldavBackend
* @param mixed $userUri
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_BackendInterface $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']);
// We're adding a notifications node, if it's supported by the backend.
if ($this->caldavBackend instanceof Sabre_CalDAV_Backend_NotificationSupport) {
$objs[] = new Sabre_CalDAV_Notifications_Collection($this->caldavBackend, $this->principalInfo['uri']);
}
return $objs;
}
/**
* 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

View file

@ -0,0 +1,312 @@
<?php
/**
* The AddressBook class represents a CardDAV addressbook, owned by a specific user
*
* The AddressBook can contain multiple vcards
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_CardDAV_IAddressBook, Sabre_DAV_IProperties, Sabre_DAVACL_IACL {
/**
* This is an array with addressbook information
*
* @var array
*/
private $addressBookInfo;
/**
* CardDAV backend
*
* @var Sabre_CardDAV_Backend_Abstract
*/
private $carddavBackend;
/**
* Constructor
*
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
* @param array $addressBookInfo
*/
public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo) {
$this->carddavBackend = $carddavBackend;
$this->addressBookInfo = $addressBookInfo;
}
/**
* Returns the name of the addressbook
*
* @return string
*/
public function getName() {
return $this->addressBookInfo['uri'];
}
/**
* Returns a card
*
* @param string $name
* @return Sabre_CardDAV_ICard
*/
public function getChild($name) {
$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name);
if (!$obj) throw new Sabre_DAV_Exception_NotFound('Card not found');
return new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj);
}
/**
* Returns the full list of cards
*
* @return array
*/
public function getChildren() {
$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
$children = array();
foreach($objs as $obj) {
$children[] = new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj);
}
return $children;
}
/**
* Creates a new directory
*
* We actually block this, as subdirectories are not allowed in addressbooks.
*
* @param string $name
* @return void
*/
public function createDirectory($name) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating collections in addressbooks is not allowed');
}
/**
* Creates a new file
*
* The contents of the new file must be a valid VCARD.
*
* This method may return an ETag.
*
* @param string $name
* @param resource $vcardData
* @return string|null
*/
public function createFile($name,$vcardData = null) {
if (is_resource($vcardData)) {
$vcardData = stream_get_contents($vcardData);
}
// Converting to UTF-8, if needed
$vcardData = Sabre_DAV_StringUtil::ensureUTF8($vcardData);
return $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData);
}
/**
* Deletes the entire addressbook.
*
* @return void
*/
public function delete() {
$this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
}
/**
* Renames the addressbook
*
* @param string $newName
* @return void
*/
public function setName($newName) {
throw new Sabre_DAV_Exception_MethodNotAllowed('Renaming addressbooks is not yet supported');
}
/**
* Returns the last modification date as a unix timestamp.
*
* @return void
*/
public function getLastModified() {
return null;
}
/**
* Updates properties on this node,
*
* The properties 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 array $mutations
* @return bool|array
*/
public function updateProperties($mutations) {
return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations);
}
/**
* Returns a list of properties for this nodes.
*
* The properties list is a list of propertynames the client requested,
* encoded in clark-notation {xmlnamespace}tagname
*
* If the array is empty, it means 'all properties' were requested.
*
* @param array $properties
* @return array
*/
public function getProperties($properties) {
$response = array();
foreach($properties as $propertyName) {
if (isset($this->addressBookInfo[$propertyName])) {
$response[$propertyName] = $this->addressBookInfo[$propertyName];
}
}
return $response;
}
/**
* 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->addressBookInfo['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->addressBookInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->addressBookInfo['principaluri'],
'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,219 @@
<?php
/**
* Parses the addressbook-query report request body.
*
* Whoever designed this format, and the CalDAV equivalent even more so,
* has no feel for design.
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_AddressBookQueryParser {
const TEST_ANYOF = 'anyof';
const TEST_ALLOF = 'allof';
/**
* List of requested properties the client wanted
*
* @var array
*/
public $requestedProperties;
/**
* The number of results the client wants
*
* null means it wasn't specified, which in most cases means 'all results'.
*
* @var int|null
*/
public $limit;
/**
* List of property filters.
*
* @var array
*/
public $filters;
/**
* Either TEST_ANYOF or TEST_ALLOF
*
* @var string
*/
public $test;
/**
* 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('card',Sabre_CardDAV_Plugin::NS_CARDDAV);
}
/**
* Parses the request.
*
* @return void
*/
public function parse() {
$filterNode = null;
$limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)');
if (is_nan($limit)) $limit = null;
$filter = $this->xpath->query('/card:addressbook-query/card:filter');
// According to the CardDAV spec there needs to be exactly 1 filter
// element. However, KDE 4.8.2 contains a bug that will encode 0 filter
// elements, so this is a workaround for that.
//
// See: https://bugs.kde.org/show_bug.cgi?id=300047
if ($filter->length === 0) {
$test = null;
$filter = null;
} elseif ($filter->length === 1) {
$filter = $filter->item(0);
$test = $this->xpath->evaluate('string(@test)', $filter);
} else {
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
}
if (!$test) $test = self::TEST_ANYOF;
if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) {
throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"');
}
$propFilters = array();
$propFilterNodes = $this->xpath->query('card:prop-filter', $filter);
for($ii=0; $ii < $propFilterNodes->length; $ii++) {
$propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii));
}
$this->filters = $propFilters;
$this->limit = $limit;
$this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
$this->test = $test;
}
/**
* Parses the prop-filter xml element
*
* @param DOMElement $propFilterNode
* @return array
*/
protected function parsePropFilterNode(DOMElement $propFilterNode) {
$propFilter = array();
$propFilter['name'] = $propFilterNode->getAttribute('name');
$propFilter['test'] = $propFilterNode->getAttribute('test');
if (!$propFilter['test']) $propFilter['test'] = 'anyof';
$propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0;
$paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode);
$propFilter['param-filters'] = array();
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
$propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii));
}
$propFilter['text-matches'] = array();
$textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode);
for($ii=0;$ii<$textMatchNodes->length;$ii++) {
$propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii));
}
return $propFilter;
}
/**
* Parses the param-filter element
*
* @param DOMElement $paramFilterNode
* @return array
*/
public function parseParamFilterNode(DOMElement $paramFilterNode) {
$paramFilter = array();
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
$paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0;
$paramFilter['text-match'] = null;
$textMatch = $this->xpath->query('card:text-match', $paramFilterNode);
if ($textMatch->length>0) {
$paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0));
}
return $paramFilter;
}
/**
* Text match
*
* @param DOMElement $textMatchNode
* @return array
*/
public function parseTextMatchNode(DOMElement $textMatchNode) {
$matchType = $textMatchNode->getAttribute('match-type');
if (!$matchType) $matchType = 'contains';
if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) {
throw new Sabre_DAV_Exception_BadRequest('Unknown match-type: ' . $matchType);
}
$negateCondition = $textMatchNode->getAttribute('negate-condition');
$negateCondition = $negateCondition==='yes';
$collation = $textMatchNode->getAttribute('collation');
if (!$collation) $collation = 'i;unicode-casemap';
return array(
'negate-condition' => $negateCondition,
'collation' => $collation,
'match-type' => $matchType,
'value' => $textMatchNode->nodeValue
);
}
}

View file

@ -0,0 +1,78 @@
<?php
/**
* AddressBook rootnode
*
* This object lists a collection of users, which can contain addressbooks.
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_AddressBookRoot extends Sabre_DAVACL_AbstractPrincipalCollection {
/**
* Principal Backend
*
* @var Sabre_DAVACL_IPrincipalBackend
*/
protected $principalBackend;
/**
* CardDAV backend
*
* @var Sabre_CardDAV_Backend_Abstract
*/
protected $carddavBackend;
/**
* Constructor
*
* This constructor needs both a principal and a carddav backend.
*
* By default this class will show a list of addressbook 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_CardDAV_Backend_Abstract $carddavBackend
* @param string $principalPrefix
*/
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CardDAV_Backend_Abstract $carddavBackend, $principalPrefix = 'principals') {
$this->carddavBackend = $carddavBackend;
parent::__construct($principalBackend, $principalPrefix);
}
/**
* Returns the name of the node
*
* @return string
*/
public function getName() {
return Sabre_CardDAV_Plugin::ADDRESSBOOK_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_CardDAV_UserAddressBooks($this->carddavBackend, $principal['uri']);
}
}

View file

@ -0,0 +1,166 @@
<?php
/**
* Abstract Backend class
*
* This class serves as a base-class for addressbook backends
*
* Note that there are references to 'addressBookId' scattered throughout the
* class. The value of the addressBookId is completely up to you, it can be any
* arbitrary value you can use as an unique identifier.
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_Backend_Abstract {
/**
* Returns the list of addressbooks for a specific user.
*
* Every addressbook should have the following properties:
* id - an arbitrary unique id
* uri - the 'basename' part of the url
* principaluri - Same as the passed parameter
*
* Any additional clark-notation property may be passed besides this. Some
* common ones are :
* {DAV:}displayname
* {urn:ietf:params:xml:ns:carddav}addressbook-description
* {http://calendarserver.org/ns/}getctag
*
* @param string $principalUri
* @return array
*/
public abstract function getAddressBooksForUser($principalUri);
/**
* Updates an addressbook's properties
*
* See Sabre_DAV_IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $addressBookId
* @param array $mutations
* @see Sabre_DAV_IProperties::updateProperties
* @return bool|array
*/
public abstract function updateAddressBook($addressBookId, array $mutations);
/**
* Creates a new address book
*
* @param string $principalUri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return void
*/
abstract public function createAddressBook($principalUri, $url, array $properties);
/**
* Deletes an entire addressbook and all its contents
*
* @param mixed $addressBookId
* @return void
*/
abstract public function deleteAddressBook($addressBookId);
/**
* Returns all cards for a specific addressbook id.
*
* This method should return the following properties for each card:
* * carddata - raw vcard data
* * uri - Some unique url
* * lastmodified - A unix timestamp
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public abstract function getCards($addressbookId);
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array
*/
public abstract function getCard($addressBookId, $cardUri);
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
abstract public function createCard($addressBookId, $cardUri, $cardData);
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
abstract public function updateCard($addressBookId, $cardUri, $cardData);
/**
* Deletes a card
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
abstract public function deleteCard($addressBookId, $cardUri);
}

View file

@ -0,0 +1,330 @@
<?php
/**
* PDO CardDAV backend
*
* This CardDAV backend uses PDO to store addressbooks
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract {
/**
* PDO connection
*
* @var PDO
*/
protected $pdo;
/**
* The PDO table name used to store addressbooks
*/
protected $addressBooksTableName;
/**
* The PDO table name used to store cards
*/
protected $cardsTableName;
/**
* Sets up the object
*
* @param PDO $pdo
* @param string $addressBooksTableName
* @param string $cardsTableName
*/
public function __construct(PDO $pdo, $addressBooksTableName = 'addressbooks', $cardsTableName = 'cards') {
$this->pdo = $pdo;
$this->addressBooksTableName = $addressBooksTableName;
$this->cardsTableName = $cardsTableName;
}
/**
* Returns the list of addressbooks for a specific user.
*
* @param string $principalUri
* @return array
*/
public function getAddressBooksForUser($principalUri) {
$stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM '.$this->addressBooksTableName.' WHERE principaluri = ?');
$stmt->execute(array($principalUri));
$addressBooks = array();
foreach($stmt->fetchAll() as $row) {
$addressBooks[] = array(
'id' => $row['id'],
'uri' => $row['uri'],
'principaluri' => $row['principaluri'],
'{DAV:}displayname' => $row['displayname'],
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
'{http://calendarserver.org/ns/}getctag' => $row['ctag'],
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' =>
new Sabre_CardDAV_Property_SupportedAddressData(),
);
}
return $addressBooks;
}
/**
* Updates an addressbook's properties
*
* See Sabre_DAV_IProperties for a description of the mutations array, as
* well as the return value.
*
* @param mixed $addressBookId
* @param array $mutations
* @see Sabre_DAV_IProperties::updateProperties
* @return bool|array
*/
public function updateAddressBook($addressBookId, array $mutations) {
$updates = array();
foreach($mutations as $property=>$newValue) {
switch($property) {
case '{DAV:}displayname' :
$updates['displayname'] = $newValue;
break;
case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
$updates['description'] = $newValue;
break;
default :
// If any unsupported values were being updated, we must
// let the entire request fail.
return false;
}
}
// No values are being updated?
if (!$updates) {
return false;
}
$query = 'UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 ';
foreach($updates as $key=>$value) {
$query.=', `' . $key . '` = :' . $key . ' ';
}
$query.=' WHERE id = :addressbookid';
$stmt = $this->pdo->prepare($query);
$updates['addressbookid'] = $addressBookId;
$stmt->execute($updates);
return true;
}
/**
* Creates a new address book
*
* @param string $principalUri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return void
*/
public function createAddressBook($principalUri, $url, array $properties) {
$values = array(
'displayname' => null,
'description' => null,
'principaluri' => $principalUri,
'uri' => $url,
);
foreach($properties as $property=>$newValue) {
switch($property) {
case '{DAV:}displayname' :
$values['displayname'] = $newValue;
break;
case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
$values['description'] = $newValue;
break;
default :
throw new Sabre_DAV_Exception_BadRequest('Unknown property: ' . $property);
}
}
$query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)';
$stmt = $this->pdo->prepare($query);
$stmt->execute($values);
}
/**
* Deletes an entire addressbook and all its contents
*
* @param int $addressBookId
* @return void
*/
public function deleteAddressBook($addressBookId) {
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
$stmt->execute(array($addressBookId));
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
$stmt->execute(array($addressBookId));
}
/**
* Returns all cards for a specific addressbook id.
*
* This method should return the following properties for each card:
* * carddata - raw vcard data
* * uri - Some unique url
* * lastmodified - A unix timestamp
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public function getCards($addressbookId) {
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
$stmt->execute(array($addressbookId));
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Returns a specfic card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array
*/
public function getCard($addressBookId, $cardUri) {
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1');
$stmt->execute(array($addressBookId, $cardUri));
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
return (count($result)>0?$result[0]:false);
}
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function createCard($addressBookId, $cardUri, $cardData) {
$stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)');
$result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return '"' . md5($cardData) . '"';
}
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressbooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function updateCard($addressBookId, $cardUri, $cardData) {
$stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?');
$stmt->execute(array($cardData, time(), $cardUri, $addressBookId));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return '"' . md5($cardData) . '"';
}
/**
* Deletes a card
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
public function deleteCard($addressBookId, $cardUri) {
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
$stmt->execute(array($addressBookId, $cardUri));
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
$stmt2->execute(array($addressBookId));
return $stmt->rowCount()===1;
}
}

View file

@ -0,0 +1,250 @@
<?php
/**
* The Card object represents a single Card from an addressbook
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, Sabre_DAVACL_IACL {
/**
* CardDAV backend
*
* @var Sabre_CardDAV_Backend_Abstract
*/
private $carddavBackend;
/**
* Array with information about this Card
*
* @var array
*/
private $cardData;
/**
* Array with information about the containing addressbook
*
* @var array
*/
private $addressBookInfo;
/**
* Constructor
*
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
* @param array $addressBookInfo
* @param array $cardData
*/
public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend,array $addressBookInfo,array $cardData) {
$this->carddavBackend = $carddavBackend;
$this->addressBookInfo = $addressBookInfo;
$this->cardData = $cardData;
}
/**
* Returns the uri for this object
*
* @return string
*/
public function getName() {
return $this->cardData['uri'];
}
/**
* Returns the VCard-formatted object
*
* @return string
*/
public function get() {
// Pre-populating 'carddata' is optional. If we don't yet have it
// already, we fetch it from the backend.
if (!isset($this->cardData['carddata'])) {
$this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
}
return $this->cardData['carddata'];
}
/**
* Updates the VCard-formatted object
*
* @param string $cardData
* @return string|null
*/
public function put($cardData) {
if (is_resource($cardData))
$cardData = stream_get_contents($cardData);
// Converting to UTF-8, if needed
$cardData = Sabre_DAV_StringUtil::ensureUTF8($cardData);
$etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData);
$this->cardData['carddata'] = $cardData;
$this->cardData['etag'] = $etag;
return $etag;
}
/**
* Deletes the card
*
* @return void
*/
public function delete() {
$this->carddavBackend->deleteCard($this->addressBookInfo['id'],$this->cardData['uri']);
}
/**
* Returns the mime content-type
*
* @return string
*/
public function getContentType() {
return 'text/x-vcard; charset=utf-8';
}
/**
* Returns an ETag for this object
*
* @return string
*/
public function getETag() {
if (isset($this->cardData['etag'])) {
return $this->cardData['etag'];
} else {
return '"' . md5($this->get()) . '"';
}
}
/**
* Returns the last modification date as a unix timestamp
*
* @return int
*/
public function getLastModified() {
return isset($this->cardData['lastmodified'])?$this->cardData['lastmodified']:null;
}
/**
* Returns the size of this object in bytes
*
* @return int
*/
public function getSize() {
if (array_key_exists('size', $this->cardData)) {
return $this->cardData['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->addressBookInfo['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->addressBookInfo['principaluri'],
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->addressBookInfo['principaluri'],
'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,18 @@
<?php
/**
* AddressBook interface
*
* Implement this interface to allow a node to be recognized as an addressbook.
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_IAddressBook extends Sabre_DAV_ICollection {
}

View file

@ -0,0 +1,18 @@
<?php
/**
* Card interface
*
* Extend the ICard interface to allow your custom nodes to be picked up as
* 'Cards'.
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_ICard extends Sabre_DAV_IFile {
}

View file

@ -0,0 +1,21 @@
<?php
/**
* IDirectory interface
*
* Implement this interface to have an addressbook marked as a 'directory'. A
* directory is an (often) global addressbook.
*
* A full description can be found in the IETF draft:
* - draft-daboo-carddav-directory-gateway
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_IDirectory extends Sabre_CardDAV_IAddressBook {
}

View file

@ -0,0 +1,696 @@
<?php
use Sabre\VObject;
/**
* CardDAV addon
*
* The CardDAV addon adds CardDAV functionality to the WebDAV server
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* Url to the addressbooks
*/
const ADDRESSBOOK_ROOT = 'addressbooks';
/**
* xml namespace for CardDAV elements
*/
const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
/**
* Add urls to this property to have them automatically exposed as
* 'directories' to the user.
*
* @var array
*/
public $directories = array();
/**
* Server class
*
* @var Sabre_DAV_Server
*/
protected $server;
/**
* Initializes the addon
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
/* Events */
$server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
$server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties'));
$server->subscribeEvent('updateProperties', array($this, 'updateProperties'));
$server->subscribeEvent('report', array($this,'report'));
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
$server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
$server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
$server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
/* Namespaces */
$server->xmlNamespaces[self::NS_CARDDAV] = 'card';
/* Mapping Interfaces to {DAV:}resourcetype values */
$server->resourceTypeMapping['Sabre_CardDAV_IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook';
$server->resourceTypeMapping['Sabre_CardDAV_IDirectory'] = '{' . self::NS_CARDDAV . '}directory';
/* Adding properties that may never be changed */
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set';
$server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set';
$server->propertyMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre_DAV_Property_Href';
$this->server = $server;
}
/**
* Returns a list of supported features.
*
* This is used in the DAV: header in the OPTIONS and PROPFIND requests.
*
* @return array
*/
public function getFeatures() {
return array('addressbook');
}
/**
* Returns a list of reports this addon 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);
if ($node instanceof Sabre_CardDAV_IAddressBook || $node instanceof Sabre_CardDAV_ICard) {
return array(
'{' . self::NS_CARDDAV . '}addressbook-multiget',
'{' . self::NS_CARDDAV . '}addressbook-query',
);
}
return array();
}
/**
* Adds all CardDAV-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, array &$requestedProperties, array &$returnedProperties) {
if ($node instanceof Sabre_DAVACL_IPrincipal) {
// calendar-home-set property
$addHome = '{' . self::NS_CARDDAV . '}addressbook-home-set';
if (in_array($addHome,$requestedProperties)) {
$principalId = $node->getName();
$addressbookHomePath = self::ADDRESSBOOK_ROOT . '/' . $principalId . '/';
unset($requestedProperties[array_search($addHome, $requestedProperties)]);
$returnedProperties[200][$addHome] = new Sabre_DAV_Property_Href($addressbookHomePath);
}
$directories = '{' . self::NS_CARDDAV . '}directory-gateway';
if ($this->directories && in_array($directories, $requestedProperties)) {
unset($requestedProperties[array_search($directories, $requestedProperties)]);
$returnedProperties[200][$directories] = new Sabre_DAV_Property_HrefList($this->directories);
}
}
if ($node instanceof Sabre_CardDAV_ICard) {
// The address-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.
$addressDataProp = '{' . self::NS_CARDDAV . '}address-data';
if (in_array($addressDataProp, $requestedProperties)) {
unset($requestedProperties[$addressDataProp]);
$val = $node->get();
if (is_resource($val))
$val = stream_get_contents($val);
$returnedProperties[200][$addressDataProp] = $val;
}
}
if ($node instanceof Sabre_CardDAV_UserAddressBooks) {
$meCardProp = '{http://calendarserver.org/ns/}me-card';
if (in_array($meCardProp, $requestedProperties)) {
$props = $this->server->getProperties($node->getOwner(), array('{http://sabredav.org/ns}vcard-url'));
if (isset($props['{http://sabredav.org/ns}vcard-url'])) {
$returnedProperties[200][$meCardProp] = new Sabre_DAV_Property_Href(
$props['{http://sabredav.org/ns}vcard-url']
);
$pos = array_search($meCardProp, $requestedProperties);
unset($requestedProperties[$pos]);
}
}
}
}
/**
* This event is triggered when a PROPPATCH method is executed
*
* @param array $mutations
* @param array $result
* @param Sabre_DAV_INode $node
* @return bool
*/
public function updateProperties(&$mutations, &$result, $node) {
if (!$node instanceof Sabre_CardDAV_UserAddressBooks) {
return true;
}
$meCard = '{http://calendarserver.org/ns/}me-card';
// The only property we care about
if (!isset($mutations[$meCard]))
return true;
$value = $mutations[$meCard];
unset($mutations[$meCard]);
if ($value instanceof Sabre_DAV_Property_IHref) {
$value = $value->getHref();
$value = $this->server->calculateUri($value);
} elseif (!is_null($value)) {
$result[400][$meCard] = null;
return false;
}
$innerResult = $this->server->updateProperties(
$node->getOwner(),
array(
'{http://sabredav.org/ns}vcard-url' => $value,
)
);
$closureResult = false;
foreach($innerResult as $status => $props) {
if (is_array($props) && array_key_exists('{http://sabredav.org/ns}vcard-url', $props)) {
$result[$status][$meCard] = null;
$closureResult = ($status>=200 && $status<300);
}
}
return $result;
}
/**
* This functions handles REPORT requests specific to CardDAV
*
* @param string $reportName
* @param DOMNode $dom
* @return bool
*/
public function report($reportName,$dom) {
switch($reportName) {
case '{'.self::NS_CARDDAV.'}addressbook-multiget' :
$this->addressbookMultiGetReport($dom);
return false;
case '{'.self::NS_CARDDAV.'}addressbook-query' :
$this->addressBookQueryReport($dom);
return false;
default :
return;
}
}
/**
* This function handles the addressbook-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 addressbookMultiGetReport($dom) {
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
$hrefElems = $dom->getElementsByTagNameNS('DAV:','href');
$propertyList = array();
foreach($hrefElems as $elem) {
$uri = $this->server->calculateUri($elem->nodeValue);
list($propertyList[]) = $this->server->getPropertiesForPath($uri,$properties);
}
$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 method is triggered before a file gets updated with new content.
*
* This addon uses this method to ensure that Card nodes receive valid
* vcard 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_CardDAV_ICard)
return;
$this->validateVCard($data);
}
/**
* This method is triggered before a new file is created.
*
* This addon uses this method to ensure that Card nodes receive valid
* vcard 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_CardDAV_IAddressBook)
return;
$this->validateVCard($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 validateVCard(&$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 = VObject\Reader::read($data);
} catch (VObject\ParseException $e) {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCARD') {
throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.');
}
if (!isset($vobj->UID)) {
throw new Sabre_DAV_Exception_BadRequest('Every vcard must have an UID.');
}
}
/**
* This function handles the addressbook-query REPORT
*
* This report is used by the client to filter an addressbook based on a
* complex query.
*
* @param DOMNode $dom
* @return void
*/
protected function addressbookQueryReport($dom) {
$query = new Sabre_CardDAV_AddressBookQueryParser($dom);
$query->parse();
$depth = $this->server->getHTTPDepth(0);
if ($depth==0) {
$candidateNodes = array(
$this->server->tree->getNodeForPath($this->server->getRequestUri())
);
} else {
$candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
}
$validNodes = array();
foreach($candidateNodes as $node) {
if (!$node instanceof Sabre_CardDAV_ICard)
continue;
$blob = $node->get();
if (is_resource($blob)) {
$blob = stream_get_contents($blob);
}
if (!$this->validateFilters($blob, $query->filters, $query->test)) {
continue;
}
$validNodes[] = $node;
if ($query->limit && $query->limit <= count($validNodes)) {
// We hit the maximum number of items, we can stop now.
break;
}
}
$result = array();
foreach($validNodes as $validNode) {
if ($depth==0) {
$href = $this->server->getRequestUri();
} else {
$href = $this->server->getRequestUri() . '/' . $validNode->getName();
}
list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0);
}
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($result));
}
/**
* Validates if a vcard makes it throught a list of filters.
*
* @param string $vcardData
* @param array $filters
* @param string $test anyof or allof (which means OR or AND)
* @return bool
*/
public function validateFilters($vcardData, array $filters, $test) {
$vcard = VObject\Reader::read($vcardData);
if (!$filters) return true;
foreach($filters as $filter) {
$isDefined = isset($vcard->{$filter['name']});
if ($filter['is-not-defined']) {
if ($isDefined) {
$success = false;
} else {
$success = true;
}
} elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
// We only need to check for existence
$success = $isDefined;
} else {
$vProperties = $vcard->select($filter['name']);
$results = array();
if ($filter['param-filters']) {
$results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
}
if ($filter['text-matches']) {
$texts = array();
foreach($vProperties as $vProperty)
$texts[] = $vProperty->value;
$results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
}
if (count($results)===1) {
$success = $results[0];
} else {
if ($filter['test'] === 'anyof') {
$success = $results[0] || $results[1];
} else {
$success = $results[0] && $results[1];
}
}
} // else
// There are two conditions where we can already determine whether
// or not this filter succeeds.
if ($test==='anyof' && $success) {
return true;
}
if ($test==='allof' && !$success) {
return false;
}
} // foreach
// If we got all the way here, it means we haven't been able to
// determine early if the test failed or not.
//
// This implies for 'anyof' that the test failed, and for 'allof' that
// we succeeded. Sounds weird, but makes sense.
return $test==='allof';
}
/**
* Validates if a param-filter can be applied to a specific property.
*
* @todo currently we're only validating the first parameter of the passed
* property. Any subsequence parameters with the same name are
* ignored.
* @param array $vProperties
* @param array $filters
* @param string $test
* @return bool
*/
protected function validateParamFilters(array $vProperties, array $filters, $test) {
foreach($filters as $filter) {
$isDefined = false;
foreach($vProperties as $vProperty) {
$isDefined = isset($vProperty[$filter['name']]);
if ($isDefined) break;
}
if ($filter['is-not-defined']) {
if ($isDefined) {
$success = false;
} else {
$success = true;
}
// If there's no text-match, we can just check for existence
} elseif (!$filter['text-match'] || !$isDefined) {
$success = $isDefined;
} else {
$success = false;
foreach($vProperties as $vProperty) {
// If we got all the way here, we'll need to validate the
// text-match filter.
$success = Sabre_DAV_StringUtil::textMatch($vProperty[$filter['name']]->value, $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
if ($success) break;
}
if ($filter['text-match']['negate-condition']) {
$success = !$success;
}
} // else
// There are two conditions where we can already determine whether
// or not this filter succeeds.
if ($test==='anyof' && $success) {
return true;
}
if ($test==='allof' && !$success) {
return false;
}
}
// If we got all the way here, it means we haven't been able to
// determine early if the test failed or not.
//
// This implies for 'anyof' that the test failed, and for 'allof' that
// we succeeded. Sounds weird, but makes sense.
return $test==='allof';
}
/**
* Validates if a text-filter can be applied to a specific property.
*
* @param array $texts
* @param array $filters
* @param string $test
* @return bool
*/
protected function validateTextMatches(array $texts, array $filters, $test) {
foreach($filters as $filter) {
$success = false;
foreach($texts as $haystack) {
$success = Sabre_DAV_StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
// Breaking on the first match
if ($success) break;
}
if ($filter['negate-condition']) {
$success = !$success;
}
if ($success && $test==='anyof')
return true;
if (!$success && $test=='allof')
return false;
}
// If we got all the way here, it means we haven't been able to
// determine early if the test failed or not.
//
// This implies for 'anyof' that the test failed, and for 'allof' that
// we succeeded. Sounds weird, but makes sense.
return $test==='allof';
}
/**
* This event is triggered after webdav-properties have been retrieved.
*
* @return bool
*/
public function afterGetProperties($uri, &$properties) {
// If the request was made using the SOGO connector, we must rewrite
// the content-type property. By default SabreDAV will send back
// text/x-vcard; charset=utf-8, but for SOGO we must strip that last
// part.
if (!isset($properties[200]['{DAV:}getcontenttype']))
return;
if (strpos($this->server->httpRequest->getHeader('User-Agent'),'Thunderbird')===false) {
return;
}
if (strpos($properties[200]['{DAV:}getcontenttype'],'text/x-vcard')===0) {
$properties[200]['{DAV:}getcontenttype'] = 'text/x-vcard';
}
}
/**
* This method is used to generate HTML output for the
* Sabre_DAV_Browser_Plugin. This allows us to generate an interface users
* 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_CardDAV_UserAddressBooks)
return;
$output.= '<tr><td colspan="2"><form method="post" action="">
<h3>Create new address book</h3>
<input type="hidden" name="sabreAction" value="mkaddressbook" />
<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 addon.
*
* @param string $uri
* @param string $action
* @param array $postVars
* @return bool
*/
public function browserPostAction($uri, $action, array $postVars) {
if ($action!=='mkaddressbook')
return;
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:carddav}addressbook');
$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,69 @@
<?php
/**
* Supported-address-data property
*
* This property is a representation of the supported-address-data property
* in the CardDAV namespace.
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_Property_SupportedAddressData extends Sabre_DAV_Property {
/**
* supported versions
*
* @var array
*/
protected $supportedData = array();
/**
* Creates the property
*
* @param array|null $supportedData
*/
public function __construct(array $supportedData = null) {
if (is_null($supportedData)) {
$supportedData = array(
array('contentType' => 'text/vcard', 'version' => '3.0'),
array('contentType' => 'text/vcard', 'version' => '4.0'),
);
}
$this->supportedData = $supportedData;
}
/**
* 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_CardDAV_Plugin::NS_CARDDAV]) ?
$server->xmlNamespaces[Sabre_CardDAV_Plugin::NS_CARDDAV] :
'card';
foreach($this->supportedData as $supported) {
$caldata = $doc->createElementNS(Sabre_CardDAV_Plugin::NS_CARDDAV, $prefix . ':address-data-type');
$caldata->setAttribute('content-type',$supported['contentType']);
$caldata->setAttribute('version',$supported['version']);
$node->appendChild($caldata);
}
}
}

View file

@ -0,0 +1,257 @@
<?php
/**
* UserAddressBooks class
*
* The UserAddressBooks collection contains a list of addressbooks associated with a user
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_UserAddressBooks extends Sabre_DAV_Collection implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
/**
* Principal uri
*
* @var array
*/
protected $principalUri;
/**
* carddavBackend
*
* @var Sabre_CardDAV_Backend_Abstract
*/
protected $carddavBackend;
/**
* Constructor
*
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
* @param string $principalUri
*/
public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, $principalUri) {
$this->carddavBackend = $carddavBackend;
$this->principalUri = $principalUri;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
list(,$name) = Sabre_DAV_URLUtil::splitPath($this->principalUri);
return $name;
}
/**
* Updates the name of this object
*
* @param string $name
* @return void
*/
public function setName($name) {
throw new Sabre_DAV_Exception_MethodNotAllowed();
}
/**
* Deletes this object
*
* @return void
*/
public function delete() {
throw new Sabre_DAV_Exception_MethodNotAllowed();
}
/**
* 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_CardDAV_AddressBook
*/
public function getChild($name) {
foreach($this->getChildren() as $child) {
if ($name==$child->getName())
return $child;
}
throw new Sabre_DAV_Exception_NotFound('Addressbook with name \'' . $name . '\' could not be found');
}
/**
* Returns a list of addressbooks
*
* @return array
*/
public function getChildren() {
$addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri);
$objs = array();
foreach($addressbooks as $addressbook) {
$objs[] = new Sabre_CardDAV_AddressBook($this->carddavBackend, $addressbook);
}
return $objs;
}
/**
* Creates a new addressbook
*
* @param string $name
* @param array $resourceType
* @param array $properties
* @return void
*/
public function createExtendedCollection($name, array $resourceType, array $properties) {
if (!in_array('{'.Sabre_CardDAV_Plugin::NS_CARDDAV.'}addressbook',$resourceType) || count($resourceType)!==2) {
throw new Sabre_DAV_Exception_InvalidResourceType('Unknown resourceType for this collection');
}
$this->carddavBackend->createAddressBook($this->principalUri, $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->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->principalUri,
'protected' => true,
),
array(
'privilege' => '{DAV:}write',
'principal' => $this->principalUri,
'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,26 @@
<?php
/**
* Version Class
*
* This class contains the Sabre_CardDAV version information
*
* @package Sabre
* @subpackage CardDAV
* @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_CardDAV_Version {
/**
* Full version number
*/
const VERSION = '1.7.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'alpha';
}

View file

@ -0,0 +1,32 @@
<?php
/**
* Sabre_CardDAV includes file
*
* Including this file will automatically include all files from the
* Sabre_CardDAV package.
*
* This often allows faster loadtimes, as autoload-speed is often quite slow.
*
* @package Sabre
* @subpackage CardDAV
* @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__ . '/AddressBookQueryParser.php';
include __DIR__ . '/AddressBookRoot.php';
include __DIR__ . '/Backend/Abstract.php';
include __DIR__ . '/Backend/PDO.php';
include __DIR__ . '/IAddressBook.php';
include __DIR__ . '/ICard.php';
include __DIR__ . '/IDirectory.php';
include __DIR__ . '/Plugin.php';
include __DIR__ . '/Property/SupportedAddressData.php';
include __DIR__ . '/UserAddressBooks.php';
include __DIR__ . '/Version.php';
include __DIR__ . '/AddressBook.php';
include __DIR__ . '/Card.php';
// End includes

View file

@ -0,0 +1,83 @@
<?php
/**
* HTTP Basic authentication backend class
*
* This class can be used by authentication objects wishing to use HTTP Basic
* Most of the digest logic is handled, implementors just need to worry about
* the validateUserPass method.
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author James David Low (http://jameslow.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_Auth_Backend_AbstractBasic implements Sabre_DAV_Auth_IBackend {
/**
* This variable holds the currently logged in username.
*
* @var string|null
*/
protected $currentUser;
/**
* Validates a username and password
*
* This method should return true or false depending on if login
* succeeded.
*
* @param string $username
* @param string $password
* @return bool
*/
abstract protected function validateUserPass($username, $password);
/**
* Returns information about the currently logged in username.
*
* If nobody is currently logged in, this method should return null.
*
* @return string|null
*/
public function getCurrentUser() {
return $this->currentUser;
}
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param Sabre_DAV_Server $server
* @param string $realm
* @throws Sabre_DAV_Exception_NotAuthenticated
* @return bool
*/
public function authenticate(Sabre_DAV_Server $server, $realm) {
$auth = new Sabre_HTTP_BasicAuth();
$auth->setHTTPRequest($server->httpRequest);
$auth->setHTTPResponse($server->httpResponse);
$auth->setRealm($realm);
$userpass = $auth->getUserPass();
if (!$userpass) {
$auth->requireLogin();
throw new Sabre_DAV_Exception_NotAuthenticated('No basic authentication headers were found');
}
// Authenticates the user
if (!$this->validateUserPass($userpass[0],$userpass[1])) {
$auth->requireLogin();
throw new Sabre_DAV_Exception_NotAuthenticated('Username or password does not match');
}
$this->currentUser = $userpass[0];
return true;
}
}

View file

@ -0,0 +1,98 @@
<?php
/**
* HTTP Digest authentication backend class
*
* This class can be used by authentication objects wishing to use HTTP Digest
* Most of the digest logic is handled, implementors just need to worry about
* the getDigestHash method
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_Auth_Backend_AbstractDigest implements Sabre_DAV_Auth_IBackend {
/**
* This variable holds the currently logged in username.
*
* @var array|null
*/
protected $currentUser;
/**
* Returns a users digest hash based on the username and realm.
*
* If the user was not known, null must be returned.
*
* @param string $realm
* @param string $username
* @return string|null
*/
abstract public function getDigestHash($realm, $username);
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param Sabre_DAV_Server $server
* @param string $realm
* @throws Sabre_DAV_Exception_NotAuthenticated
* @return bool
*/
public function authenticate(Sabre_DAV_Server $server, $realm) {
$digest = new Sabre_HTTP_DigestAuth();
// Hooking up request and response objects
$digest->setHTTPRequest($server->httpRequest);
$digest->setHTTPResponse($server->httpResponse);
$digest->setRealm($realm);
$digest->init();
$username = $digest->getUsername();
// No username was given
if (!$username) {
$digest->requireLogin();
throw new Sabre_DAV_Exception_NotAuthenticated('No digest authentication headers were found');
}
$hash = $this->getDigestHash($realm, $username);
// If this was false, the user account didn't exist
if ($hash===false || is_null($hash)) {
$digest->requireLogin();
throw new Sabre_DAV_Exception_NotAuthenticated('The supplied username was not on file');
}
if (!is_string($hash)) {
throw new Sabre_DAV_Exception('The returned value from getDigestHash must be a string or null');
}
// If this was false, the password or part of the hash was incorrect.
if (!$digest->validateA1($hash)) {
$digest->requireLogin();
throw new Sabre_DAV_Exception_NotAuthenticated('Incorrect username');
}
$this->currentUser = $username;
return true;
}
/**
* Returns the currently logged in username.
*
* @return string|null
*/
public function getCurrentUser() {
return $this->currentUser;
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* Apache authenticator
*
* This authentication backend assumes that authentication has been
* configured in apache, rather than within SabreDAV.
*
* Make sure apache is properly configured for this to work.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Auth_Backend_Apache implements Sabre_DAV_Auth_IBackend {
/**
* Current apache user
*
* @var string
*/
protected $remoteUser;
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param Sabre_DAV_Server $server
* @param string $realm
* @return bool
*/
public function authenticate(Sabre_DAV_Server $server, $realm) {
$remoteUser = $server->httpRequest->getRawServerValue('REMOTE_USER');
if (is_null($remoteUser)) {
throw new Sabre_DAV_Exception('We did not receive the $_SERVER[REMOTE_USER] property. This means that apache might have been misconfigured');
}
$this->remoteUser = $remoteUser;
return true;
}
/**
* Returns information about the currently logged in user.
*
* If nobody is currently logged in, this method should return null.
*
* @return array|null
*/
public function getCurrentUser() {
return $this->remoteUser;
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* This is an authentication backend that uses a file to manage passwords.
*
* The backend file must conform to Apache's htdigest format
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Auth_Backend_File extends Sabre_DAV_Auth_Backend_AbstractDigest {
/**
* List of users
*
* @var array
*/
protected $users = array();
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file fist.
*
* @param string|null $filename
*/
public function __construct($filename=null) {
if (!is_null($filename))
$this->loadFile($filename);
}
/**
* Loads an htdigest-formatted file. This method can be called multiple times if
* more than 1 file is used.
*
* @param string $filename
* @return void
*/
public function loadFile($filename) {
foreach(file($filename,FILE_IGNORE_NEW_LINES) as $line) {
if (substr_count($line, ":") !== 2)
throw new Sabre_DAV_Exception('Malformed htdigest file. Every line should contain 2 colons');
list($username,$realm,$A1) = explode(':',$line);
if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1))
throw new Sabre_DAV_Exception('Malformed htdigest file. Invalid md5 hash');
$this->users[$realm . ':' . $username] = $A1;
}
}
/**
* Returns a users' information
*
* @param string $realm
* @param string $username
* @return string
*/
public function getDigestHash($realm, $username) {
return isset($this->users[$realm . ':' . $username])?$this->users[$realm . ':' . $username]:false;
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* This is an authentication backend that uses a file to manage passwords.
*
* The backend file must conform to Apache's htdigest format
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Auth_Backend_PDO extends Sabre_DAV_Auth_Backend_AbstractDigest {
/**
* Reference to PDO connection
*
* @var PDO
*/
protected $pdo;
/**
* PDO table name we'll be using
*
* @var string
*/
protected $tableName;
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file fist.
*
* @param PDO $pdo
* @param string $tableName The PDO table name to use
*/
public function __construct(PDO $pdo, $tableName = 'users') {
$this->pdo = $pdo;
$this->tableName = $tableName;
}
/**
* Returns the digest hash for a user.
*
* @param string $realm
* @param string $username
* @return string|null
*/
public function getDigestHash($realm,$username) {
$stmt = $this->pdo->prepare('SELECT username, digesta1 FROM '.$this->tableName.' WHERE username = ?');
$stmt->execute(array($username));
$result = $stmt->fetchAll();
if (!count($result)) return;
return $result[0]['digesta1'];
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* This is the base class for any authentication object.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Auth_IBackend {
/**
* Authenticates the user based on the current request.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
*
* @param Sabre_DAV_Server $server
* @param string $realm
* @return bool
*/
function authenticate(Sabre_DAV_Server $server,$realm);
/**
* Returns information about the currently logged in username.
*
* If nobody is currently logged in, this method should return null.
*
* @return string|null
*/
function getCurrentUser();
}

View file

@ -0,0 +1,111 @@
<?php
/**
* This addon provides Authentication for a WebDAV server.
*
* It relies on a Backend object, which provides user information.
*
* Additionally, it provides support for:
* * {DAV:}current-user-principal property from RFC5397
* * {DAV:}principal-collection-set property from RFC3744
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Auth_Plugin extends Sabre_DAV_ServerPlugin {
/**
* Reference to main server object
*
* @var Sabre_DAV_Server
*/
private $server;
/**
* Authentication backend
*
* @var Sabre_DAV_Auth_IBackend
*/
private $authBackend;
/**
* The authentication realm.
*
* @var string
*/
private $realm;
/**
* __construct
*
* @param Sabre_DAV_Auth_IBackend $authBackend
* @param string $realm
*/
public function __construct(Sabre_DAV_Auth_IBackend $authBackend, $realm) {
$this->authBackend = $authBackend;
$this->realm = $realm;
}
/**
* Initializes the addon. This function is automatically called by the server
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),10);
}
/**
* Returns a addon name.
*
* Using this name other addons will be able to access other addons
* using Sabre_DAV_Server::getPlugin
*
* @return string
*/
public function getPluginName() {
return 'auth';
}
/**
* Returns the current users' principal uri.
*
* If nobody is logged in, this will return null.
*
* @return string|null
*/
public function getCurrentUser() {
$userInfo = $this->authBackend->getCurrentUser();
if (!$userInfo) return null;
return $userInfo;
}
/**
* This method is called before any HTTP method and forces users to be authenticated
*
* @param string $method
* @param string $uri
* @throws Sabre_DAV_Exception_NotAuthenticated
* @return bool
*/
public function beforeMethod($method, $uri) {
$this->authBackend->authenticate($this->server,$this->realm);
}
}

View file

@ -0,0 +1,97 @@
<?php
/**
* GuessContentType addon
*
* A lot of the built-in File objects just return application/octet-stream
* as a content-type by default. This is a problem for some clients, because
* they expect a correct contenttype.
*
* There's really no accurate, fast and portable way to determine the contenttype
* so this extension does what the rest of the world does, and guesses it based
* on the file extension.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Browser_GuessContentType extends Sabre_DAV_ServerPlugin {
/**
* List of recognized file extensions
*
* Feel free to add more
*
* @var array
*/
public $extensionMap = array(
// images
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
// groupware
'ics' => 'text/calendar',
'vcf' => 'text/x-vcard',
// text
'txt' => 'text/plain',
);
/**
* Initializes the addon
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
// Using a relatively low priority (200) to allow other extensions
// to set the content-type first.
$server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'),200);
}
/**
* Handler for teh afterGetProperties event
*
* @param string $path
* @param array $properties
* @return void
*/
public function afterGetProperties($path, &$properties) {
if (array_key_exists('{DAV:}getcontenttype', $properties[404])) {
list(, $fileName) = Sabre_DAV_URLUtil::splitPath($path);
$contentType = $this->getContentType($fileName);
if ($contentType) {
$properties[200]['{DAV:}getcontenttype'] = $contentType;
unset($properties[404]['{DAV:}getcontenttype']);
}
}
}
/**
* Simple method to return the contenttype
*
* @param string $fileName
* @return string
*/
protected function getContentType($fileName) {
// Just grabbing the extension
$extension = strtolower(substr($fileName,strrpos($fileName,'.')+1));
if (isset($this->extensionMap[$extension]))
return $this->extensionMap[$extension];
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* This is a simple addon that will map any GET request for non-files to
* PROPFIND allprops-requests.
*
* This should allow easy debugging of PROPFIND
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Browser_MapGetToPropFind extends Sabre_DAV_ServerPlugin {
/**
* reference to server class
*
* @var Sabre_DAV_Server
*/
protected $server;
/**
* Initializes the addon and subscribes to events
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor'));
}
/**
* This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request
*
* @param string $method
* @param string $uri
* @return bool
*/
public function httpGetInterceptor($method, $uri) {
if ($method!='GET') return true;
$node = $this->server->tree->getNodeForPath($uri);
if ($node instanceof Sabre_DAV_IFile) return;
$this->server->invokeMethod('PROPFIND',$uri);
return false;
}
}

View file

@ -0,0 +1,489 @@
<?php
/**
* Browser Plugin
*
* This addon provides a html representation, so that a WebDAV server may be accessed
* using a browser.
*
* The class intercepts GET requests to collection resources and generates a simple
* html index.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin {
/**
* List of default icons for nodes.
*
* This is an array with class / interface names as keys, and asset names
* as values.
*
* The evaluation order is reversed. The last item in the list gets
* precendence.
*
* @var array
*/
public $iconMap = array(
'Sabre_DAV_IFile' => 'icons/file',
'Sabre_DAV_ICollection' => 'icons/collection',
'Sabre_DAVACL_IPrincipal' => 'icons/principal',
'Sabre_CalDAV_ICalendar' => 'icons/calendar',
'Sabre_CardDAV_IAddressBook' => 'icons/addressbook',
'Sabre_CardDAV_ICard' => 'icons/card',
);
/**
* The file extension used for all icons
*
* @var string
*/
public $iconExtension = '.png';
/**
* reference to server class
*
* @var Sabre_DAV_Server
*/
protected $server;
/**
* enablePost turns on the 'actions' panel, which allows people to create
* folders and upload files straight from a browser.
*
* @var bool
*/
protected $enablePost = true;
/**
* By default the browser addon will generate a favicon and other images.
* To turn this off, set this property to false.
*
* @var bool
*/
protected $enableAssets = true;
/**
* Creates the object.
*
* By default it will allow file creation and uploads.
* Specify the first argument as false to disable this
*
* @param bool $enablePost
* @param bool $enableAssets
*/
public function __construct($enablePost=true, $enableAssets = true) {
$this->enablePost = $enablePost;
$this->enableAssets = $enableAssets;
}
/**
* Initializes the addon and subscribes to events
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
$this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor'));
$this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200);
if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler'));
}
/**
* This method intercepts GET requests to collections and returns the html
*
* @param string $method
* @param string $uri
* @return bool
*/
public function httpGetInterceptor($method, $uri) {
if ($method !== 'GET') return true;
// We're not using straight-up $_GET, because we want everything to be
// unit testable.
$getVars = array();
parse_str($this->server->httpRequest->getQueryString(), $getVars);
if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) {
$this->serveAsset($getVars['assetName']);
return false;
}
try {
$node = $this->server->tree->getNodeForPath($uri);
} catch (Sabre_DAV_Exception_NotFound $e) {
// We're simply stopping when the file isn't found to not interfere
// with other addons.
return;
}
if ($node instanceof Sabre_DAV_IFile)
return;
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8');
$this->server->httpResponse->sendBody(
$this->generateDirectoryIndex($uri)
);
return false;
}
/**
* Handles POST requests for tree operations.
*
* @param string $method
* @param string $uri
* @return bool
*/
public function httpPOSTHandler($method, $uri) {
if ($method!='POST') return;
$contentType = $this->server->httpRequest->getHeader('Content-Type');
list($contentType) = explode(';', $contentType);
if ($contentType !== 'application/x-www-form-urlencoded' &&
$contentType !== 'multipart/form-data') {
return;
}
$postVars = $this->server->httpRequest->getPostVars();
if (!isset($postVars['sabreAction']))
return;
if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) {
switch($postVars['sabreAction']) {
case 'mkcol' :
if (isset($postVars['name']) && trim($postVars['name'])) {
// Using basename() because we won't allow slashes
list(, $folderName) = Sabre_DAV_URLUtil::splitPath(trim($postVars['name']));
$this->server->createDirectory($uri . '/' . $folderName);
}
break;
case 'put' :
if ($_FILES) $file = current($_FILES);
else break;
list(, $newName) = Sabre_DAV_URLUtil::splitPath(trim($file['name']));
if (isset($postVars['name']) && trim($postVars['name']))
$newName = trim($postVars['name']);
// Making sure we only have a 'basename' component
list(, $newName) = Sabre_DAV_URLUtil::splitPath($newName);
if (is_uploaded_file($file['tmp_name'])) {
$this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r'));
}
break;
}
}
$this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri());
$this->server->httpResponse->sendStatus(302);
return false;
}
/**
* Escapes a string for html.
*
* @param string $value
* @return string
*/
public function escapeHTML($value) {
return htmlspecialchars($value,ENT_QUOTES,'UTF-8');
}
/**
* Generates the html directory index for a given url
*
* @param string $path
* @return string
*/
public function generateDirectoryIndex($path) {
$version = '';
if (Sabre_DAV_Server::$exposeVersion) {
$version = Sabre_DAV_Version::VERSION ."-". Sabre_DAV_Version::STABILITY;
}
$html = "<html>
<head>
<title>Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . "</title>
<style type=\"text/css\">
body { Font-family: arial}
h1 { font-size: 150% }
</style>
";
if ($this->enableAssets) {
$html.='<link rel="shortcut icon" href="'.$this->getAssetUrl('favicon.ico').'" type="image/vnd.microsoft.icon" />';
}
$html .= "</head>
<body>
<h1>Index for " . $this->escapeHTML($path) . "/</h1>
<table>
<tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr>
<tr><td colspan=\"5\"><hr /></td></tr>";
$files = $this->server->getPropertiesForPath($path,array(
'{DAV:}displayname',
'{DAV:}resourcetype',
'{DAV:}getcontenttype',
'{DAV:}getcontentlength',
'{DAV:}getlastmodified',
),1);
$parent = $this->server->tree->getNodeForPath($path);
if ($path) {
list($parentUri) = Sabre_DAV_URLUtil::splitPath($path);
$fullPath = Sabre_DAV_URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
$icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':'';
$html.= "<tr>
<td>$icon</td>
<td><a href=\"{$fullPath}\">..</a></td>
<td>[parent]</td>
<td></td>
<td></td>
</tr>";
}
foreach($files as $file) {
// This is the current directory, we can skip it
if (rtrim($file['href'],'/')==$path) continue;
list(, $name) = Sabre_DAV_URLUtil::splitPath($file['href']);
$type = null;
if (isset($file[200]['{DAV:}resourcetype'])) {
$type = $file[200]['{DAV:}resourcetype']->getValue();
// resourcetype can have multiple values
if (!is_array($type)) $type = array($type);
foreach($type as $k=>$v) {
// Some name mapping is preferred
switch($v) {
case '{DAV:}collection' :
$type[$k] = 'Collection';
break;
case '{DAV:}principal' :
$type[$k] = 'Principal';
break;
case '{urn:ietf:params:xml:ns:carddav}addressbook' :
$type[$k] = 'Addressbook';
break;
case '{urn:ietf:params:xml:ns:caldav}calendar' :
$type[$k] = 'Calendar';
break;
case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' :
$type[$k] = 'Schedule Inbox';
break;
case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' :
$type[$k] = 'Schedule Outbox';
break;
case '{http://calendarserver.org/ns/}calendar-proxy-read' :
$type[$k] = 'Proxy-Read';
break;
case '{http://calendarserver.org/ns/}calendar-proxy-write' :
$type[$k] = 'Proxy-Write';
break;
}
}
$type = implode(', ', $type);
}
// If no resourcetype was found, we attempt to use
// the contenttype property
if (!$type && isset($file[200]['{DAV:}getcontenttype'])) {
$type = $file[200]['{DAV:}getcontenttype'];
}
if (!$type) $type = 'Unknown';
$size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:'';
$lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(DateTime::ATOM):'';
$fullPath = Sabre_DAV_URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/'));
$displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name;
$displayName = $this->escapeHTML($displayName);
$type = $this->escapeHTML($type);
$icon = '';
if ($this->enableAssets) {
$node = $parent->getChild($name);
foreach(array_reverse($this->iconMap) as $class=>$iconName) {
if ($node instanceof $class) {
$icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>';
break;
}
}
}
$html.= "<tr>
<td>$icon</td>
<td><a href=\"{$fullPath}\">{$displayName}</a></td>
<td>{$type}</td>
<td>{$size}</td>
<td>{$lastmodified}</td>
</tr>";
}
$html.= "<tr><td colspan=\"5\"><hr /></td></tr>";
$output = '';
if ($this->enablePost) {
$this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output));
}
$html.=$output;
$html.= "</table>
<address>Generated by SabreDAV " . $version . " (c)2007-2012 <a href=\"http://code.google.com/p/sabredav/\">http://code.google.com/p/sabredav/</a></address>
</body>
</html>";
return $html;
}
/**
* This method is used to generate the 'actions panel' output for
* collections.
*
* This specifically generates the interfaces for creating new files, and
* creating new directories.
*
* @param Sabre_DAV_INode $node
* @param mixed $output
* @return void
*/
public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
if (!$node instanceof Sabre_DAV_ICollection)
return;
// We also know fairly certain that if an object is a non-extended
// SimpleCollection, we won't need to show the panel either.
if (get_class($node)==='Sabre_DAV_SimpleCollection')
return;
$output.= '<tr><td colspan="2"><form method="post" action="">
<h3>Create new folder</h3>
<input type="hidden" name="sabreAction" value="mkcol" />
Name: <input type="text" name="name" /><br />
<input type="submit" value="create" />
</form>
<form method="post" action="" enctype="multipart/form-data">
<h3>Upload file</h3>
<input type="hidden" name="sabreAction" value="put" />
Name (optional): <input type="text" name="name" /><br />
File: <input type="file" name="file" /><br />
<input type="submit" value="upload" />
</form>
</td></tr>';
}
/**
* This method takes a path/name of an asset and turns it into url
* suiteable for http access.
*
* @param string $assetName
* @return string
*/
protected function getAssetUrl($assetName) {
return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
}
/**
* This method returns a local pathname to an asset.
*
* @param string $assetName
* @return string
*/
protected function getLocalAssetPath($assetName) {
// Making sure people aren't trying to escape from the base path.
$assetSplit = explode('/', $assetName);
if (in_array('..',$assetSplit)) {
throw new Sabre_DAV_Exception('Incorrect asset path');
}
$path = __DIR__ . '/assets/' . $assetName;
return $path;
}
/**
* This method reads an asset from disk and generates a full http response.
*
* @param string $assetName
* @return void
*/
protected function serveAsset($assetName) {
$assetPath = $this->getLocalAssetPath($assetName);
if (!file_exists($assetPath)) {
throw new Sabre_DAV_Exception_NotFound('Could not find an asset with this name');
}
// Rudimentary mime type detection
switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) {
case 'ico' :
$mime = 'image/vnd.microsoft.icon';
break;
case 'png' :
$mime = 'image/png';
break;
default:
$mime = 'application/octet-stream';
break;
}
$this->server->httpResponse->setHeader('Content-Type', $mime);
$this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
$this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
$this->server->httpResponse->sendStatus(200);
$this->server->httpResponse->sendBody(fopen($assetPath,'r'));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,522 @@
<?php
/**
* SabreDAV DAV client
*
* This client wraps around Curl to provide a convenient API to a WebDAV
* server.
*
* NOTE: This class is experimental, it's api will likely change in the future.
*
* @package Sabre
* @subpackage DAVClient
* @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_DAV_Client {
/**
* The propertyMap is a key-value array.
*
* If you use the propertyMap, any {DAV:}multistatus responses with the
* proeprties listed in this array, will automatically be mapped to a
* respective class.
*
* The {DAV:}resourcetype property is automatically added. This maps to
* Sabre_DAV_Property_ResourceType
*
* @var array
*/
public $propertyMap = array();
protected $baseUri;
protected $userName;
protected $password;
protected $proxy;
/**
* Basic authentication
*/
const AUTH_BASIC = 1;
/**
* Digest authentication
*/
const AUTH_DIGEST = 2;
/**
* The authentication type we're using.
*
* This is a bitmask of AUTH_BASIC and AUTH_DIGEST.
*
* If DIGEST is used, the client makes 1 extra request per request, to get
* the authentication tokens.
*
* @var int
*/
protected $authType;
/**
* Constructor
*
* Settings are provided through the 'settings' argument. The following
* settings are supported:
*
* * baseUri
* * userName (optional)
* * password (optional)
* * proxy (optional)
*
* @param array $settings
*/
public function __construct(array $settings) {
if (!isset($settings['baseUri'])) {
throw new InvalidArgumentException('A baseUri must be provided');
}
$validSettings = array(
'baseUri',
'userName',
'password',
'proxy',
);
foreach($validSettings as $validSetting) {
if (isset($settings[$validSetting])) {
$this->$validSetting = $settings[$validSetting];
}
}
if (isset($settings['authType'])) {
$this->authType = $settings['authType'];
} else {
$this->authType = self::AUTH_BASIC | self::AUTH_DIGEST;
}
$this->propertyMap['{DAV:}resourcetype'] = 'Sabre_DAV_Property_ResourceType';
}
/**
* Does a PROPFIND request
*
* The list of requested properties must be specified as an array, in clark
* notation.
*
* The returned array will contain a list of filenames as keys, and
* properties as values.
*
* The properties array will contain the list of properties. Only properties
* that are actually returned from the server (without error) will be
* returned, anything else is discarded.
*
* Depth should be either 0 or 1. A depth of 1 will cause a request to be
* made to the server to also return all child resources.
*
* @param string $url
* @param array $properties
* @param int $depth
* @return array
*/
public function propFind($url, array $properties, $depth = 0) {
$body = '<?xml version="1.0"?>' . "\n";
$body.= '<d:propfind xmlns:d="DAV:">' . "\n";
$body.= ' <d:prop>' . "\n";
foreach($properties as $property) {
list(
$namespace,
$elementName
) = Sabre_DAV_XMLUtil::parseClarkNotation($property);
if ($namespace === 'DAV:') {
$body.=' <d:' . $elementName . ' />' . "\n";
} else {
$body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\"/>\n";
}
}
$body.= ' </d:prop>' . "\n";
$body.= '</d:propfind>';
$response = $this->request('PROPFIND', $url, $body, array(
'Depth' => $depth,
'Content-Type' => 'application/xml'
));
$result = $this->parseMultiStatus($response['body']);
// If depth was 0, we only return the top item
if ($depth===0) {
reset($result);
$result = current($result);
return $result[200];
}
$newResult = array();
foreach($result as $href => $statusList) {
$newResult[$href] = $statusList[200];
}
return $newResult;
}
/**
* Updates a list of properties on the server
*
* The list of properties must have clark-notation properties for the keys,
* and the actual (string) value for the value. If the value is null, an
* attempt is made to delete the property.
*
* @todo Must be building the request using the DOM, and does not yet
* support complex properties.
* @param string $url
* @param array $properties
* @return void
*/
public function propPatch($url, array $properties) {
$body = '<?xml version="1.0"?>' . "\n";
$body.= '<d:propertyupdate xmlns:d="DAV:">' . "\n";
foreach($properties as $propName => $propValue) {
list(
$namespace,
$elementName
) = Sabre_DAV_XMLUtil::parseClarkNotation($propName);
if ($propValue === null) {
$body.="<d:remove><d:prop>\n";
if ($namespace === 'DAV:') {
$body.=' <d:' . $elementName . ' />' . "\n";
} else {
$body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\"/>\n";
}
$body.="</d:prop></d:remove>\n";
} else {
$body.="<d:set><d:prop>\n";
if ($namespace === 'DAV:') {
$body.=' <d:' . $elementName . '>';
} else {
$body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\">";
}
// Shitty.. i know
$body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8');
if ($namespace === 'DAV:') {
$body.='</d:' . $elementName . '>' . "\n";
} else {
$body.="</x:" . $elementName . ">\n";
}
$body.="</d:prop></d:set>\n";
}
}
$body.= '</d:propertyupdate>';
$this->request('PROPPATCH', $url, $body, array(
'Content-Type' => 'application/xml'
));
}
/**
* Performs an HTTP options request
*
* This method returns all the features from the 'DAV:' header as an array.
* If there was no DAV header, or no contents this method will return an
* empty array.
*
* @return array
*/
public function options() {
$result = $this->request('OPTIONS');
if (!isset($result['headers']['dav'])) {
return array();
}
$features = explode(',', $result['headers']['dav']);
foreach($features as &$v) {
$v = trim($v);
}
return $features;
}
/**
* Performs an actual HTTP request, and returns the result.
*
* If the specified url is relative, it will be expanded based on the base
* url.
*
* The returned array contains 3 keys:
* * body - the response body
* * httpCode - a HTTP code (200, 404, etc)
* * headers - a list of response http headers. The header names have
* been lowercased.
*
* @param string $method
* @param string $url
* @param string $body
* @param array $headers
* @return array
*/
public function request($method, $url = '', $body = null, $headers = array()) {
$url = $this->getAbsoluteUrl($url);
$curlSettings = array(
CURLOPT_RETURNTRANSFER => true,
// Return headers as part of the response
CURLOPT_HEADER => true,
CURLOPT_POSTFIELDS => $body,
// Automatically follow redirects
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
);
switch ($method) {
case 'HEAD' :
// do not read body with HEAD requests (this is neccessary because cURL does not ignore the body with HEAD
// requests when the Content-Length header is given - which in turn is perfectly valid according to HTTP
// specs...) cURL does unfortunately return an error in this case ("transfer closed transfer closed with
// ... bytes remaining to read") this can be circumvented by explicitly telling cURL to ignore the
// response body
$curlSettings[CURLOPT_NOBODY] = true;
$curlSettings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
break;
default:
$curlSettings[CURLOPT_CUSTOMREQUEST] = $method;
break;
}
// Adding HTTP headers
$nHeaders = array();
foreach($headers as $key=>$value) {
$nHeaders[] = $key . ': ' . $value;
}
$curlSettings[CURLOPT_HTTPHEADER] = $nHeaders;
if ($this->proxy) {
$curlSettings[CURLOPT_PROXY] = $this->proxy;
}
if ($this->userName && $this->authType) {
$curlType = 0;
if ($this->authType & self::AUTH_BASIC) {
$curlType |= CURLAUTH_BASIC;
}
if ($this->authType & self::AUTH_DIGEST) {
$curlType |= CURLAUTH_DIGEST;
}
$curlSettings[CURLOPT_HTTPAUTH] = $curlType;
$curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password;
}
list(
$response,
$curlInfo,
$curlErrNo,
$curlError
) = $this->curlRequest($url, $curlSettings);
$headerBlob = substr($response, 0, $curlInfo['header_size']);
$response = substr($response, $curlInfo['header_size']);
// In the case of 100 Continue, or redirects we'll have multiple lists
// of headers for each separate HTTP response. We can easily split this
// because they are separated by \r\n\r\n
$headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));
// We only care about the last set of headers
$headerBlob = $headerBlob[count($headerBlob)-1];
// Splitting headers
$headerBlob = explode("\r\n", $headerBlob);
$headers = array();
foreach($headerBlob as $header) {
$parts = explode(':', $header, 2);
if (count($parts)==2) {
$headers[strtolower(trim($parts[0]))] = trim($parts[1]);
}
}
$response = array(
'body' => $response,
'statusCode' => $curlInfo['http_code'],
'headers' => $headers
);
if ($curlErrNo) {
throw new Sabre_DAV_Exception('[CURL] Error while making request: ' . $curlError . ' (error code: ' . $curlErrNo . ')');
}
if ($response['statusCode']>=400) {
switch ($response['statusCode']) {
case 400 :
throw new Sabre_DAV_Exception_BadRequest('Bad request');
case 401 :
throw new Sabre_DAV_Exception_NotAuthenticated('Not authenticated');
case 402 :
throw new Sabre_DAV_Exception_PaymentRequired('Payment required');
case 403 :
throw new Sabre_DAV_Exception_Forbidden('Forbidden');
case 404:
throw new Sabre_DAV_Exception_NotFound('Resource not found.');
case 405 :
throw new Sabre_DAV_Exception_MethodNotAllowed('Method not allowed');
case 409 :
throw new Sabre_DAV_Exception_Conflict('Conflict');
case 412 :
throw new Sabre_DAV_Exception_PreconditionFailed('Precondition failed');
case 416 :
throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('Requested Range Not Satisfiable');
case 500 :
throw new Sabre_DAV_Exception('Internal server error');
case 501 :
throw new Sabre_DAV_Exception_NotImplemented('Not Implemeneted');
case 507 :
throw new Sabre_DAV_Exception_InsufficientStorage('Insufficient storage');
default:
throw new Sabre_DAV_Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')');
}
}
return $response;
}
/**
* Wrapper for all curl functions.
*
* The only reason this was split out in a separate method, is so it
* becomes easier to unittest.
*
* @param string $url
* @param array $settings
* @return array
*/
protected function curlRequest($url, $settings) {
$curl = curl_init($url);
curl_setopt_array($curl, $settings);
return array(
curl_exec($curl),
curl_getinfo($curl),
curl_errno($curl),
curl_error($curl)
);
}
/**
* Returns the full url based on the given url (which may be relative). All
* urls are expanded based on the base url as given by the server.
*
* @param string $url
* @return string
*/
protected function getAbsoluteUrl($url) {
// If the url starts with http:// or https://, the url is already absolute.
if (preg_match('/^http(s?):\/\//', $url)) {
return $url;
}
// If the url starts with a slash, we must calculate the url based off
// the root of the base url.
if (strpos($url,'/') === 0) {
$parts = parse_url($this->baseUri);
return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url;
}
// Otherwise...
return $this->baseUri . $url;
}
/**
* Parses a WebDAV multistatus response body
*
* This method returns an array with the following structure
*
* array(
* 'url/to/resource' => array(
* '200' => array(
* '{DAV:}property1' => 'value1',
* '{DAV:}property2' => 'value2',
* ),
* '404' => array(
* '{DAV:}property1' => null,
* '{DAV:}property2' => null,
* ),
* )
* 'url/to/resource2' => array(
* .. etc ..
* )
* )
*
*
* @param string $body xml body
* @return array
*/
public function parseMultiStatus($body) {
$responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA);
if ($responseXML===false) {
throw new InvalidArgumentException('The passed data is not valid XML');
}
$responseXML->registerXPathNamespace('d', 'DAV:');
$propResult = array();
foreach($responseXML->xpath('d:response') as $response) {
$response->registerXPathNamespace('d', 'DAV:');
$href = $response->xpath('d:href');
$href = (string)$href[0];
$properties = array();
foreach($response->xpath('d:propstat') as $propStat) {
$propStat->registerXPathNamespace('d', 'DAV:');
$status = $propStat->xpath('d:status');
list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3);
$properties[$statusCode] = Sabre_DAV_XMLUtil::parseProperties(dom_import_simplexml($propStat), $this->propertyMap);
}
$propResult[$href] = $properties;
}
return $propResult;
}
}

View file

@ -0,0 +1,110 @@
<?php
/**
* Collection class
*
* This is a helper class, that should aid in getting collections classes setup.
* Most of its methods are implemented, and throw permission denied exceptions
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_Collection extends Sabre_DAV_Node implements Sabre_DAV_ICollection {
/**
* Returns a child object, by its name.
*
* This method makes use of the getChildren method to grab all the child
* nodes, and compares the name.
* Generally its wise to override this, as this can usually be optimized
*
* This method must throw Sabre_DAV_Exception_NotFound if the node does not
* exist.
*
* @param string $name
* @throws Sabre_DAV_Exception_NotFound
* @return Sabre_DAV_INode
*/
public function getChild($name) {
foreach($this->getChildren() as $child) {
if ($child->getName()==$name) return $child;
}
throw new Sabre_DAV_Exception_NotFound('File not found: ' . $name);
}
/**
* Checks is a child-node exists.
*
* It is generally a good idea to try and override this. Usually it can be optimized.
*
* @param string $name
* @return bool
*/
public function childExists($name) {
try {
$this->getChild($name);
return true;
} catch(Sabre_DAV_Exception_NotFound $e) {
return false;
}
}
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After succesful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
*/
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');
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* SabreDAV base exception
*
* This is SabreDAV's base exception file, use this to implement your own exception.
*
* @package Sabre
* @subpackage DAV
* @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
*/
/**
* Main Exception class.
*
* This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occurred.
* The default for this is 500.
*
* This class also allows you to generate custom xml data for your exceptions. This will be displayed
* in the 'error' element in the failing response.
*/
class Sabre_DAV_Exception extends Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 500;
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
}
/**
* This method allows the exception to return any extra HTTP response headers.
*
* The headers must be returned as an array.
*
* @param Sabre_DAV_Server $server
* @return array
*/
public function getHTTPHeaders(Sabre_DAV_Server $server) {
return array();
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* BadRequest
*
* The BadRequest is thrown when the user submitted an invalid HTTP request
* BadRequest
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_BadRequest extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 400;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* Conflict
*
* A 409 Conflict is thrown when a user tried to make a directory over an existing
* file or in a parent directory that doesn't exist.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_Conflict extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 409;
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* ConflictingLock
*
* Similar to Exception_Locked, this exception thrown when a LOCK request
* was made, on a resource which was already locked
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_ConflictingLock extends Sabre_DAV_Exception_Locked {
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
if ($this->lock) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:no-conflicting-lock');
$errorNode->appendChild($error);
if (!is_object($this->lock)) var_dump($this->lock);
$error->appendChild($errorNode->ownerDocument->createElementNS('DAV:','d:href',$this->lock->uri));
}
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* FileNotFound
*
* Deprecated: Warning, this class is deprecated and will be removed in a
* future version of SabreDAV. Please use Sabre_DAV_Exception_NotFound instead.
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @deprecated Use Sabre_DAV_Exception_NotFound instead
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_DAV_Exception_FileNotFound extends Sabre_DAV_Exception_NotFound {
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Forbidden
*
* This exception is thrown whenever a user tries to do an operation he's not allowed to
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_Forbidden extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 403;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* InsufficientStorage
*
* This Exception can be thrown, when for example a harddisk is full or a quota is exceeded
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_InsufficientStorage extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 507;
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* InvalidResourceType
*
* This exception is thrown when the user tried to create a new collection, with
* a special resourcetype value that was not recognized by the server.
*
* See RFC5689 section 3.3
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_InvalidResourceType extends Sabre_DAV_Exception_Forbidden {
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:valid-resourcetype');
$errorNode->appendChild($error);
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* LockTokenMatchesRequestUri
*
* This exception is thrown by UNLOCK if a supplied lock-token is invalid
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_LockTokenMatchesRequestUri extends Sabre_DAV_Exception_Conflict {
/**
* Creates the exception
*/
public function __construct() {
$this->message = 'The locktoken supplied does not match any locks on this entity';
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-matches-request-uri');
$errorNode->appendChild($error);
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* Locked
*
* The 423 is thrown when a client tried to access a resource that was locked, without supplying a valid lock token
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_Locked extends Sabre_DAV_Exception {
/**
* Lock information
*
* @var Sabre_DAV_Locks_LockInfo
*/
protected $lock;
/**
* Creates the exception
*
* A LockInfo object should be passed if the user should be informed
* which lock actually has the file locked.
*
* @param Sabre_DAV_Locks_LockInfo $lock
*/
public function __construct(Sabre_DAV_Locks_LockInfo $lock = null) {
$this->lock = $lock;
}
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 423;
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
if ($this->lock) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-submitted');
$errorNode->appendChild($error);
if (!is_object($this->lock)) var_dump($this->lock);
$error->appendChild($errorNode->ownerDocument->createElementNS('DAV:','d:href',$this->lock->uri));
}
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* MethodNotAllowed
*
* The 405 is thrown when a client tried to create a directory on an already existing directory
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_MethodNotAllowed extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 405;
}
/**
* This method allows the exception to return any extra HTTP response headers.
*
* The headers must be returned as an array.
*
* @param Sabre_DAV_Server $server
* @return array
*/
public function getHTTPHeaders(Sabre_DAV_Server $server) {
$methods = $server->getAllowedMethods($server->getRequestUri());
return array(
'Allow' => strtoupper(implode(', ',$methods)),
);
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* NotAuthenticated
*
* This exception is thrown when the client did not provide valid
* authentication credentials.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_NotAuthenticated extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 401;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* NotFound
*
* This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_NotFound extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 404;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* NotImplemented
*
* This exception is thrown when the client tried to call an unsupported HTTP method or other feature
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_NotImplemented extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 501;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* Payment Required
*
* The PaymentRequired exception may be thrown in a case where a user must pay
* to access a certain resource or operation.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_PaymentRequired extends Sabre_DAV_Exception {
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 402;
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* PreconditionFailed
*
* This exception is normally thrown when a client submitted a conditional request,
* like for example an If, If-None-Match or If-Match header, which caused the HTTP
* request to not execute (the condition of the header failed)
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_PreconditionFailed extends Sabre_DAV_Exception {
/**
* When this exception is thrown, the header-name might be set.
*
* This allows the exception-catching code to determine which HTTP header
* caused the exception.
*
* @var string
*/
public $header = null;
/**
* Create the exception
*
* @param string $message
* @param string $header
*/
public function __construct($message, $header=null) {
parent::__construct($message);
$this->header = $header;
}
/**
* Returns the HTTP statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 412;
}
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
if ($this->header) {
$prop = $errorNode->ownerDocument->createElement('s:header');
$prop->nodeValue = $this->header;
$errorNode->appendChild($prop);
}
}
}

View file

@ -0,0 +1,30 @@
<?php
/**
* ReportNotImplemented
*
* This exception is thrown when the client requested an unknown report through the REPORT method
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_ReportNotImplemented extends Sabre_DAV_Exception_NotImplemented {
/**
* This method allows the exception to include additional information into the WebDAV error response
*
* @param Sabre_DAV_Server $server
* @param DOMElement $errorNode
* @return void
*/
public function serialize(Sabre_DAV_Server $server,DOMElement $errorNode) {
$error = $errorNode->ownerDocument->createElementNS('DAV:','d:supported-report');
$errorNode->appendChild($error);
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* RequestedRangeNotSatisfiable
*
* This exception is normally thrown when the user
* request a range that is out of the entity bounds.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_RequestedRangeNotSatisfiable extends Sabre_DAV_Exception {
/**
* returns the http statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 416;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* UnSupportedMediaType
*
* The 415 Unsupported Media Type status code is generally sent back when the client
* tried to call an HTTP method, with a body the server didn't understand
*
* @package Sabre
* @subpackage DAV
* @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_DAV_Exception_UnsupportedMediaType extends Sabre_DAV_Exception {
/**
* returns the http statuscode for this exception
*
* @return int
*/
public function getHTTPCode() {
return 415;
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* Directory class
*
* @package Sabre
* @subpackage DAV
* @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_DAV_FS_Directory extends Sabre_DAV_FS_Node implements Sabre_DAV_ICollection, Sabre_DAV_IQuota {
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
*/
public function createFile($name, $data = null) {
$newPath = $this->path . '/' . $name;
file_put_contents($newPath,$data);
}
/**
* Creates a new subdirectory
*
* @param string $name
* @return void
*/
public function createDirectory($name) {
$newPath = $this->path . '/' . $name;
mkdir($newPath);
}
/**
* Returns a specific child node, referenced by its name
*
* This method must throw Sabre_DAV_Exception_NotFound if the node does not
* exist.
*
* @param string $name
* @throws Sabre_DAV_Exception_NotFound
* @return Sabre_DAV_INode
*/
public function getChild($name) {
$path = $this->path . '/' . $name;
if (!file_exists($path)) throw new Sabre_DAV_Exception_NotFound('File with name ' . $path . ' could not be located');
if (is_dir($path)) {
return new Sabre_DAV_FS_Directory($path);
} else {
return new Sabre_DAV_FS_File($path);
}
}
/**
* Returns an array with all the child nodes
*
* @return Sabre_DAV_INode[]
*/
public function getChildren() {
$nodes = array();
foreach(scandir($this->path) as $node) if($node!='.' && $node!='..') $nodes[] = $this->getChild($node);
return $nodes;
}
/**
* Checks if a child exists.
*
* @param string $name
* @return bool
*/
public function childExists($name) {
$path = $this->path . '/' . $name;
return file_exists($path);
}
/**
* Deletes all files in this directory, and then itself
*
* @return void
*/
public function delete() {
foreach($this->getChildren() as $child) $child->delete();
rmdir($this->path);
}
/**
* Returns available diskspace information
*
* @return array
*/
public function getQuotaInfo() {
return array(
disk_total_space($this->path)-disk_free_space($this->path),
disk_free_space($this->path)
);
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* File class
*
* @package Sabre
* @subpackage DAV
* @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_DAV_FS_File extends Sabre_DAV_FS_Node implements Sabre_DAV_IFile {
/**
* Updates the data
*
* @param resource $data
* @return void
*/
public function put($data) {
file_put_contents($this->path,$data);
}
/**
* Returns the data
*
* @return string
*/
public function get() {
return fopen($this->path,'r');
}
/**
* Delete the current file
*
* @return void
*/
public function delete() {
unlink($this->path);
}
/**
* Returns the size of the node, in bytes
*
* @return int
*/
public function getSize() {
return filesize($this->path);
}
/**
* Returns the ETag for a file
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return mixed
*/
public function getETag() {
return null;
}
/**
* Returns the mime-type for a file
*
* If null is returned, we'll assume application/octet-stream
*
* @return mixed
*/
public function getContentType() {
return null;
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* Base node-class
*
* The node class implements the method used by both the File and the Directory classes
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_FS_Node implements Sabre_DAV_INode {
/**
* The path to the current node
*
* @var string
*/
protected $path;
/**
* Sets up the node, expects a full path name
*
* @param string $path
*/
public function __construct($path) {
$this->path = $path;
}
/**
* Returns the name of the node
*
* @return string
*/
public function getName() {
list(, $name) = Sabre_DAV_URLUtil::splitPath($this->path);
return $name;
}
/**
* Renames the node
*
* @param string $name The new name
* @return void
*/
public function setName($name) {
list($parentPath, ) = Sabre_DAV_URLUtil::splitPath($this->path);
list(, $newName) = Sabre_DAV_URLUtil::splitPath($name);
$newPath = $parentPath . '/' . $newName;
rename($this->path,$newPath);
$this->path = $newPath;
}
/**
* Returns the last modification time, as a unix timestamp
*
* @return int
*/
public function getLastModified() {
return filemtime($this->path);
}
}

View file

@ -0,0 +1,157 @@
<?php
/**
* Directory class
*
* @package Sabre
* @subpackage DAV
* @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_DAV_FSExt_Directory extends Sabre_DAV_FSExt_Node implements Sabre_DAV_ICollection, Sabre_DAV_IQuota {
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
*/
public function createFile($name, $data = null) {
// We're not allowing dots
if ($name=='.' || $name=='..') throw new Sabre_DAV_Exception_Forbidden('Permission denied to . and ..');
$newPath = $this->path . '/' . $name;
file_put_contents($newPath,$data);
return '"' . md5_file($newPath) . '"';
}
/**
* Creates a new subdirectory
*
* @param string $name
* @return void
*/
public function createDirectory($name) {
// We're not allowing dots
if ($name=='.' || $name=='..') throw new Sabre_DAV_Exception_Forbidden('Permission denied to . and ..');
$newPath = $this->path . '/' . $name;
mkdir($newPath);
}
/**
* Returns a specific child node, referenced by its name
*
* This method must throw Sabre_DAV_Exception_NotFound if the node does not
* exist.
*
* @param string $name
* @throws Sabre_DAV_Exception_NotFound
* @return Sabre_DAV_INode
*/
public function getChild($name) {
$path = $this->path . '/' . $name;
if (!file_exists($path)) throw new Sabre_DAV_Exception_NotFound('File could not be located');
if ($name=='.' || $name=='..') throw new Sabre_DAV_Exception_Forbidden('Permission denied to . and ..');
if (is_dir($path)) {
return new Sabre_DAV_FSExt_Directory($path);
} else {
return new Sabre_DAV_FSExt_File($path);
}
}
/**
* Checks if a child exists.
*
* @param string $name
* @return bool
*/
public function childExists($name) {
if ($name=='.' || $name=='..')
throw new Sabre_DAV_Exception_Forbidden('Permission denied to . and ..');
$path = $this->path . '/' . $name;
return file_exists($path);
}
/**
* Returns an array with all the child nodes
*
* @return Sabre_DAV_INode[]
*/
public function getChildren() {
$nodes = array();
foreach(scandir($this->path) as $node) if($node!='.' && $node!='..' && $node!='.sabredav') $nodes[] = $this->getChild($node);
return $nodes;
}
/**
* Deletes all files in this directory, and then itself
*
* @return bool
*/
public function delete() {
// Deleting all children
foreach($this->getChildren() as $child) $child->delete();
// Removing resource info, if its still around
if (file_exists($this->path . '/.sabredav')) unlink($this->path . '/.sabredav');
// Removing the directory itself
rmdir($this->path);
return parent::delete();
}
/**
* Returns available diskspace information
*
* @return array
*/
public function getQuotaInfo() {
return array(
disk_total_space($this->path)-disk_free_space($this->path),
disk_free_space($this->path)
);
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* File class
*
* @package Sabre
* @subpackage DAV
* @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_DAV_FSExt_File extends Sabre_DAV_FSExt_Node implements Sabre_DAV_PartialUpdate_IFile {
/**
* Updates the data
*
* data is a readable stream resource.
*
* @param resource|string $data
* @return string
*/
public function put($data) {
file_put_contents($this->path,$data);
return '"' . md5_file($this->path) . '"';
}
/**
* Updates the data at a given offset
*
* The data argument is a readable stream resource.
* The offset argument is a 0-based offset where the data should be
* written.
*
* param resource|string $data
* @return void
*/
public function putRange($data, $offset) {
$f = fopen($this->path, 'c');
fseek($f,$offset-1);
if (is_string($data)) {
fwrite($f, $data);
} else {
stream_copy_to_stream($data,$f);
}
fclose($f);
return '"' . md5_file($this->path) . '"';
}
/**
* Returns the data
*
* @return resource
*/
public function get() {
return fopen($this->path,'r');
}
/**
* Delete the current file
*
* @return bool
*/
public function delete() {
unlink($this->path);
return parent::delete();
}
/**
* Returns the ETag for a file
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return string|null
*/
public function getETag() {
return '"' . md5_file($this->path). '"';
}
/**
* Returns the mime-type for a file
*
* If null is returned, we'll assume application/octet-stream
*
* @return string|null
*/
public function getContentType() {
return null;
}
/**
* Returns the size of the file, in bytes
*
* @return int
*/
public function getSize() {
return filesize($this->path);
}
}

View file

@ -0,0 +1,212 @@
<?php
/**
* Base node-class
*
* The node class implements the method used by both the File and the Directory classes
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_FSExt_Node extends Sabre_DAV_FS_Node implements Sabre_DAV_IProperties {
/**
* Updates properties on this node,
*
* @param array $properties
* @see Sabre_DAV_IProperties::updateProperties
* @return bool|array
*/
public function updateProperties($properties) {
$resourceData = $this->getResourceData();
foreach($properties as $propertyName=>$propertyValue) {
// If it was null, we need to delete the property
if (is_null($propertyValue)) {
if (isset($resourceData['properties'][$propertyName])) {
unset($resourceData['properties'][$propertyName]);
}
} else {
$resourceData['properties'][$propertyName] = $propertyValue;
}
}
$this->putResourceData($resourceData);
return true;
}
/**
* Returns a list of properties for this nodes.;
*
* The properties list is a list of propertynames the client requested, encoded as xmlnamespace#tagName, for example: http://www.example.org/namespace#author
* If the array is empty, all properties should be returned
*
* @param array $properties
* @return array
*/
function getProperties($properties) {
$resourceData = $this->getResourceData();
// if the array was empty, we need to return everything
if (!$properties) return $resourceData['properties'];
$props = array();
foreach($properties as $property) {
if (isset($resourceData['properties'][$property])) $props[$property] = $resourceData['properties'][$property];
}
return $props;
}
/**
* Returns the path to the resource file
*
* @return string
*/
protected function getResourceInfoPath() {
list($parentDir) = Sabre_DAV_URLUtil::splitPath($this->path);
return $parentDir . '/.sabredav';
}
/**
* Returns all the stored resource information
*
* @return array
*/
protected function getResourceData() {
$path = $this->getResourceInfoPath();
if (!file_exists($path)) return array('properties' => array());
// opening up the file, and creating a shared lock
$handle = fopen($path,'r');
flock($handle,LOCK_SH);
$data = '';
// Reading data until the eof
while(!feof($handle)) {
$data.=fread($handle,8192);
}
// We're all good
fclose($handle);
// Unserializing and checking if the resource file contains data for this file
$data = unserialize($data);
if (!isset($data[$this->getName()])) {
return array('properties' => array());
}
$data = $data[$this->getName()];
if (!isset($data['properties'])) $data['properties'] = array();
return $data;
}
/**
* Updates the resource information
*
* @param array $newData
* @return void
*/
protected function putResourceData(array $newData) {
$path = $this->getResourceInfoPath();
// opening up the file, and creating a shared lock
$handle = fopen($path,'a+');
flock($handle,LOCK_EX);
$data = '';
rewind($handle);
// Reading data until the eof
while(!feof($handle)) {
$data.=fread($handle,8192);
}
// Unserializing and checking if the resource file contains data for this file
$data = unserialize($data);
$data[$this->getName()] = $newData;
ftruncate($handle,0);
rewind($handle);
fwrite($handle,serialize($data));
fclose($handle);
}
/**
* Renames the node
*
* @param string $name The new name
* @return void
*/
public function setName($name) {
list($parentPath, ) = Sabre_DAV_URLUtil::splitPath($this->path);
list(, $newName) = Sabre_DAV_URLUtil::splitPath($name);
$newPath = $parentPath . '/' . $newName;
// We're deleting the existing resourcedata, and recreating it
// for the new path.
$resourceData = $this->getResourceData();
$this->deleteResourceData();
rename($this->path,$newPath);
$this->path = $newPath;
$this->putResourceData($resourceData);
}
/**
* @return bool
*/
public function deleteResourceData() {
// When we're deleting this node, we also need to delete any resource information
$path = $this->getResourceInfoPath();
if (!file_exists($path)) return true;
// opening up the file, and creating a shared lock
$handle = fopen($path,'a+');
flock($handle,LOCK_EX);
$data = '';
rewind($handle);
// Reading data until the eof
while(!feof($handle)) {
$data.=fread($handle,8192);
}
// Unserializing and checking if the resource file contains data for this file
$data = unserialize($data);
if (isset($data[$this->getName()])) unset($data[$this->getName()]);
ftruncate($handle,0);
rewind($handle);
fwrite($handle,serialize($data));
fclose($handle);
return true;
}
public function delete() {
return $this->deleteResourceData();
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* File class
*
* This is a helper class, that should aid in getting file classes setup.
* Most of its methods are implemented, and throw permission denied exceptions
*
* @package Sabre
* @subpackage DAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_DAV_File extends Sabre_DAV_Node implements Sabre_DAV_IFile {
/**
* Updates the data
*
* data is a readable stream resource.
*
* @param resource $data
* @return void
*/
public function put($data) {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to change data');
}
/**
* Returns the data
*
* This method may either return a string or a readable stream resource
*
* @return mixed
*/
public function get() {
throw new Sabre_DAV_Exception_Forbidden('Permission denied to read this file');
}
/**
* Returns the size of the file, in bytes.
*
* @return int
*/
public function getSize() {
return 0;
}
/**
* Returns the ETag for a file
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return string|null
*/
public function getETag() {
return null;
}
/**
* Returns the mime-type for a file
*
* If null is returned, we'll assume application/octet-stream
*
* @return string|null
*/
public function getContentType() {
return null;
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* The ICollection Interface
*
* This interface should be implemented by each class that represents a collection
*
* @package Sabre
* @subpackage DAV
* @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_DAV_ICollection extends Sabre_DAV_INode {
/**
* Creates a new file in the directory
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
*/
function createFile($name, $data = null);
/**
* Creates a new subdirectory
*
* @param string $name
* @return void
*/
function createDirectory($name);
/**
* Returns a specific child node, referenced by its name
*
* This method must throw Sabre_DAV_Exception_NotFound if the node does not
* exist.
*
* @param string $name
* @return Sabre_DAV_INode
*/
function getChild($name);
/**
* Returns an array with all the child nodes
*
* @return Sabre_DAV_INode[]
*/
function getChildren();
/**
* Checks if a child-node with the specified name exists
*
* @param string $name
* @return bool
*/
function childExists($name);
}

View file

@ -0,0 +1,28 @@
<?php
/**
* The IExtendedCollection interface.
*
* This interface can be used to create special-type of collection-resources
* as defined by RFC 5689.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_IExtendedCollection extends Sabre_DAV_ICollection {
/**
* Creates a new collection
*
* @param string $name
* @param array $resourceType
* @param array $properties
* @return void
*/
function createExtendedCollection($name, array $resourceType, array $properties);
}

View file

@ -0,0 +1,77 @@
<?php
/**
* This interface represents a file in the directory tree
*
* A file is a bit of a broad definition. In general it implies that on
* this specific node a PUT or GET method may be performed, to either update,
* or retrieve the contents of the file.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_IFile extends Sabre_DAV_INode {
/**
* Updates the data
*
* The data argument is a readable stream resource.
*
* After a succesful put operation, you may choose to return an ETag. The
* etag must always be surrounded by double-quotes. These quotes must
* appear in the actual string you're returning.
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file, the contents haven't changed in the mean
* time.
*
* If you don't plan to store the file byte-by-byte, and you return a
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
* @param resource $data
* @return string|null
*/
function put($data);
/**
* Returns the data
*
* This method may either return a string or a readable stream resource
*
* @return mixed
*/
function get();
/**
* Returns the mime-type for a file
*
* If null is returned, we'll assume application/octet-stream
*
* @return string|null
*/
function getContentType();
/**
* Returns the ETag for a file
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
*
* Return null if the ETag can not effectively be determined
*
* @return void
*/
function getETag();
/**
* Returns the size of the node, in bytes
*
* @return int
*/
function getSize();
}

View file

@ -0,0 +1,46 @@
<?php
/**
* The INode interface is the base interface, and the parent class of both ICollection and IFile
*
* @package Sabre
* @subpackage DAV
* @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_DAV_INode {
/**
* Deleted the current node
*
* @return void
*/
function delete();
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
function getName();
/**
* Renames the node
*
* @param string $name The new name
* @return void
*/
function setName($name);
/**
* Returns the last modification time, as a unix timestamp
*
* @return int
*/
function getLastModified();
}

View file

@ -0,0 +1,67 @@
<?php
/**
* IProperties interface
*
* Implement this interface to support custom WebDAV properties requested and sent from clients.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_IProperties extends Sabre_DAV_INode {
/**
* Updates properties on this node,
*
* The properties 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 array $mutations
* @return bool|array
*/
function updateProperties($mutations);
/**
* Returns a list of properties for this nodes.
*
* The properties list is a list of propertynames the client requested,
* encoded in clark-notation {xmlnamespace}tagname
*
* If the array is empty, it means 'all properties' were requested.
*
* @param array $properties
* @return void
*/
function getProperties($properties);
}

View file

@ -0,0 +1,27 @@
<?php
/**
* IQuota interface
*
* Implement this interface to add the ability to return quota information. The ObjectTree
* will check for quota information on any given node. If the information is not available it will
* attempt to fetch the information from the root node.
*
* @package Sabre
* @subpackage DAV
* @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_DAV_IQuota extends Sabre_DAV_ICollection {
/**
* Returns the quota information
*
* This method MUST return an array with 2 values, the first being the total used space,
* the second the available space (in bytes)
*/
function getQuotaInfo();
}

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