]> git.mxchange.org Git - friendica-addons.git/blob - dav/SabreDAV/lib/Sabre/CalDAV/CalendarQueryValidator.php
Move friendica-specific parts into an own subdirectory
[friendica-addons.git] / dav / SabreDAV / lib / Sabre / CalDAV / CalendarQueryValidator.php
1 <?php
2
3 /**
4  * CalendarQuery Validator
5  *
6  * This class is responsible for checking if an iCalendar object matches a set
7  * of filters. The main function to do this is 'validate'.
8  *
9  * This is used to determine which icalendar objects should be returned for a
10  * calendar-query REPORT request.
11  *
12  * @package Sabre
13  * @subpackage CalDAV
14  * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
15  * @author Evert Pot (http://www.rooftopsolutions.nl/)
16  * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
17  */
18 class Sabre_CalDAV_CalendarQueryValidator {
19
20     /**
21      * Verify if a list of filters applies to the calendar data object
22      *
23      * The list of filters must be formatted as parsed by Sabre_CalDAV_CalendarQueryParser
24      *
25      * @param Sabre_VObject_Component $vObject
26      * @param array $filters
27      * @return bool
28      */
29     public function validate(Sabre_VObject_Component $vObject,array $filters) {
30
31         // The top level object is always a component filter.
32         // We'll parse it manually, as it's pretty simple.
33         if ($vObject->name !== $filters['name']) {
34             return false;
35         }
36
37         return
38             $this->validateCompFilters($vObject, $filters['comp-filters']) &&
39             $this->validatePropFilters($vObject, $filters['prop-filters']);
40
41
42     }
43
44     /**
45      * This method checks the validity of comp-filters.
46      *
47      * A list of comp-filters needs to be specified. Also the parent of the
48      * component we're checking should be specified, not the component to check
49      * itself.
50      *
51      * @param Sabre_VObject_Component $parent
52      * @param array $filters
53      * @return bool
54      */
55     protected function validateCompFilters(Sabre_VObject_Component $parent, array $filters) {
56
57         foreach($filters as $filter) {
58
59             $isDefined = isset($parent->$filter['name']);
60
61             if ($filter['is-not-defined']) {
62
63                 if ($isDefined) {
64                     return false;
65                 } else {
66                     continue;
67                 }
68
69             }
70             if (!$isDefined) {
71                 return false;
72             }
73
74             if ($filter['time-range']) {
75                 foreach($parent->$filter['name'] as $subComponent) {
76                     if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
77                         continue 2;
78                     }
79                 }
80                 return false;
81             }
82
83             if (!$filter['comp-filters'] && !$filter['prop-filters']) {
84                 continue;
85             }
86
87             // If there are sub-filters, we need to find at least one component
88             // for which the subfilters hold true.
89             foreach($parent->$filter['name'] as $subComponent) {
90
91                 if (
92                     $this->validateCompFilters($subComponent, $filter['comp-filters']) &&
93                     $this->validatePropFilters($subComponent, $filter['prop-filters'])) {
94                         // We had a match, so this comp-filter succeeds
95                         continue 2;
96                 }
97
98             }
99
100             // If we got here it means there were sub-comp-filters or
101             // sub-prop-filters and there was no match. This means this filter
102             // needs to return false.
103             return false;
104
105         }
106
107         // If we got here it means we got through all comp-filters alive so the
108         // filters were all true.
109         return true;
110
111     }
112
113     /**
114      * This method checks the validity of prop-filters.
115      *
116      * A list of prop-filters needs to be specified. Also the parent of the
117      * property we're checking should be specified, not the property to check
118      * itself.
119      *
120      * @param Sabre_VObject_Component $parent
121      * @param array $filters
122      * @return bool
123      */
124     protected function validatePropFilters(Sabre_VObject_Component $parent, array $filters) {
125
126         foreach($filters as $filter) {
127
128             $isDefined = isset($parent->$filter['name']);
129
130             if ($filter['is-not-defined']) {
131
132                 if ($isDefined) {
133                     return false;
134                 } else {
135                     continue;
136                 }
137
138             }
139             if (!$isDefined) {
140                 return false;
141             }
142
143             if ($filter['time-range']) {
144                 foreach($parent->$filter['name'] as $subComponent) {
145                     if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
146                         continue 2;
147                     }
148                 }
149                 return false;
150             }
151
152             if (!$filter['param-filters'] && !$filter['text-match']) {
153                 continue;
154             }
155
156             // If there are sub-filters, we need to find at least one property
157             // for which the subfilters hold true.
158             foreach($parent->$filter['name'] as $subComponent) {
159
160                 if(
161                     $this->validateParamFilters($subComponent, $filter['param-filters']) &&
162                     (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
163                 ) {
164                     // We had a match, so this prop-filter succeeds
165                     continue 2;
166                 }
167
168             }
169
170             // If we got here it means there were sub-param-filters or
171             // text-match filters and there was no match. This means the
172             // filter needs to return false.
173             return false;
174
175         }
176
177         // If we got here it means we got through all prop-filters alive so the
178         // filters were all true.
179         return true;
180
181     }
182
183     /**
184      * This method checks the validity of param-filters.
185      *
186      * A list of param-filters needs to be specified. Also the parent of the
187      * parameter we're checking should be specified, not the parameter to check
188      * itself.
189      *
190      * @param Sabre_VObject_Property $parent
191      * @param array $filters
192      * @return bool
193      */
194     protected function validateParamFilters(Sabre_VObject_Property $parent, array $filters) {
195
196         foreach($filters as $filter) {
197
198             $isDefined = isset($parent[$filter['name']]);
199
200             if ($filter['is-not-defined']) {
201
202                 if ($isDefined) {
203                     return false;
204                 } else {
205                     continue;
206                 }
207
208             }
209             if (!$isDefined) {
210                 return false;
211             }
212
213             if (!$filter['text-match']) {
214                 continue;
215             }
216
217             // If there are sub-filters, we need to find at least one parameter
218             // for which the subfilters hold true.
219             foreach($parent[$filter['name']] as $subParam) {
220
221                 if($this->validateTextMatch($subParam,$filter['text-match'])) {
222                     // We had a match, so this param-filter succeeds
223                     continue 2;
224                 }
225
226             }
227
228             // If we got here it means there was a text-match filter and there
229             // were no matches. This means the filter needs to return false.
230             return false;
231
232         }
233
234         // If we got here it means we got through all param-filters alive so the
235         // filters were all true.
236         return true;
237
238     }
239
240     /**
241      * This method checks the validity of a text-match.
242      *
243      * A single text-match should be specified as well as the specific property
244      * or parameter we need to validate.
245      *
246      * @param Sabre_VObject_Node $parent
247      * @param array $textMatch
248      * @return bool
249      */
250     protected function validateTextMatch(Sabre_VObject_Node $parent, array $textMatch) {
251
252         $value = (string)$parent;
253
254         $isMatching = Sabre_DAV_StringUtil::textMatch($value, $textMatch['value'], $textMatch['collation']);
255
256         return ($textMatch['negate-condition'] xor $isMatching);
257
258     }
259
260     /**
261      * Validates if a component matches the given time range.
262      *
263      * This is all based on the rules specified in rfc4791, which are quite
264      * complex.
265      *
266      * @param Sabre_VObject_Node $component
267      * @param DateTime $start
268      * @param DateTime $end
269      * @return bool
270      */
271     protected function validateTimeRange(Sabre_VObject_Node $component, $start, $end) {
272
273         if (is_null($start)) {
274             $start = new DateTime('1900-01-01');
275         }
276         if (is_null($end)) {
277             $end = new DateTime('3000-01-01');
278         }
279
280         switch($component->name) {
281
282             case 'VEVENT' :
283             case 'VTODO' :
284             case 'VJOURNAL' :
285
286                 return $component->isInTimeRange($start, $end);
287
288             case 'VALARM' :
289
290                 // If the valarm is wrapped in a recurring event, we need to
291                 // expand the recursions, and validate each.
292                 //
293                 // Our datamodel doesn't easily allow us to do this straight
294                 // in the VALARM component code, so this is a hack, and an
295                 // expensive one too.
296                 if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
297
298                     // Fire up the iterator!
299                     $it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
300                     while($it->valid()) {
301                         $expandedEvent = $it->getEventObject();
302
303                         // We need to check from these expanded alarms, which
304                         // one is the first to trigger. Based on this, we can
305                         // determine if we can 'give up' expanding events.
306                         $firstAlarm = null;
307                         if ($expandedEvent->VALARM !== null) {
308                             foreach($expandedEvent->VALARM as $expandedAlarm) {
309
310                                 $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
311                                 if ($expandedAlarm->isInTimeRange($start, $end)) {
312                                     return true;
313                                 }
314
315                                 if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
316                                     // This is an alarm with a non-relative trigger
317                                     // time, likely created by a buggy client. The
318                                     // implication is that every alarm in this
319                                     // recurring event trigger at the exact same
320                                     // time. It doesn't make sense to traverse
321                                     // further.
322                                 } else {
323                                     // We store the first alarm as a means to
324                                     // figure out when we can stop traversing.
325                                     if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
326                                         $firstAlarm = $effectiveTrigger;
327                                     }
328                                 }
329                             }
330                         }
331                         if (is_null($firstAlarm)) {
332                             // No alarm was found.
333                             //
334                             // Or technically: No alarm that will change for
335                             // every instance of the recurrence was found,
336                             // which means we can assume there was no match.
337                             return false;
338                         }
339                         if ($firstAlarm > $end) {
340                             return false;
341                         }
342                         $it->next();
343                     }
344                     return false;
345                 } else {
346                     return $component->isInTimeRange($start, $end);
347                 }
348
349             case 'VFREEBUSY' :
350                 throw new Sabre_DAV_Exception_NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
351
352             case 'COMPLETED' :
353             case 'CREATED' :
354             case 'DTEND' :
355             case 'DTSTAMP' :
356             case 'DTSTART' :
357             case 'DUE' :
358             case 'LAST-MODIFIED' :
359                 return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
360
361
362
363             default :
364                 throw new Sabre_DAV_Exception_BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
365
366         }
367
368     }
369
370 }