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