Version 3.2.3

This commit is contained in:
Neo committed 2025-12-19 00:34:03 -08:00
1 parent ae5c01cc78
commit 78706903a2
2641 files changed
+228363 -201264

No files matched your search

@@ -0,0 +1,151 @@
<?php
/**
* Abstract Calendaring backend. Extend this class to create your own backends.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
abstract class Sabre_CalDAV_Backend_Abstract {
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which the basename of the uri with which the calendar is
* accessed.
* * principalUri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* @param string $principalUri
* @return array
*/
abstract function getCalendarsForUser($principalUri);
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar.
*
* This function must return a server-wide unique id that can be used
* later to reference the calendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return string|int
*/
abstract function createCalendar($principalUri,$calendarUri,array $properties);
/**
* 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-existant property is always succesful.
*
* 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 $properties
* @return bool|array
*/
public function updateCalendar($calendarId, array $properties) {
return false;
}
/**
* Delete a calendar and all it's objects
*
* @param string $calendarId
* @return void
*/
abstract function deleteCalendar($calendarId);
/**
* Returns all calendar objects within a calendar object.
*
* Every item contains an array with the following keys:
* * id - unique identifier which will be used for subsequent updates
* * calendardata - The iCalendar-compatible calnedar 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
*
* @param string $calendarId
* @return array
*/
abstract function getCalendarObjects($calendarId);
/**
* Returns information from a single calendar object, based on it's object uri.
*
* @param string $calendarId
* @param string $objectUri
* @return array
*/
abstract function getCalendarObject($calendarId,$objectUri);
/**
* Creates a new calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @param string $calendarData
* @return void
*/
abstract function createCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Updates an existing calendarobject, based on it's uri.
*
* @param string $calendarId
* @param string $objectUri
* @param string $calendarData
* @return void
*/
abstract function updateCalendarObject($calendarId,$objectUri,$calendarData);
/**
* Deletes an existing calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @return void
*/
abstract function deleteCalendarObject($calendarId,$objectUri);
}
@@ -0,0 +1,356 @@
<?php
/**
* PDO CalDAV backend
*
* This backend is used to store calendar-data in a PDO database, such as
* sqlite or MySQL
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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 {
/**
* pdo
*
* @var PDO
*/
private $pdo;
/**
* 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
*/
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/**
* 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 calendars WHERE principaluri = ?");
$stmt->execute(array($principalUri));
$calendars = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$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 mixed
*/
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])) {
$myValue = $properties[$xmlName];
$values[':' . $dbName] = $properties[$xmlName];
$fieldNames[] = $dbName;
}
}
$stmt = $this->pdo->prepare("INSERT INTO calendars (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")");
$stmt->execute($values);
return $this->pdo->lastInsertId();
}
/**
* Updates a calendars properties
*
* 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-existant property is always succesful.
*
* 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 $properties
* @return bool|array
*/
public function updateCalendar($calendarId, array $properties) {
$newValues = array();
$result = array(
200 => array(), // Ok
403 => array(), // Forbidden
424 => array(), // Failed Dependency
);
$hasError = false;
foreach($properties as $propertyName=>$propertyValue) {
// We don't know about this property.
if (!isset($this->propertyMap[$propertyName])) {
$hasError = true;
$result[403][$propertyName] = null;
unset($properties[$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($properties 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 calendars 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 calendarobjects WHERE calendarid = ?');
$stmt->execute(array($calendarId));
$stmt = $this->pdo->prepare('DELETE FROM calendars WHERE id = ?');
$stmt->execute(array($calendarId));
}
/**
* Returns all calendar objects within a calendar object.
*
* Every item contains an array with the following keys:
* * id - unique identifier which will be used for subsequent updates
* * calendardata - The iCalendar-compatible calnedar 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
*
* @param string $calendarId
* @return array
*/
public function getCalendarObjects($calendarId) {
$stmt = $this->pdo->prepare('SELECT * FROM calendarobjects WHERE calendarid = ?');
$stmt->execute(array($calendarId));
return $stmt->fetchAll();
}
/**
* Returns information from a single calendar object, based on it's object uri.
*
* @param string $calendarId
* @param string $objectUri
* @return array
*/
public function getCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('SELECT * FROM calendarobjects WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId, $objectUri));
return $stmt->fetch();
}
/**
* Creates a new calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @param string $calendarData
* @return void
*/
public function createCalendarObject($calendarId,$objectUri,$calendarData) {
$stmt = $this->pdo->prepare('INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified) VALUES (?,?,?,?)');
$stmt->execute(array($calendarId,$objectUri,$calendarData,time()));
$stmt = $this->pdo->prepare('UPDATE calendars SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* @param string $calendarId
* @param string $objectUri
* @param string $calendarData
* @return void
*/
public function updateCalendarObject($calendarId,$objectUri,$calendarData) {
$stmt = $this->pdo->prepare('UPDATE calendarobjects SET calendardata = ?, lastmodified = ? WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarData,time(),$calendarId,$objectUri));
$stmt = $this->pdo->prepare('UPDATE calendars SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
}
/**
* Deletes an existing calendar object.
*
* @param string $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId,$objectUri) {
$stmt = $this->pdo->prepare('DELETE FROM calendarobjects WHERE calendarid = ? AND uri = ?');
$stmt->execute(array($calendarId,$objectUri));
$stmt = $this->pdo->prepare('UPDATE calendars SET ctag = ctag + 1 WHERE id = ?');
$stmt->execute(array($calendarId));
}
}
@@ -0,0 +1,10 @@
<html>
<head><title>IP.Board - Bulletin Board System</title></head>
<body>
<h1>403: IP.Board -&gt; Forbidden</h1>
<hr>
You have reached this page in error, please use your back button to return to the forum.
<hr>
<a href="http://www.invisionpower.com/">IP.Board</a>
</body>
</html>
@@ -0,0 +1,254 @@
<?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-2010 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_DAV_ICollection, Sabre_DAV_IProperties {
/**
* This is an array with calendar information
*
* @var array
*/
private $calendarInfo;
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
*/
private $caldavBackend;
/**
* Authentication backend
*
* @var Sabre_DAV_Auth_Backend_Abstract
*/
private $authBackend;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param array $calendarInfo
* @return void
*/
public function __construct(Sabre_DAV_Auth_Backend_Abstract $authBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend,$calendarInfo) {
$this->caldavBackend = $caldavBackend;
$this->authBackend = $authBackend;
$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) {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
return $this->caldavBackend->updateCalendar($this->calendarInfo['id'],$mutations);
}
/**
* Returns the list of properties
*
* @param array $properties
* @return array
*/
public function getProperties($requestedProperties) {
$response = array();
if (!$this->hasPrivilege()) return array();
foreach($requestedProperties as $prop) switch($prop) {
case '{DAV:}resourcetype' :
$response[$prop] = new Sabre_DAV_Property_ResourceType(array('{urn:ietf:params:xml:ns:caldav}calendar','{DAV:}collection'));
break;
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_DAV_Property_Principal(Sabre_DAV_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_DAV_ICalendarObject
*/
public function getChild($name) {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
if (!$obj) throw new Sabre_DAV_Exception_FileNotFound('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() {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
$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) {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
$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) {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
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 void
*/
public function createFile($name,$calendarData = null) {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
$calendarData = stream_get_contents($calendarData);
$supportedComponents = $this->calendarInfo['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set']->getValue();
Sabre_CalDAV_ICalendarUtil::validateICalendarObject($calendarData, $supportedComponents);
$this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData);
}
/**
* Deletes the calendar.
*
* @return void
*/
public function delete() {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
$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) {
if (!$this->hasPrivilege()) throw new Sabre_DAV_Exception_Forbidden('Permission denied to access this calendar');
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;
}
/**
* Check if user has access.
*
* This method does a check if the currently logged in user
* has permission to access this calendar. There is only read-write
* access, so you're in or you're out.
*
* @return bool
*/
protected function hasPrivilege() {
if (!$user = $this->authBackend->getCurrentUser()) return false;
if ($user['uri']!==$this->calendarInfo['principaluri']) return false;
return true;
}
}
@@ -0,0 +1,176 @@
<?php
/**
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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_DAV_IProperties {
/**
* Sabre_CalDAV_Backend_Abstract
*
* @var array
*/
private $caldavBackend;
/**
* Array with information about this CalendarObject
*
* @var array
*/
private $objectData;
/**
* Array with information about the containing calendar
*
* @var array
*/
private $calendarInfo;
/**
* Constructor
*
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param array $objectData
*/
public function __construct(Sabre_CalDAV_Backend_Abstract $caldavBackend,$calendarInfo,$objectData) {
$this->caldavBackend = $caldavBackend;
$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() {
return $this->objectData['calendardata'];
}
/**
* Updates the ICalendar-formatted object
*
* @param string $calendarData
* @return void
*/
public function put($calendarData) {
if (is_resource($calendarData))
$calendarData = stream_get_contents($calendarData);
$supportedComponents = $this->calendarInfo['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set']->getValue();
Sabre_CalDAV_ICalendarUtil::validateICalendarObject($calendarData, $supportedComponents);
$this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData);
$this->objectData['calendardata'] = $calendarData;
}
/**
* 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';
}
/**
* Returns an ETag for this object.
*
* The ETag is an arbritrary string, but MUST be surrounded by double-quotes.
*
* @return string
*/
public function getETag() {
return '"' . md5($this->objectData['calendardata']). '"';
}
/**
* Returns the list of properties for this object
*
* @param array $properties
* @return array
*/
public function getProperties($properties) {
$response = array();
if (in_array('{urn:ietf:params:xml:ns:caldav}calendar-data',$properties))
$response['{urn:ietf:params:xml:ns:caldav}calendar-data'] = str_replace("\r","",$this->objectData['calendardata']);
return $response;
}
/**
* Updates properties
*
* @param array $properties
* @return array
*/
public function updateProperties($properties) {
return false;
}
/**
* Returns the last modification date as a unix timestamp
*
* @return time
*/
public function getLastModified() {
return strtotime($this->objectData['lastmodified']);
}
/**
* Returns the size of this object in bytes
*
* @return int
*/
public function getSize() {
return strlen($this->objectData['calendardata']);
}
}
@@ -0,0 +1,74 @@
<?php
/**
* Users collection
*
* This object is responsible for generating a collection of users.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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_DAV_Directory {
/**
* Authentication Backend
*
* @var Sabre_DAV_Auth_Backend_Abstract
*/
protected $authBackend;
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
*/
protected $caldavBackend;
/**
* Constructor
*
* This constructor needs both an authentication and a caldav backend.
*
* @param Sabre_DAV_Auth_Backend_Abstract $authBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
*/
public function __construct(Sabre_DAV_Auth_Backend_Abstract $authBackend,Sabre_CalDAV_Backend_Abstract $caldavBackend) {
$this->authBackend = $authBackend;
$this->caldavBackend = $caldavBackend;
}
/**
* Returns the name of the node
*
* @return string
*/
public function getName() {
return Sabre_CalDAV_Plugin::CALENDAR_ROOT;
}
/**
* Returns the list of users as Sabre_CalDAV_User objects.
*
* @return array
*/
public function getChildren() {
$users = $this->authBackend->getUsers();
$children = array();
foreach($users as $user) {
$children[] = new Sabre_CalDAV_UserCalendars($this->authBackend, $this->caldavBackend, $user['uri']);
}
return $children;
}
}
@@ -0,0 +1,18 @@
<?php
/**
* InvalidICalendarObject
*
* This exception is thrown when an attempt is made to create or update
* an invalid ICalendar object
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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_InvalidICalendarObject extends Sabre_DAV_Exception_PreconditionFailed {
}
@@ -0,0 +1,10 @@
<html>
<head><title>IP.Board - Bulletin Board System</title></head>
<body>
<h1>403: IP.Board -&gt; Forbidden</h1>
<hr>
You have reached this page in error, please use your back button to return to the forum.
<hr>
<a href="http://www.invisionpower.com/">IP.Board</a>
</body>
</html>
@@ -0,0 +1,154 @@
<?php
/**
* This class contains several utilities related to the ICalendar (rfc2445) format
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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_ICalendarUtil {
/**
* Validates an ICalendar object
*
* This method makes sure this ICalendar object is properly formatted.
* If we can't parse it, we'll throw exceptions.
*
* @param string $icalData
* @param array $allowedComponents
* @return bool
*/
static function validateICalendarObject($icalData, array $allowedComponents) {
$xcal = simplexml_load_string(self::toXCal($icalData));
if (!$xcal) throw new Sabre_CalDAV_Exception_InvalidICalendarObject('Invalid calendarobject format');
$xcal->registerXPathNameSpace('cal','urn:ietf:params:xml:ns:xcal');
// Check if there's only 1 component
$components = array('vevent','vtodo','vjournal','vfreebusy');
$componentsFound = array();
foreach($components as $component) {
$test = $xcal->xpath('/cal:iCalendar/cal:vcalendar/cal:' . $component);
if (is_array($test)) $componentsFound = array_merge($componentsFound, $test);
}
if (count($componentsFound)>1) {
throw new Sabre_CalDAV_Exception_InvalidICalendarObject('Only 1 of VEVENT, VTODO, VJOURNAL or VFREEBUSY may be specified per calendar object');
}
if (count($componentsFound)<1) {
throw new Sabre_CalDAV_Exception_InvalidICalendarObject('One VEVENT, VTODO, VJOURNAL or VFREEBUSY must be specified. 0 found.');
}
$component = $componentsFound[0];
// Check if the component is allowed
$name = $component->getName();
if (!in_array(strtoupper($name),$allowedComponents)) {
throw new Sabre_CalDAV_Exception_InvalidICalendarObject(strtoupper($name) . ' is not allowed in this calendar.');
}
if (count($xcal->xpath('/cal:iCalendar/cal:vcalendar/cal:method'))>0) {
throw new Sabre_CalDAV_Exception_InvalidICalendarObject('The METHOD property is not allowed in calendar objects');
}
return true;
}
/**
* Converts ICalendar data to XML.
*
* Properties are converted to lowercase xml elements. Parameters are;
* converted to attributes. BEGIN:VEVENT is converted to <vevent> and
* END:VEVENT </vevent> as well as other components.
*
* It's a very loose parser. If any line does not conform to the spec, it
* will simply be ignored. It will try to detect if \r\n or \n line endings
* are used.
*
* @todo Currently quoted attributes are not parsed correctly.
* @see http://tools.ietf.org/html/draft-royer-calsch-xcal-03
* @param string $icalData
* @return string.
*/
static function toXCAL($icalData) {
// Detecting line endings
$lb="\r\n";
if (strpos($icalData,"\r\n")!==false) $lb = "\r\n";
elseif (strpos($icalData,"\n")!==false) $lb = "\n";
// Splitting up items per line
$lines = explode($lb,$icalData);
// Properties can be folded over 2 lines. In this case the second
// line will be preceeded by a space or tab.
$lines2 = array();
foreach($lines as $line) {
if (!$line) continue;
if ($line[0]===" " || $line[0]==="\t") {
$lines2[count($lines2)-1].=substr($line,1);
continue;
}
$lines2[]=$line;
}
$xml = '<?xml version="1.0"?>' . "\n";
$xml.= "<iCalendar xmlns=\"urn:ietf:params:xml:ns:xcal\">\n";
$spaces = 2;
foreach($lines2 as $line) {
$matches = array();
// This matches PROPERTYNAME;ATTRIBUTES:VALUE
if (!preg_match('/^([^:^;]*)(?:;([^:]*))?:(.*)$/',$line,$matches))
continue;
$propertyName = strtolower($matches[1]);
$attributes = $matches[2];
$value = $matches[3];
// If the line was in the format BEGIN:COMPONENT or END:COMPONENT, we need to special case it.
if ($propertyName === 'begin') {
$xml.=str_repeat(" ",$spaces);
$xml.='<' . strtolower($value) . ">\n";
$spaces+=2;
continue;
} elseif ($propertyName === 'end') {
$spaces-=2;
$xml.=str_repeat(" ",$spaces);
$xml.='</' . strtolower($value) . ">\n";
continue;
}
$xml.=str_repeat(" ",$spaces);
$xml.='<' . $propertyName;
if ($attributes) {
// There can be multiple attributes
$attributes = explode(';',$attributes);
foreach($attributes as $att) {
list($attName,$attValue) = explode('=',$att,2);
$attName = strtolower($attName);
if ($attName === 'language') $attName='xml:lang';
$xml.=' ' . $attName . '="' . htmlspecialchars($attValue) . '"';
}
}
$xml.='>'. htmlspecialchars(trim($value)) . '</' . $propertyName . ">\n";
}
$xml.="</iCalendar>";
return $xml;
}
}
@@ -0,0 +1,707 @@
<?php
/**
* CalDAV plugin
*
* This plugin provides functionality added by CalDAV (RFC 4791)
* It implements new reports, and the MKCALENDAR method.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 Rooftop Solutions. All rights reserved.
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* This is the official CalDAV namespace
*/
const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
/**
* This is the namespace for the proprietary calendarserver extensions
*/
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
/**
* The following constants are used to differentiate
* the various filters for the calendar-query report
*/
const FILTER_COMPFILTER = 1;
const FILTER_TIMERANGE = 3;
const FILTER_PROPFILTER = 4;
const FILTER_PARAMFILTER = 5;
const FILTER_TEXTMATCH = 6;
/**
* The hardcoded root for calendar objects. It is unfortunate
* that we're stuck with it, but it will have to do for now
*/
const CALENDAR_ROOT = 'calendars';
/**
* Reference to server object
*
* @var Sabre_DAV_Server
*/
private $server;
/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* @param string $uri
* @return array
*/
public function getHTTPMethods($uri) {
// The MKCALENDAR is only available on unmapped uri's, whose
// parents extend IExtendedCollection
list($parent, $name) = Sabre_DAV_URLUtil::splitPath($uri);
$node = $this->server->tree->getNodeForPath($parent);
if ($node instanceof Sabre_DAV_IExtendedCollection) {
try {
$node->getChild($name);
} catch (Sabre_DAV_Exception_FileNotFound $e) {
return array('MKCALENDAR');
}
}
return array();
}
/**
* Returns a list of features for the DAV: HTTP header.
*
* @return array
*/
public function getFeatures() {
return array('calendar-access');
}
/**
* Initializes the plugin
*
* @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
$server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
//$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000);
$server->subscribeEvent('report',array($this,'report'));
$server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'));
$server->xmlNamespaces[self::NS_CALDAV] = 'cal';
$server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs';
$server->propertyMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre_CalDAV_Property_SupportedCalendarComponentSet';
array_push($server->protectedProperties,
'{' . self::NS_CALDAV . '}supported-calendar-component-set',
'{' . self::NS_CALDAV . '}supported-calendar-data',
'{' . self::NS_CALDAV . '}max-resource-size',
'{' . self::NS_CALDAV . '}min-date-time',
'{' . self::NS_CALDAV . '}max-date-time',
'{' . self::NS_CALDAV . '}max-instances',
'{' . self::NS_CALDAV . '}max-attendees-per-instance',
'{' . self::NS_CALDAV . '}calendar-home-set',
'{' . self::NS_CALDAV . '}supported-collation-set',
// scheduling extension
'{' . self::NS_CALDAV . '}calendar-user-address-set'
);
}
/**
* This function handles support for the MKCALENDAR method
*
* @param string $method
* @return bool
*/
public function unknownMethod($method, $uri) {
if ($method!=='MKCALENDAR') return;
$this->httpMkCalendar($uri);
// false is returned to stop the unknownMethod event
return false;
}
/**
* This functions handles REPORT requests specific to CalDAV
*
* @param string $reportName
* @param DOMNode $dom
* @return bool
*/
public function report($reportName,$dom) {
switch($reportName) {
case '{'.self::NS_CALDAV.'}calendar-multiget' :
$this->calendarMultiGetReport($dom);
return false;
case '{'.self::NS_CALDAV.'}calendar-query' :
$this->calendarQueryReport($dom);
return false;
default :
return true;
}
}
/**
* This function handles the MKCALENDAR HTTP method, which creates
* a new calendar.
*
* @param string $uri
* @return void
*/
public function httpMkCalendar($uri) {
// Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
// for clients matching iCal in the user agent
//$ua = $this->server->httpRequest->getHeader('User-Agent');
//if (strpos($ua,'iCal/')!==false) {
// throw new Sabre_DAV_Exception_Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.');
//}
$body = $this->server->httpRequest->getBody(true);
$dom = Sabre_DAV_XMLUtil::loadDOMDocument($body);
$properties = array();
foreach($dom->firstChild->childNodes as $child) {
if (Sabre_DAV_XMLUtil::toClarkNotation($child)!=='{DAV:}set') continue;
foreach(Sabre_DAV_XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) {
$properties[$k] = $prop;
}
}
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');
$this->server->createCollection($uri,$resourceType,$properties);
$this->server->httpResponse->sendStatus(201);
$this->server->httpResponse->setHeader('Content-Length',0);
}
/**
* afterGetProperties
*
* This method handler is invoked after properties for a specific resource
* are received. This allows us to add any properties that might have been
* missing.
*
* @param string $path
* @param array $properties
* @return void
*/
public function afterGetProperties($path, &$properties) {
// Find out if we are currently looking at a principal resource
$currentNode = $this->server->tree->getNodeForPath($path);
if ($currentNode instanceof Sabre_DAV_Auth_Principal) {
// calendar-home-set property
$calHome = '{' . self::NS_CALDAV . '}calendar-home-set';
if (array_key_exists($calHome,$properties[404])) {
$principalId = $currentNode->getName();
$calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/';
unset($properties[404][$calHome]);
$properties[200][$calHome] = new Sabre_DAV_Property_Href($calendarHomePath);
}
// calendar-user-address-set property
$calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set';
if (array_key_exists($calProp,$properties[404])) {
// Do we have an email address?
$props = $currentNode->getProperties(array('{http://sabredav.org/ns}email-address'));
if (isset($props['{http://sabredav.org/ns}email-address'])) {
$email = $props['{http://sabredav.org/ns}email-address'];
} else {
// We're going to make up an emailaddress
$email = $currentNode->getName() . '.sabredav@' . $this->server->httpRequest->getHeader('host');
}
$properties[200][$calProp] = new Sabre_DAV_Property_Href('mailto:' . $email, false);
unset($properties[404][$calProp]);
}
}
if ($currentNode instanceof Sabre_CalDAV_Calendar || $currentNode instanceof Sabre_CalDAV_CalendarObject) {
if (array_key_exists('{DAV:}supported-report-set', $properties[200])) {
$properties[200]['{DAV:}supported-report-set']->addReport(array(
'{' . self::NS_CALDAV . '}calendar-multiget',
'{' . self::NS_CALDAV . '}calendar-query',
// '{' . self::NS_CALDAV . '}supported-collation-set',
// '{' . self::NS_CALDAV . '}free-busy-query',
));
}
}
}
/**
* This function handles the calendar-multiget REPORT.
*
* This report is used by the client to fetch the content of a series
* of urls. Effectively avoiding a lot of redundant requests.
*
* @param DOMNode $dom
* @return void
*/
public function calendarMultiGetReport($dom) {
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
$hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
foreach($hrefElems as $elem) {
$uri = $this->server->calculateUri($elem->nodeValue);
list($objProps) = $this->server->getPropertiesForPath($uri,$properties);
$propertyList[]=$objProps;
}
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
}
/**
* This function handles the calendar-query REPORT
*
* This report is used by clients to request calendar objects based on
* complex conditions.
*
* @param DOMNode $dom
* @return void
*/
public function calendarQueryReport($dom) {
$requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
$filterNode = $dom->getElementsByTagNameNS('urn:ietf:params:xml:ns:caldav','filter');
if ($filterNode->length!==1) {
throw new Sabre_DAV_Exception_BadRequest('The calendar-query report must have a filter element');
}
$filters = Sabre_CalDAV_XMLUtil::parseCalendarQueryFilters($filterNode->item(0));
$requestedCalendarData = true;
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
// We always retrieve calendar-data, as we need it for filtering.
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
// If calendar-data wasn't explicitly requested, we need to remove
// it after processing.
$requestedCalendarData = false;
}
// These are the list of nodes that potentially match the requirement
$candidateNodes = $this->server->getPropertiesForPath($this->server->getRequestUri(),$requestedProperties,$this->server->getHTTPDepth(0));
$verifiedNodes = array();
foreach($candidateNodes as $node) {
// If the node didn't have a calendar-data property, it must not be a calendar object
if (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) continue;
if ($this->validateFilters($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'],$filters)) {
if (!$requestedCalendarData) {
unset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
}
$verifiedNodes[] = $node;
}
}
$this->server->httpResponse->sendStatus(207);
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($verifiedNodes));
}
/**
* Verify if a list of filters applies to the calendar data object
*
* The calendarData object must be a valid iCalendar blob. The list of
* filters must be formatted as parsed by Sabre_CalDAV_Plugin::parseCalendarQueryFilters
*
* @param string $calendarData
* @param array $filters
* @return bool
*/
public function validateFilters($calendarData,$filters) {
// We are converting the calendar object to an XML structure
// This makes it far easier to parse
$xCalendarData = Sabre_CalDAV_ICalendarUtil::toXCal($calendarData);
$xml = simplexml_load_string($xCalendarData);
$xml->registerXPathNamespace('c','urn:ietf:params:xml:ns:xcal');
foreach($filters as $xpath=>$filter) {
// if-not-defined comes first
if (isset($filter['is-not-defined'])) {
if (!$xml->xpath($xpath))
continue;
else
return false;
}
$elem = $xml->xpath($xpath);
if (!$elem) return false;
$elem = $elem[0];
if (isset($filter['time-range'])) {
switch($elem->getName()) {
case 'vevent' :
$result = $this->validateTimeRangeFilterForEvent($xml,$xpath,$filter);
if ($result===false) return false;
break;
case 'vtodo' :
$result = $this->validateTimeRangeFilterForTodo($xml,$xpath,$filter);
if ($result===false) return false;
break;
case 'vjournal' :
// TODO: not implemented
break;
$result = $this->validateTimeRangeFilterForJournal($xml,$xpath,$filter);
if ($result===false) return false;
break;
case 'vfreebusy' :
// TODO: not implemented
break;
$result = $this->validateTimeRangeFilterForFreeBusy($xml,$xpath,$filter);
if ($result===false) return false;
break;
case 'valarm' :
// TODO: not implemented
break;
$result = $this->validateTimeRangeFilterForAlarm($xml,$xpath,$filter);
if ($result===false) return false;
break;
}
}
if (isset($filter['text-match'])) {
$currentString = (string)$elem;
$isMatching = $this->substringMatch($currentString, $filter['text-match']['value'], $filter['text-match']['collation']);
if ($filter['text-match']['negate-condition'] && $isMatching) return false;
if (!$filter['text-match']['negate-condition'] && !$isMatching) return false;
}
}
return true;
}
private function validateTimeRangeFilterForEvent(SimpleXMLElement $xml,$currentXPath,array $currentFilter) {
// Grabbing the DTSTART property
$xdtstart = $xml->xpath($currentXPath.'/c:dtstart');
if (!count($xdtstart)) {
throw new Sabre_DAV_Exception_BadRequest('DTSTART property missing from calendar object');
}
// The dtstart can be both a date, or datetime property
if ((string)$xdtstart[0]['value']==='DATE') {
$isDateTime = false;
} else {
$isDateTime = true;
}
// Determining the timezone
if ($tzid = (string)$xdtstart[0]['tzid']) {
$tz = new DateTimeZone($tzid);
} else {
$tz = null;
}
if ($isDateTime) {
$dtstart = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtstart[0],$tz);
} else {
$dtstart = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtstart[0]);
}
// Grabbing the DTEND property
$xdtend = $xml->xpath($currentXPath.'/c:dtend');
$dtend = null;
if (count($xdtend)) {
// Determining the timezone
if ($tzid = (string)$xdtend[0]['tzid']) {
$tz = new DateTimeZone($tzid);
} else {
$tz = null;
}
// Since the VALUE parameter of both DTSTART and DTEND must be the same
// we can assume we don't need to check the VALUE paramter of DTEND.
if ($isDateTime) {
$dtend = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtend[0],$tz);
} else {
$dtend = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtend[0],$tz);
}
}
if (is_null($dtend)) {
// The DTEND property was not found. We will first see if the event has a duration
// property
$xduration = $xml->xpath($currentXPath.'/c:duration');
if (count($xduration)) {
$duration = Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]);
// Making sure that the duration is bigger than 0 seconds.
$tempDT = clone $dtstart;
$tempDT->modify($duration);
if ($tempDT > $dtstart) {
// use DTEND = DTSTART + DURATION
$dtend = $tempDT;
} else {
// use DTEND = DTSTART
$dtend = $dtstart;
}
}
}
if (is_null($dtend)) {
if ($isDateTime) {
// DTEND = DTSTART
$dtend = $dtstart;
} else {
// DTEND = DTSTART + 1 DAY
$dtend = clone $dtstart;
$dtend->modify('+1 day');
}
}
if (!is_null($currentFilter['time-range']['start']) && $currentFilter['time-range']['start'] >= $dtend) return false;
if (!is_null($currentFilter['time-range']['end']) && $currentFilter['time-range']['end'] <= $dtstart) return false;
return true;
}
private function validateTimeRangeFilterForTodo(SimpleXMLElement $xml,$currentXPath,array $filter) {
// Gathering all relevant elements
$dtStart = null;
$duration = null;
$due = null;
$completed = null;
$created = null;
$xdt = $xml->xpath($currentXPath.'/c:dtstart');
if (count($xdt)) {
// The dtstart can be both a date, or datetime property
if ((string)$xdt[0]['value']==='DATE') {
$isDateTime = false;
} else {
$isDateTime = true;
}
// Determining the timezone
if ($tzid = (string)$xdt[0]['tzid']) {
$tz = new DateTimeZone($tzid);
} else {
$tz = null;
}
if ($isDateTime) {
$dtStart = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0],$tz);
} else {
$dtStart = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdt[0]);
}
}
// Only need to grab duration if dtStart is set
if (!is_null($dtStart)) {
$xduration = $xml->xpath($currentXPath.'/c:duration');
if (count($xduration)) {
$duration = Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]);
}
}
if (!is_null($dtStart) && !is_null($duration)) {
// Comparision from RFC 4791:
// (start <= DTSTART+DURATION) AND ((end > DTSTART) OR (end >= DTSTART+DURATION))
$end = clone $dtStart;
$end->modify($duration);
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $end) &&
(is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $dtStart || $filter['time-range']['end'] >= $end) ) {
return true;
} else {
return false;
}
}
// Need to grab the DUE property
$xdt = $xml->xpath($currentXPath.'/c:due');
if (count($xdt)) {
// The due property can be both a date, or datetime property
if ((string)$xdt[0]['value']==='DATE') {
$isDateTime = false;
} else {
$isDateTime = true;
}
// Determining the timezone
if ($tzid = (string)$xdt[0]['tzid']) {
$tz = new DateTimeZone($tzid);
} else {
$tz = null;
}
if ($isDateTime) {
$due = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0],$tz);
} else {
$due = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdt[0]);
}
}
if (!is_null($dtStart) && !is_null($due)) {
// Comparision from RFC 4791:
// ((start < DUE) OR (start <= DTSTART)) AND ((end > DTSTART) OR (end >= DUE))
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] < $due || $filter['time-range']['start'] < $dtstart) &&
(is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $due) ) {
return true;
} else {
return false;
}
}
if (!is_null($dtStart)) {
// Comparision from RFC 4791
// (start <= DTSTART) AND (end > DTSTART)
if ( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $dtStart) &&
(is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $dtStart) ) {
return true;
} else {
return false;
}
}
if (!is_null($due)) {
// Comparison from RFC 4791
// (start < DUE) AND (end >= DUE)
if ( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] < $due) &&
(is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $due) ) {
return true;
} else {
return false;
}
}
// Need to grab the COMPLETED property
$xdt = $xml->xpath($currentXPath.'/c:completed');
if (count($xdt)) {
$completed = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0]);
}
// Need to grab the CREATED property
$xdt = $xml->xpath($currentXPath.'/c:created');
if (count($xdt)) {
$created = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdt[0]);
}
if (!is_null($completed) && !is_null($created)) {
// Comparison from RFC 4791
// ((start <= CREATED) OR (start <= COMPLETED)) AND ((end >= CREATED) OR (end >= COMPLETED))
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $created || $filter['time-range']['start'] <= $completed) &&
(is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $created || $filter['time-range']['end'] >= $completed)) {
return true;
} else {
return false;
}
}
if (!is_null($completed)) {
// Comparison from RFC 4791
// (start <= COMPLETED) AND (end >= COMPLETED)
if( (is_null($filter['time-range']['start']) || $filter['time-range']['start'] <= $completed) &&
(is_null($filter['time-range']['end']) || $filter['time-range']['end'] >= $completed)) {
return true;
} else {
return false;
}
}
if (!is_null($created)) {
// Comparison from RFC 4791
// (end > CREATED)
if( (is_null($filter['time-range']['end']) || $filter['time-range']['end'] > $created) ) {
return true;
} else {
return false;
}
}
// Everything else is TRUE
return true;
}
public function substringMatch($haystack, $needle, $collation) {
switch($collation) {
case 'i;ascii-casemap' :
// default strtolower takes locale into consideration
// we don't want this.
$haystack = str_replace(range('a','z'), range('A','Z'), $haystack);
$needle = str_replace(range('a','z'), range('A','Z'), $needle);
return strpos($haystack, $needle)!==false;
case 'i;octet' :
return strpos($haystack, $needle)!==false;
default:
throw new Sabre_DAV_Exception_BadRequest('Unknown collation: ' . $collation);
}
}
}
@@ -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-2010 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 void
*/
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);
}
}
@@ -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-2010 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);
}
}
@@ -0,0 +1,34 @@
<?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-2010 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 {
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')
);
}
}
@@ -0,0 +1,10 @@
<html>
<head><title>IP.Board - Bulletin Board System</title></head>
<body>
<h1>403: IP.Board -&gt; Forbidden</h1>
<hr>
You have reached this page in error, please use your back button to return to the forum.
<hr>
<a href="http://www.invisionpower.com/">IP.Board</a>
</body>
</html>
@@ -0,0 +1,50 @@
<?php
/**
* CalDAV server
*
* 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.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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 {
/**
* 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);
/* Directory structure */
$root = new Sabre_DAV_SimpleDirectory('root');
$principals = new Sabre_DAV_Auth_PrincipalCollection($authBackend);
$root->addChild($principals);
$calendars = new Sabre_CalDAV_CalendarRootNode($authBackend, $calendarBackend);
$root->addChild($calendars);
$objectTree = new Sabre_DAV_ObjectTree($root);
/* Initializing server */
parent::__construct($objectTree);
/* Server Plugins */
$authPlugin = new Sabre_DAV_Auth_Plugin($authBackend,'SabreDAV');
$this->addPlugin($authPlugin);
$caldavPlugin = new Sabre_CalDAV_Plugin();
$this->addPlugin($caldavPlugin);
}
}
@@ -0,0 +1,193 @@
<?php
/**
* The UserCalenders class contains all calendars associated to one user
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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 {
/**
* Authentication backend
*
* @var Sabre_DAV_Auth_Backend_Abstract
*/
protected $authBackend;
/**
* Array with user information
*
* @var array
*/
protected $userUri;
/**
* CalDAV backend
*
* @var Sabre_CalDAV_Backend_Abstract
*/
protected $caldavBackend;
/**
* Constructor
*
* @param Sabre_DAV_Auth_Backend_Abstract $authBackend
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
* @param mixed $userUri
*/
public function __construct(Sabre_DAV_Auth_Backend_Abstract $authBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $userUri) {
$this->authBackend = $authBackend;
$this->caldavBackend = $caldavBackend;
$this->userUri = $userUri;
}
/**
* Returns the name of this object
*
* @return string
*/
public function getName() {
list(,$name) = Sabre_DAV_URLUtil::splitPath($this->userUri);
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_FileNotFound('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->userUri);
$objs = array();
foreach($calendars as $calendar) {
$objs[] = new Sabre_CalDAV_Calendar($this->authBackend, $this->caldavBackend, $calendar);
}
return $objs;
}
/**
* Creates a new calendar
*
* @param string $name
* @param string $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->userUri, $name, $properties);
}
}
@@ -0,0 +1,24 @@
<?php
/**
* This class contains the Sabre_CalDAV version constants.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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.3.0';
/**
* Stability : alpha, beta, stable
*/
const STABILITY = 'stable';
}
@@ -0,0 +1,208 @@
<?php
/**
* XML utilities for CalDAV
*
* This class contains a few static methods used for parsing certain CalDAV
* requests.
*
* @package Sabre
* @subpackage CalDAV
* @copyright Copyright (C) 2007-2010 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_XMLUtil {
/**
* This function parses the calendar-query report request body
*
* The body is quite complicated, so we're turning it into a PHP
* array.
*
* The resulting associative array has xpath expressions as keys.
* By default the xpath expressions should simply be checked for existance
* The xpath expressions can point to elements or attributes.
*
* The array values can contain a number of items, which alters the query
* filter.
*
* * time-range. Must also check if the todo or event falls within the
* specified timerange. How this is interpreted depends on
* the type of object (VTODO, VEVENT, VJOURNAL, etc)
* * is-not-defined
* Instead of checking if the attribute or element exist,
* we must check if it doesn't.
* * text-match
* Checks if the value of the attribute or element matches
* the specified value. This is actually another array with
* the 'collation', 'value' and 'negate-condition' items.
*
* Refer to the CalDAV spec for more information.
*
* @param DOMNode $domNode
* @param string $basePath used for recursive calls.
* @param array $filters used for recursive calls.
* @return array
*/
static public function parseCalendarQueryFilters($domNode,$basePath = '/c:iCalendar', &$filters = array()) {
foreach($domNode->childNodes as $child) {
switch(Sabre_DAV_XMLUtil::toClarkNotation($child)) {
case '{urn:ietf:params:xml:ns:caldav}comp-filter' :
case '{urn:ietf:params:xml:ns:caldav}prop-filter' :
$filterName = $basePath . '/' . 'c:' . strtolower($child->getAttribute('name'));
$filters[$filterName] = array();
self::parseCalendarQueryFilters($child, $filterName,$filters);
break;
case '{urn:ietf:params:xml:ns:caldav}time-range' :
if ($start = $child->getAttribute('start')) {
$start = self::parseICalendarDateTime($start);
} else {
$start = null;
}
if ($end = $child->getAttribute('end')) {
$end = self::parseICalendarDateTime($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');
}
$filters[$basePath]['time-range'] = array(
'start' => $start,
'end' => $end
);
break;
case '{urn:ietf:params:xml:ns:caldav}is-not-defined' :
$filters[$basePath]['is-not-defined'] = true;
break;
case '{urn:ietf:params:xml:ns:caldav}param-filter' :
$filterName = $basePath . '/@' . strtolower($child->getAttribute('name'));
$filters[$filterName] = array();
self::parseCalendarQueryFilters($child, $filterName, $filters);
break;
case '{urn:ietf:params:xml:ns:caldav}text-match' :
$collation = $child->getAttribute('collation');
if (!$collation) $collation = 'i;ascii-casemap';
$filters[$basePath]['text-match'] = array(
'collation' => $collation,
'negate-condition' => $child->getAttribute('negate-condition')==='yes',
'value' => $child->nodeValue,
);
break;
}
}
return $filters;
}
/**
* Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
*
* Specifying a reference timezone is optional. It will only be used
* if the non-UTC format is used. The argument is used as a reference, the
* returned DateTime object will still be in the UTC timezone.
*
* @param string $dt
* @param DateTimeZone $tz
* @return DateTime
*/
static public function parseICalendarDateTime($dt,DateTimeZone $tz = null) {
// Format is YYYYMMDD + "T" + hhmmss
$result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);
if (!$result) {
throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar datetime value is incorrect: ' . $dt);
}
if ($matches[7]==='Z' || is_null($tz)) {
$tz = new DateTimeZone('UTC');
}
$date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);
// Still resetting the timezone, to normalize everything to UTC
$date->setTimeZone(new DateTimeZone('UTC'));
return $date;
}
/**
* Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
*
* @param string $date
* @param DateTimeZone $tz
* @return DateTime
*/
static public function parseICalendarDate($date) {
// Format is YYYYMMDD
$result = preg_match('/^([1-3][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches);
if (!$result) {
throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar date value is incorrect: ' . $date);
}
$date = new DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new DateTimeZone('UTC'));
return $date;
}
/**
* Parses an iCalendar (RFC5545) formatted duration and returns a string suitable
* for strtotime or DateTime::modify.
*
* NOTE: When we require PHP 5.3 this can be replaced by the DateTimeInterval object, which
* supports ISO 8601 Intervals, which is a superset of ICalendar durations.
*
* For now though, we're just gonna live with this messy system
*
* @param string $duration
* @return string
*/
static public function parseICalendarDuration($duration) {
$result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
if (!$result) {
throw new Sabre_DAV_Exception_BadRequest('The supplied iCalendar duration value is incorrect: ' . $duration);
}
$parts = array(
'week',
'day',
'hour',
'minute',
'second',
);
$newDur = '';
foreach($parts as $part) {
if (isset($matches[$part]) && $matches[$part]) {
$newDur.=' '.$matches[$part] . ' ' . $part . 's';
}
}
$newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
return $newDur;
}
}
@@ -0,0 +1,10 @@
<html>
<head><title>IP.Board - Bulletin Board System</title></head>
<body>
<h1>403: IP.Board -&gt; Forbidden</h1>
<hr>
You have reached this page in error, please use your back button to return to the forum.
<hr>
<a href="http://www.invisionpower.com/">IP.Board</a>
</body>
</html>