708 lines
25 KiB
PHP
708 lines
25 KiB
PHP
![]() |
<?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);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|