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