]> git.mxchange.org Git - friendica-addons.git/blob - dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryParser.php
Merge branch 'master' of git://github.com/friendica/friendica-addons
[friendica-addons.git] / dav / SabreDAV / lib / Sabre / CalDAV / CalendarQueryParser.php
1 <?php
2
3 /**
4  * Parses the calendar-query report request body.
5  *
6  * Whoever designed this format, and the CalDAV equivalent even more so,
7  * has no feel for design.
8  *
9  * @package Sabre
10  * @subpackage CalDAV
11  * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
12  * @author Evert Pot (http://www.rooftopsolutions.nl/)
13  * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
14  */
15 class Sabre_CalDAV_CalendarQueryParser {
16
17     /**
18      * List of requested properties the client wanted
19      *
20      * @var array
21      */
22     public $requestedProperties;
23
24     /**
25      * List of property/component filters.
26      *
27      * @var array
28      */
29     public $filters;
30
31     /**
32      * This property will contain null if CALDAV:expand was not specified, 
33      * otherwise it will contain an array with 2 elements (start, end). Each 
34      * contain a DateTime object.
35      *
36      * If expand is specified, recurring calendar objects are to be expanded 
37      * into their individual components, and only the components that fall 
38      * within the specified time-range are to be returned.
39      *
40      * For more details, see rfc4791, section 9.6.5.
41      * 
42      * @var null|array 
43      */
44     public $expand;
45
46     /**
47      * DOM Document
48      *
49      * @var DOMDocument
50      */
51     protected $dom;
52
53     /**
54      * DOM XPath object
55      *
56      * @var DOMXPath
57      */
58     protected $xpath;
59
60     /**
61      * Creates the parser
62      *
63      * @param DOMDocument $dom
64      */
65     public function __construct(DOMDocument $dom) {
66
67         $this->dom = $dom;
68
69         $this->xpath = new DOMXPath($dom);
70         $this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
71         $this->xpath->registerNameSpace('dav','urn:DAV');
72
73     }
74
75     /**
76      * Parses the request.
77      *
78      * @return void
79      */
80     public function parse() {
81
82         $filterNode = null;
83
84         $filter = $this->xpath->query('/cal:calendar-query/cal:filter');
85         if ($filter->length !== 1) {
86             throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
87         }
88
89         $compFilters = $this->parseCompFilters($filter->item(0));
90         if (count($compFilters)!==1) {
91             throw new Sabre_DAV_Exception_BadRequest('There must be exactly 1 top-level comp-filter.');
92         }
93
94         $this->filters = $compFilters[0];
95         $this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
96
97         $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
98         if ($expand->length>0) {
99             $this->expand = $this->parseExpand($expand->item(0));
100         }
101              
102
103     }
104
105     /**
106      * Parses all the 'comp-filter' elements from a node
107      *
108      * @param DOMElement $parentNode
109      * @return array
110      */
111     protected function parseCompFilters(DOMElement $parentNode) {
112
113         $compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
114         $result = array();
115
116         for($ii=0; $ii < $compFilterNodes->length; $ii++) {
117
118             $compFilterNode = $compFilterNodes->item($ii);
119
120             $compFilter = array();
121             $compFilter['name'] = $compFilterNode->getAttribute('name');
122             $compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
123             $compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
124             $compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
125             $compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
126
127             if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
128                 'VEVENT',
129                 'VTODO',
130                 'VJOURNAL',
131                 'VFREEBUSY',
132                 'VALARM',
133             ))) {
134                 throw new Sabre_DAV_Exception_BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
135             };
136
137             $result[] = $compFilter;
138
139         }
140
141         return $result;
142
143     }
144
145     /**
146      * Parses all the prop-filter elements from a node
147      *
148      * @param DOMElement $parentNode
149      * @return array
150      */
151     protected function parsePropFilters(DOMElement $parentNode) {
152
153         $propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
154         $result = array();
155
156         for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
157
158             $propFilterNode = $propFilterNodes->item($ii);
159             $propFilter = array();
160             $propFilter['name'] = $propFilterNode->getAttribute('name');
161             $propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
162             $propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
163             $propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
164             $propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
165
166             $result[] = $propFilter;
167
168         }
169
170         return $result;
171
172     }
173
174     /**
175      * Parses the param-filter element
176      *
177      * @param DOMElement $parentNode
178      * @return array
179      */
180     protected function parseParamFilters(DOMElement $parentNode) {
181
182         $paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
183         $result = array();
184
185         for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
186
187             $paramFilterNode = $paramFilterNodes->item($ii);
188             $paramFilter = array();
189             $paramFilter['name'] = $paramFilterNode->getAttribute('name');
190             $paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
191             $paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
192
193             $result[] = $paramFilter;
194
195         }
196
197         return $result;
198
199     }
200
201     /**
202      * Parses the text-match element
203      *
204      * @param DOMElement $parentNode
205      * @return array|null
206      */
207     protected function parseTextMatch(DOMElement $parentNode) {
208
209         $textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
210
211         if ($textMatchNodes->length === 0)
212             return null;
213
214         $textMatchNode = $textMatchNodes->item(0);
215         $negateCondition = $textMatchNode->getAttribute('negate-condition');
216         $negateCondition = $negateCondition==='yes';
217         $collation = $textMatchNode->getAttribute('collation');
218         if (!$collation) $collation = 'i;ascii-casemap';
219
220         return array(
221             'negate-condition' => $negateCondition,
222             'collation' => $collation,
223             'value' => $textMatchNode->nodeValue
224         );
225
226     }
227
228     /**
229      * Parses the time-range element
230      *
231      * @param DOMElement $parentNode
232      * @return array|null
233      */
234     protected function parseTimeRange(DOMElement $parentNode) {
235
236         $timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
237         if ($timeRangeNodes->length === 0) {
238             return null;
239         }
240
241         $timeRangeNode = $timeRangeNodes->item(0);
242
243         if ($start = $timeRangeNode->getAttribute('start')) {
244             $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
245         } else {
246             $start = null;
247         }
248         if ($end = $timeRangeNode->getAttribute('end')) {
249             $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
250         } else {
251             $end = null;
252         }
253
254         if (!is_null($start) && !is_null($end) && $end <= $start) {
255             throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter');
256         }
257
258         return array(
259             'start' => $start,
260             'end' => $end,
261         );
262
263     }
264
265     /**
266      * Parses the CALDAV:expand element
267      * 
268      * @param DOMElement $parentNode 
269      * @return void
270      */
271     protected function parseExpand(DOMElement $parentNode) {
272
273         $start = $parentNode->getAttribute('start');
274         if(!$start) {
275             throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element');
276         } 
277         $start = Sabre_VObject_DateTimeParser::parseDateTime($start);
278
279         $end = $parentNode->getAttribute('end');
280         if(!$end) {
281             throw new Sabre_DAV_Exception_BadRequest('The "end" attribute is required for the CALDAV:expand element');
282         } 
283         $end = Sabre_VObject_DateTimeParser::parseDateTime($end);
284         
285         if ($end <= $start) {
286             throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
287         }
288
289         return array(
290             'start' => $start,
291             'end' => $end,
292         );
293
294     }
295
296 }