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