299 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			299 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | |
| 
 | |
| namespace Sabre\CalDAV;
 | |
| 
 | |
| use Sabre\VObject;
 | |
| 
 | |
| /**
 | |
|  * Parses the calendar-query report request body.
 | |
|  *
 | |
|  * Whoever designed this format, and the CalDAV equivalent even more so,
 | |
|  * has no feel for design.
 | |
|  *
 | |
|  * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
 | |
|  * @author Evert Pot (http://evertpot.com/)
 | |
|  * @license http://sabre.io/license/ Modified BSD License
 | |
|  */
 | |
| class 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',Plugin::NS_CALDAV);
 | |
|         $this->xpath->registerNameSpace('dav','urn:DAV');
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses the request.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function parse() {
 | |
| 
 | |
|         $filterNode = null;
 | |
| 
 | |
|         $filter = $this->xpath->query('/cal:calendar-query/cal:filter');
 | |
|         if ($filter->length !== 1) {
 | |
|             throw new \Sabre\DAV\Exception\BadRequest('Only one filter element is allowed');
 | |
|         }
 | |
| 
 | |
|         $compFilters = $this->parseCompFilters($filter->item(0));
 | |
|         if (count($compFilters)!==1) {
 | |
|             throw new \Sabre\DAV\Exception\BadRequest('There must be exactly 1 top-level comp-filter.');
 | |
|         }
 | |
| 
 | |
|         $this->filters = $compFilters[0];
 | |
|         $this->requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($this->dom->firstChild));
 | |
| 
 | |
|         $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
 | |
|         if ($expand->length>0) {
 | |
|             $this->expand = $this->parseExpand($expand->item(0));
 | |
|         }
 | |
| 
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses all the 'comp-filter' elements from a node
 | |
|      *
 | |
|      * @param \DOMElement $parentNode
 | |
|      * @return array
 | |
|      */
 | |
|     protected function parseCompFilters(\DOMElement $parentNode) {
 | |
| 
 | |
|         $compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
 | |
|         $result = array();
 | |
| 
 | |
|         for($ii=0; $ii < $compFilterNodes->length; $ii++) {
 | |
| 
 | |
|             $compFilterNode = $compFilterNodes->item($ii);
 | |
| 
 | |
|             $compFilter = array();
 | |
|             $compFilter['name'] = $compFilterNode->getAttribute('name');
 | |
|             $compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
 | |
|             $compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
 | |
|             $compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
 | |
|             $compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
 | |
| 
 | |
|             if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
 | |
|                 'VEVENT',
 | |
|                 'VTODO',
 | |
|                 'VJOURNAL',
 | |
|                 'VFREEBUSY',
 | |
|                 'VALARM',
 | |
|             ))) {
 | |
|                 throw new \Sabre\DAV\Exception\BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
 | |
|             };
 | |
| 
 | |
|             $result[] = $compFilter;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         return $result;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses all the prop-filter elements from a node
 | |
|      *
 | |
|      * @param \DOMElement $parentNode
 | |
|      * @return array
 | |
|      */
 | |
|     protected function parsePropFilters(\DOMElement $parentNode) {
 | |
| 
 | |
|         $propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
 | |
|         $result = array();
 | |
| 
 | |
|         for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
 | |
| 
 | |
|             $propFilterNode = $propFilterNodes->item($ii);
 | |
|             $propFilter = array();
 | |
|             $propFilter['name'] = $propFilterNode->getAttribute('name');
 | |
|             $propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
 | |
|             $propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
 | |
|             $propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
 | |
|             $propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
 | |
| 
 | |
|             $result[] = $propFilter;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         return $result;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses the param-filter element
 | |
|      *
 | |
|      * @param \DOMElement $parentNode
 | |
|      * @return array
 | |
|      */
 | |
|     protected function parseParamFilters(\DOMElement $parentNode) {
 | |
| 
 | |
|         $paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
 | |
|         $result = array();
 | |
| 
 | |
|         for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
 | |
| 
 | |
|             $paramFilterNode = $paramFilterNodes->item($ii);
 | |
|             $paramFilter = array();
 | |
|             $paramFilter['name'] = $paramFilterNode->getAttribute('name');
 | |
|             $paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
 | |
|             $paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
 | |
| 
 | |
|             $result[] = $paramFilter;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         return $result;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses the text-match element
 | |
|      *
 | |
|      * @param \DOMElement $parentNode
 | |
|      * @return array|null
 | |
|      */
 | |
|     protected function parseTextMatch(\DOMElement $parentNode) {
 | |
| 
 | |
|         $textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
 | |
| 
 | |
|         if ($textMatchNodes->length === 0)
 | |
|             return null;
 | |
| 
 | |
|         $textMatchNode = $textMatchNodes->item(0);
 | |
|         $negateCondition = $textMatchNode->getAttribute('negate-condition');
 | |
|         $negateCondition = $negateCondition==='yes';
 | |
|         $collation = $textMatchNode->getAttribute('collation');
 | |
|         if (!$collation) $collation = 'i;ascii-casemap';
 | |
| 
 | |
|         return array(
 | |
|             'negate-condition' => $negateCondition,
 | |
|             'collation' => $collation,
 | |
|             'value' => $textMatchNode->nodeValue
 | |
|         );
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses the time-range element
 | |
|      *
 | |
|      * @param \DOMElement $parentNode
 | |
|      * @return array|null
 | |
|      */
 | |
|     protected function parseTimeRange(\DOMElement $parentNode) {
 | |
| 
 | |
|         $timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
 | |
|         if ($timeRangeNodes->length === 0) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         $timeRangeNode = $timeRangeNodes->item(0);
 | |
| 
 | |
|         if ($start = $timeRangeNode->getAttribute('start')) {
 | |
|             $start = VObject\DateTimeParser::parseDateTime($start);
 | |
|         } else {
 | |
|             $start = null;
 | |
|         }
 | |
|         if ($end = $timeRangeNode->getAttribute('end')) {
 | |
|             $end = VObject\DateTimeParser::parseDateTime($end);
 | |
|         } else {
 | |
|             $end = null;
 | |
|         }
 | |
| 
 | |
|         if (!is_null($start) && !is_null($end) && $end <= $start) {
 | |
|             throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the time-range filter');
 | |
|         }
 | |
| 
 | |
|         return array(
 | |
|             'start' => $start,
 | |
|             'end' => $end,
 | |
|         );
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Parses the CALDAV:expand element
 | |
|      *
 | |
|      * @param \DOMElement $parentNode
 | |
|      * @return void
 | |
|      */
 | |
|     protected function parseExpand(\DOMElement $parentNode) {
 | |
| 
 | |
|         $start = $parentNode->getAttribute('start');
 | |
|         if(!$start) {
 | |
|             throw new \Sabre\DAV\Exception\BadRequest('The "start" attribute is required for the CALDAV:expand element');
 | |
|         }
 | |
|         $start = VObject\DateTimeParser::parseDateTime($start);
 | |
| 
 | |
|         $end = $parentNode->getAttribute('end');
 | |
|         if(!$end) {
 | |
|             throw new \Sabre\DAV\Exception\BadRequest('The "end" attribute is required for the CALDAV:expand element');
 | |
|         }
 | |
| 
 | |
|         $end = VObject\DateTimeParser::parseDateTime($end);
 | |
| 
 | |
|         if ($end <= $start) {
 | |
|             throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.');
 | |
|         }
 | |
| 
 | |
|         return array(
 | |
|             'start' => $start,
 | |
|             'end' => $end,
 | |
|         );
 | |
| 
 | |
|     }
 | |
| 
 | |
| }
 |