aboutsummaryrefslogtreecommitdiffstats
path: root/3rdparty/Sabre/CalDAV/Plugin.php
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/Sabre/CalDAV/Plugin.php')
-rw-r--r--3rdparty/Sabre/CalDAV/Plugin.php773
1 files changed, 407 insertions, 366 deletions
diff --git a/3rdparty/Sabre/CalDAV/Plugin.php b/3rdparty/Sabre/CalDAV/Plugin.php
index 02747c8395e..d7d1d970518 100644
--- a/3rdparty/Sabre/CalDAV/Plugin.php
+++ b/3rdparty/Sabre/CalDAV/Plugin.php
@@ -8,8 +8,8 @@
*
* @package Sabre
* @subpackage CalDAV
- * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
@@ -18,7 +18,7 @@ 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
*/
@@ -41,21 +41,48 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
const CALENDAR_ROOT = 'calendars';
/**
- * Reference to server object
- *
- * @var Sabre_DAV_Server
+ * Reference to server object
+ *
+ * @var Sabre_DAV_Server
*/
private $server;
/**
+ * The email handler for invites and other scheduling messages.
+ *
+ * @var Sabre_CalDAV_Schedule_IMip
+ */
+ protected $imipHandler;
+
+ /**
+ * Sets the iMIP handler.
+ *
+ * iMIP = The email transport of iCalendar scheduling messages. Setting
+ * this is optional, but if you want the server to allow invites to be sent
+ * out, you must set a handler.
+ *
+ * Specifically iCal will plain assume that the server supports this. If
+ * the server doesn't, iCal will display errors when inviting people to
+ * events.
+ *
+ * @param Sabre_CalDAV_Schedule_IMip $imipHandler
+ * @return void
+ */
+ public function setIMipHandler(Sabre_CalDAV_Schedule_IMip $imipHandler) {
+
+ $this->imipHandler = $imipHandler;
+
+ }
+
+ /**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
- * This method is passed a uri. It should only return HTTP methods that are
+ * This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* @param string $uri
- * @return array
+ * @return array
*/
public function getHTTPMethods($uri) {
@@ -68,7 +95,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if ($node instanceof Sabre_DAV_IExtendedCollection) {
try {
$node->getChild($name);
- } catch (Sabre_DAV_Exception_FileNotFound $e) {
+ } catch (Sabre_DAV_Exception_NotFound $e) {
return array('MKCALENDAR');
}
}
@@ -77,9 +104,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
/**
- * Returns a list of features for the DAV: HTTP header.
- *
- * @return array
+ * Returns a list of features for the DAV: HTTP header.
+ *
+ * @return array
*/
public function getFeatures() {
@@ -89,11 +116,11 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* Returns a plugin name.
- *
+ *
* Using this name other plugins will be able to access other plugins
- * using Sabre_DAV_Server::getPlugin
- *
- * @return string
+ * using Sabre_DAV_Server::getPlugin
+ *
+ * @return string
*/
public function getPluginName() {
@@ -105,38 +132,46 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
* Returns a list of reports this plugin supports.
*
* This will be used in the {DAV:}supported-report-set property.
- * Note that you still need to subscribe to the 'report' event to actually
- * implement them
- *
+ * Note that you still need to subscribe to the 'report' event to actually
+ * implement them
+ *
* @param string $uri
- * @return array
+ * @return array
*/
public function getSupportedReportSet($uri) {
$node = $this->server->tree->getNodeForPath($uri);
+
+ $reports = array();
if ($node instanceof Sabre_CalDAV_ICalendar || $node instanceof Sabre_CalDAV_ICalendarObject) {
- return array(
- '{' . self::NS_CALDAV . '}calendar-multiget',
- '{' . self::NS_CALDAV . '}calendar-query',
- );
+ $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
+ $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
}
- return array();
+ if ($node instanceof Sabre_CalDAV_ICalendar) {
+ $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
+ }
+ return $reports;
}
/**
- * Initializes the plugin
- *
- * @param Sabre_DAV_Server $server
+ * Initializes the plugin
+ *
+ * @param Sabre_DAV_Server $server
* @return void
*/
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
+
$server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
//$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000);
$server->subscribeEvent('report',array($this,'report'));
$server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties'));
+ $server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
+ $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
+ $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
+ $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
$server->xmlNamespaces[self::NS_CALDAV] = 'cal';
$server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs';
@@ -144,6 +179,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
$server->propertyMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre_CalDAV_Property_SupportedCalendarComponentSet';
$server->resourceTypeMapping['Sabre_CalDAV_ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
+ $server->resourceTypeMapping['Sabre_CalDAV_Schedule_IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox';
$server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
$server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
@@ -161,7 +197,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
'{' . self::NS_CALDAV . '}calendar-data',
// scheduling extension
+ '{' . self::NS_CALDAV . '}schedule-inbox-URL',
+ '{' . self::NS_CALDAV . '}schedule-outbox-URL',
'{' . self::NS_CALDAV . '}calendar-user-address-set',
+ '{' . self::NS_CALDAV . '}calendar-user-type',
// CalendarServer extensions
'{' . self::NS_CALENDARSERVER . '}getctag',
@@ -173,36 +212,55 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* This function handles support for the MKCALENDAR method
- *
- * @param string $method
- * @return bool
+ *
+ * @param string $method
+ * @param string $uri
+ * @return bool
*/
public function unknownMethod($method, $uri) {
- if ($method!=='MKCALENDAR') return;
+ switch ($method) {
+ case 'MKCALENDAR' :
+ $this->httpMkCalendar($uri);
+ // false is returned to stop the propagation of the
+ // unknownMethod event.
+ return false;
+ case 'POST' :
+ // Checking if we're talking to an outbox
+ try {
+ $node = $this->server->tree->getNodeForPath($uri);
+ } catch (Sabre_DAV_Exception_NotFound $e) {
+ return;
+ }
+ if (!$node instanceof Sabre_CalDAV_Schedule_IOutbox)
+ return;
- $this->httpMkCalendar($uri);
- // false is returned to stop the unknownMethod event
- return false;
+ $this->outboxRequest($node);
+ return false;
+
+ }
}
/**
- * This functions handles REPORT requests specific to CalDAV
- *
- * @param string $reportName
- * @param DOMNode $dom
- * @return bool
+ * This functions handles REPORT requests specific to CalDAV
+ *
+ * @param string $reportName
+ * @param DOMNode $dom
+ * @return bool
*/
public function report($reportName,$dom) {
- switch($reportName) {
+ switch($reportName) {
case '{'.self::NS_CALDAV.'}calendar-multiget' :
$this->calendarMultiGetReport($dom);
return false;
case '{'.self::NS_CALDAV.'}calendar-query' :
$this->calendarQueryReport($dom);
return false;
+ case '{'.self::NS_CALDAV.'}free-busy-query' :
+ $this->freeBusyQueryReport($dom);
+ return false;
}
@@ -212,9 +270,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
/**
* This function handles the MKCALENDAR HTTP method, which creates
* a new calendar.
- *
+ *
* @param string $uri
- * @return void
+ * @return void
*/
public function httpMkCalendar($uri) {
@@ -238,7 +296,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
foreach(Sabre_DAV_XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) {
$properties[$k] = $prop;
}
-
+
}
}
@@ -254,9 +312,9 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
* beforeGetProperties
*
* This method handler is invoked before any after properties for a
- * resource are fetched. This allows us to add in any CalDAV specific
- * properties.
- *
+ * resource are fetched. This allows us to add in any CalDAV specific
+ * properties.
+ *
* @param string $path
* @param Sabre_DAV_INode $node
* @param array $requestedProperties
@@ -270,12 +328,21 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
// calendar-home-set property
$calHome = '{' . self::NS_CALDAV . '}calendar-home-set';
if (in_array($calHome,$requestedProperties)) {
- $principalId = $node->getName();
+ $principalId = $node->getName();
$calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/';
unset($requestedProperties[$calHome]);
$returnedProperties[200][$calHome] = new Sabre_DAV_Property_Href($calendarHomePath);
}
+ // schedule-outbox-URL property
+ $scheduleProp = '{' . self::NS_CALDAV . '}schedule-outbox-URL';
+ if (in_array($scheduleProp,$requestedProperties)) {
+ $principalId = $node->getName();
+ $outboxPath = self::CALENDAR_ROOT . '/' . $principalId . '/outbox';
+ unset($requestedProperties[$scheduleProp]);
+ $returnedProperties[200][$scheduleProp] = new Sabre_DAV_Property_Href($outboxPath);
+ }
+
// calendar-user-address-set property
$calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set';
if (in_array($calProp,$requestedProperties)) {
@@ -287,7 +354,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
- // These two properties are shortcuts for ical to easily find
+ // These two properties are shortcuts for ical to easily find
// other principals this principal has access to.
$propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
$propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
@@ -301,8 +368,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
$groupNode = $this->server->tree->getNodeForPath($group);
- // If the node is either ap proxy-read or proxy-write
- // group, we grab the parent principal and add it to the
+ // If the node is either ap proxy-read or proxy-write
+ // group, we grab the parent principal and add it to the
// list.
if ($groupNode instanceof Sabre_CalDAV_Principal_ProxyRead) {
list($readList[]) = Sabre_DAV_URLUtil::splitPath($group);
@@ -327,8 +394,8 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
if ($node instanceof Sabre_CalDAV_ICalendarObject) {
- // The calendar-data property is not supposed to be a 'real'
- // property, but in large chunks of the spec it does act as such.
+ // The calendar-data property is not supposed to be a 'real'
+ // property, but in large chunks of the spec it does act as such.
// Therefore we simply expose it as a property.
$calDataProp = '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data';
if (in_array($calDataProp, $requestedProperties)) {
@@ -350,18 +417,52 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
*
* 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
+ *
+ * @param DOMNode $dom
* @return void
*/
public function calendarMultiGetReport($dom) {
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
-
$hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
+
+ $xpath = new DOMXPath($dom);
+ $xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
+ $xpath->registerNameSpace('dav','urn:DAV');
+
+ $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand');
+ if ($expand->length>0) {
+ $expandElem = $expand->item(0);
+ $start = $expandElem->getAttribute('start');
+ $end = $expandElem->getAttribute('end');
+ if(!$start || !$end) {
+ throw new Sabre_DAV_Exception_BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element');
+ }
+ $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
+ $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
+
+ if ($end <= $start) {
+ throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
+ }
+
+ $expand = true;
+
+ } else {
+
+ $expand = false;
+
+ }
+
foreach($hrefElems as $elem) {
$uri = $this->server->calculateUri($elem->nodeValue);
list($objProps) = $this->server->getPropertiesForPath($uri,$properties);
+
+ if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
+ $vObject = Sabre_VObject_Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
+ $vObject->expand($start, $end);
+ $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
+ }
+
$propertyList[]=$objProps;
}
@@ -377,48 +478,57 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
*
* This report is used by clients to request calendar objects based on
* complex conditions.
- *
- * @param DOMNode $dom
+ *
+ * @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));
+ $parser = new Sabre_CalDAV_CalendarQueryParser($dom);
+ $parser->parse();
$requestedCalendarData = true;
+ $requestedProperties = $parser->requestedProperties;
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
+
// We always retrieve calendar-data, as we need it for filtering.
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
- // If calendar-data wasn't explicitly requested, we need to remove
+ // 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));
+ $candidateNodes = $this->server->getPropertiesForPath(
+ $this->server->getRequestUri(),
+ $requestedProperties,
+ $this->server->getHTTPDepth(0)
+ );
$verifiedNodes = array();
+ $validator = new Sabre_CalDAV_CalendarQueryValidator();
+
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 (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']))
+ continue;
+
+ $vObject = Sabre_VObject_Reader::read($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
+ if ($validator->validate($vObject,$parser->filters)) {
- 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']);
}
+ if ($parser->expand) {
+ $vObject->expand($parser->expand['start'], $parser->expand['end']);
+ $node[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
+ }
$verifiedNodes[] = $node;
- }
+ }
}
@@ -428,360 +538,291 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
-
/**
- * Verify if a list of filters applies to the calendar data object
+ * This method is responsible for parsing the request and generating the
+ * response for the CALDAV:free-busy-query REPORT.
*
- * 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
+ * @param DOMNode $dom
+ * @return void
*/
- 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' :
- case 'vfreebusy' :
- case 'valarm' :
- // TODO: not implemented
- break;
-
- /*
-
- case 'vjournal' :
- $result = $this->validateTimeRangeFilterForJournal($xml,$xpath,$filter);
- if ($result===false) return false;
- break;
- case 'vfreebusy' :
- $result = $this->validateTimeRangeFilterForFreeBusy($xml,$xpath,$filter);
- if ($result===false) return false;
- break;
- case 'valarm' :
- $result = $this->validateTimeRangeFilterForAlarm($xml,$xpath,$filter);
- if ($result===false) return false;
- break;
-
- */
-
- }
+ protected function freeBusyQueryReport(DOMNode $dom) {
- }
+ $start = null;
+ $end = null;
- if (isset($filter['text-match'])) {
- $currentString = (string)$elem;
+ foreach($dom->firstChild->childNodes as $childNode) {
- $isMatching = Sabre_DAV_StringUtil::textMatch($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;
-
+ $clark = Sabre_DAV_XMLUtil::toClarkNotation($childNode);
+ if ($clark == '{' . self::NS_CALDAV . '}time-range') {
+ $start = $childNode->getAttribute('start');
+ $end = $childNode->getAttribute('end');
+ break;
}
}
- return true;
-
- }
-
- /**
- * Checks whether a time-range filter matches an event.
- *
- * @param SimpleXMLElement $xml Event as xml object
- * @param string $currentXPath XPath to check
- * @param array $currentFilter Filter information
- * @return void
- */
- 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');
+ if ($start) {
+ $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
+ }
+ if ($end) {
+ $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
}
- // The dtstart can be both a date, or datetime property
- if ((string)$xdtstart[0]['value']==='DATE' || strlen((string)$xdtstart[0])===8) {
- $isDateTime = false;
- } else {
- $isDateTime = true;
+ if (!$start && !$end) {
+ throw new Sabre_DAV_Exception_BadRequest('The freebusy report must have a time-range filter');
}
+ $acl = $this->server->getPlugin('acl');
- // Determining the timezone
- if ($tzid = (string)$xdtstart[0]['tzid']) {
- $tz = new DateTimeZone($tzid);
- } else {
- $tz = null;
+ if (!$acl) {
+ throw new Sabre_DAV_Exception('The ACL plugin must be loaded for free-busy queries to work');
}
- if ($isDateTime) {
- $dtstart = Sabre_CalDAV_XMLUtil::parseICalendarDateTime((string)$xdtstart[0],$tz);
- } else {
- $dtstart = Sabre_CalDAV_XMLUtil::parseICalendarDate((string)$xdtstart[0]);
+ $uri = $this->server->getRequestUri();
+ $acl->checkPrivileges($uri,'{' . self::NS_CALDAV . '}read-free-busy');
+
+ $calendar = $this->server->tree->getNodeForPath($uri);
+ if (!$calendar instanceof Sabre_CalDAV_ICalendar) {
+ throw new Sabre_DAV_Exception_NotImplemented('The free-busy-query REPORT is only implemented on calendars');
}
+ $objects = array_map(function($child) {
+ $obj = $child->get();
+ if (is_resource($obj)) {
+ $obj = stream_get_contents($obj);
+ }
+ return $obj;
+ }, $calendar->getChildren());
- // Grabbing the DTEND property
- $xdtend = $xml->xpath($currentXPath.'/c:dtend');
- $dtend = null;
+ $generator = new Sabre_VObject_FreeBusyGenerator();
+ $generator->setObjects($objects);
+ $generator->setTimeRange($start, $end);
+ $result = $generator->getResult();
+ $result = $result->serialize();
- if (count($xdtend)) {
- // Determining the timezone
- if ($tzid = (string)$xdtend[0]['tzid']) {
- $tz = new DateTimeZone($tzid);
- } else {
- $tz = null;
- }
+ $this->server->httpResponse->sendStatus(200);
+ $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
+ $this->server->httpResponse->setHeader('Content-Length', strlen($result));
+ $this->server->httpResponse->sendBody($result);
- // Since the VALUE prameter 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;
- }
+ /**
+ * This method is triggered before a file gets updated with new content.
+ *
+ * This plugin uses this method to ensure that CalDAV objects receive
+ * valid calendar data.
+ *
+ * @param string $path
+ * @param Sabre_DAV_IFile $node
+ * @param resource $data
+ * @return void
+ */
+ public function beforeWriteContent($path, Sabre_DAV_IFile $node, &$data) {
- }
- }
+ if (!$node instanceof Sabre_CalDAV_ICalendarObject)
+ return;
+
+ $this->validateICalendar($data);
- if (is_null($dtend)) {
- if ($isDateTime) {
- // DTEND = DTSTART
- $dtend = $dtstart;
- } else {
- // DTEND = DTSTART + 1 DAY
- $dtend = clone $dtstart;
- $dtend->modify('+1 day');
- }
- }
- // TODO: we need to properly parse RRULE's, but it's very difficult.
- // For now, we're always returning events if they have an RRULE at all.
- $rrule = $xml->xpath($currentXPath.'/c:rrule');
- $hasRrule = (count($rrule))>0;
-
- 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 && !$hasRrule) return false;
- return true;
-
}
- private function validateTimeRangeFilterForTodo(SimpleXMLElement $xml,$currentXPath,array $filter) {
+ /**
+ * This method is triggered before a new file is created.
+ *
+ * This plugin uses this method to ensure that newly created calendar
+ * objects contain valid calendar data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param Sabre_DAV_ICollection $parentNode
+ * @return void
+ */
+ public function beforeCreateFile($path, &$data, Sabre_DAV_ICollection $parentNode) {
- // Gathering all relevant elements
+ if (!$parentNode instanceof Sabre_CalDAV_Calendar)
+ return;
- $dtStart = null;
- $duration = null;
- $due = null;
- $completed = null;
- $created = null;
+ $this->validateICalendar($data);
- $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]);
- }
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @return void
+ */
+ protected function validateICalendar(&$data) {
+
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
}
- // Only need to grab duration if dtStart is set
- if (!is_null($dtStart)) {
+ // Converting the data to unicode, if needed.
+ $data = Sabre_DAV_StringUtil::ensureUTF8($data);
- $xduration = $xml->xpath($currentXPath.'/c:duration');
- if (count($xduration)) {
- $duration = Sabre_CalDAV_XMLUtil::parseICalendarDuration((string)$xduration[0]);
- }
+ try {
+
+ $vobj = Sabre_VObject_Reader::read($data);
+
+ } catch (Sabre_VObject_ParseException $e) {
+
+ throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
}
- if (!is_null($dtStart) && !is_null($duration)) {
+ }
- // Comparision from RFC 4791:
- // (start <= DTSTART+DURATION) AND ((end > DTSTART) OR (end >= DTSTART+DURATION))
+ /**
+ * This method handles POST requests to the schedule-outbox
+ *
+ * @param Sabre_CalDAV_Schedule_IOutbox $outboxNode
+ * @return void
+ */
+ public function outboxRequest(Sabre_CalDAV_Schedule_IOutbox $outboxNode) {
- $end = clone $dtStart;
- $end->modify($duration);
+ $originator = $this->server->httpRequest->getHeader('Originator');
+ $recipients = $this->server->httpRequest->getHeader('Recipient');
- 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;
- }
+ if (!$originator) {
+ throw new Sabre_DAV_Exception_BadRequest('The Originator: header must be specified when making POST requests');
+ }
+ if (!$recipients) {
+ throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests');
+ }
+ if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) {
+ throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address');
}
+ $originator = substr($originator,7);
- // 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]);
+ $recipients = explode(',',$recipients);
+ foreach($recipients as $k=>$recipient) {
+
+ $recipient = trim($recipient);
+ if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) {
+ throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address');
}
+ $recipient = substr($recipient, 7);
+ $recipients[$k] = $recipient;
}
- 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;
- }
+ // We need to make sure that 'originator' matches one of the email
+ // addresses of the selected principal.
+ $principal = $outboxNode->getOwner();
+ $props = $this->server->getProperties($principal,array(
+ '{' . self::NS_CALDAV . '}calendar-user-address-set',
+ ));
+ $addresses = array();
+ if (isset($props['{' . self::NS_CALDAV . '}calendar-user-address-set'])) {
+ $addresses = $props['{' . self::NS_CALDAV . '}calendar-user-address-set']->getHrefs();
}
- if (!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 (!in_array('mailto:' . $originator, $addresses)) {
+ throw new Sabre_DAV_Exception_Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header');
+ }
+ try {
+ $vObject = Sabre_VObject_Reader::read($this->server->httpRequest->getBody(true));
+ } catch (Sabre_VObject_ParseException $e) {
+ throw new Sabre_DAV_Exception_BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
}
- 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;
+ // Checking for the object type
+ $componentType = null;
+ foreach($vObject->getComponents() as $component) {
+ if ($component->name !== 'VTIMEZONE') {
+ $componentType = $component->name;
+ break;
}
-
- }
- // 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($componentType)) {
+ throw new Sabre_DAV_Exception_BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
}
- 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;
- }
+ // Validating the METHOD
+ $method = strtoupper((string)$vObject->METHOD);
+ if (!$method) {
+ throw new Sabre_DAV_Exception_BadRequest('A METHOD property must be specified in iTIP messages');
}
- if (!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 (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') {
+ $this->iMIPMessage($originator, $recipients, $vObject);
+ $this->server->httpResponse->sendStatus(200);
+ $this->server->httpResponse->sendBody('Messages sent');
+ } else {
+ throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented');
}
- 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;
- }
+ }
+
+ /**
+ * Sends an iMIP message by email.
+ *
+ * @param string $originator
+ * @param array $recipients
+ * @param Sabre_VObject_Component $vObject
+ * @return void
+ */
+ protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject) {
+
+ if (!$this->imipHandler) {
+ throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.');
}
+ $this->imipHandler->sendMessage($originator, $recipients, $vObject);
+
+ }
+
+ /**
+ * This method is used to generate HTML output for the
+ * Sabre_DAV_Browser_Plugin. This allows us to generate an interface users
+ * can use to create new calendars.
+ *
+ * @param Sabre_DAV_INode $node
+ * @param string $output
+ * @return bool
+ */
+ public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
+
+ if (!$node instanceof Sabre_CalDAV_UserCalendars)
+ return;
+
+ $output.= '<tr><td colspan="2"><form method="post" action="">
+ <h3>Create new calendar</h3>
+ <input type="hidden" name="sabreAction" value="mkcalendar" />
+ <label>Name (uri):</label> <input type="text" name="name" /><br />
+ <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
+ <input type="submit" value="create" />
+ </form>
+ </td></tr>';
+
+ return false;
- // Everything else is TRUE
- return true;
+ }
+
+ /**
+ * This method allows us to intercept the 'mkcalendar' sabreAction. This
+ * action enables the user to create new calendars from the browser plugin.
+ *
+ * @param string $uri
+ * @param string $action
+ * @param array $postVars
+ * @return bool
+ */
+ public function browserPostAction($uri, $action, array $postVars) {
+
+ if ($action!=='mkcalendar')
+ return;
+
+ $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');
+ $properties = array();
+ if (isset($postVars['{DAV:}displayname'])) {
+ $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname'];
+ }
+ $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties);
+ return false;
}