Mercurial > hg > rc1
comparison vendor/sabre/vobject/lib/Component/VCalendar.php @ 7:430dbd5346f7
vendor sabre as distributed
author | Charlie Root |
---|---|
date | Sat, 13 Jan 2018 09:06:10 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
6:cec75ba50afc | 7:430dbd5346f7 |
---|---|
1 <?php | |
2 | |
3 namespace Sabre\VObject\Component; | |
4 | |
5 use DateTime; | |
6 use DateTimeZone; | |
7 use Sabre\VObject; | |
8 use Sabre\VObject\Component; | |
9 use Sabre\VObject\Recur\EventIterator; | |
10 use Sabre\VObject\Recur\NoInstancesException; | |
11 | |
12 /** | |
13 * The VCalendar component | |
14 * | |
15 * This component adds functionality to a component, specific for a VCALENDAR. | |
16 * | |
17 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/). | |
18 * @author Evert Pot (http://evertpot.com/) | |
19 * @license http://sabre.io/license/ Modified BSD License | |
20 */ | |
21 class VCalendar extends VObject\Document { | |
22 | |
23 /** | |
24 * The default name for this component. | |
25 * | |
26 * This should be 'VCALENDAR' or 'VCARD'. | |
27 * | |
28 * @var string | |
29 */ | |
30 static $defaultName = 'VCALENDAR'; | |
31 | |
32 /** | |
33 * This is a list of components, and which classes they should map to. | |
34 * | |
35 * @var array | |
36 */ | |
37 static $componentMap = array( | |
38 'VALARM' => 'Sabre\\VObject\\Component\\VAlarm', | |
39 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent', | |
40 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy', | |
41 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal', | |
42 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone', | |
43 'VTODO' => 'Sabre\\VObject\\Component\\VTodo', | |
44 ); | |
45 | |
46 /** | |
47 * List of value-types, and which classes they map to. | |
48 * | |
49 * @var array | |
50 */ | |
51 static $valueMap = array( | |
52 'BINARY' => 'Sabre\\VObject\\Property\\Binary', | |
53 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean', | |
54 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', | |
55 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date', | |
56 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
57 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', | |
58 'FLOAT' => 'Sabre\\VObject\\Property\\Float', | |
59 'INTEGER' => 'Sabre\\VObject\\Property\\Integer', | |
60 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period', | |
61 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', | |
62 'TEXT' => 'Sabre\\VObject\\Property\\Text', | |
63 'TIME' => 'Sabre\\VObject\\Property\\Time', | |
64 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only. | |
65 'URI' => 'Sabre\\VObject\\Property\\Uri', | |
66 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset', | |
67 ); | |
68 | |
69 /** | |
70 * List of properties, and which classes they map to. | |
71 * | |
72 * @var array | |
73 */ | |
74 static $propertyMap = array( | |
75 // Calendar properties | |
76 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText', | |
77 'METHOD' => 'Sabre\\VObject\\Property\\FlatText', | |
78 'PRODID' => 'Sabre\\VObject\\Property\\FlatText', | |
79 'VERSION' => 'Sabre\\VObject\\Property\\FlatText', | |
80 | |
81 // Component properties | |
82 'ATTACH' => 'Sabre\\VObject\\Property\\Uri', | |
83 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text', | |
84 'CLASS' => 'Sabre\\VObject\\Property\\FlatText', | |
85 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText', | |
86 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText', | |
87 'GEO' => 'Sabre\\VObject\\Property\\Float', | |
88 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText', | |
89 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\Integer', | |
90 'PRIORITY' => 'Sabre\\VObject\\Property\\Integer', | |
91 'RESOURCES' => 'Sabre\\VObject\\Property\\Text', | |
92 'STATUS' => 'Sabre\\VObject\\Property\\FlatText', | |
93 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText', | |
94 | |
95 // Date and Time Component Properties | |
96 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
97 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
98 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
99 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
100 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', | |
101 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period', | |
102 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText', | |
103 | |
104 // Time Zone Component Properties | |
105 'TZID' => 'Sabre\\VObject\\Property\\FlatText', | |
106 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText', | |
107 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset', | |
108 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset', | |
109 'TZURL' => 'Sabre\\VObject\\Property\\Uri', | |
110 | |
111 // Relationship Component Properties | |
112 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', | |
113 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText', | |
114 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress', | |
115 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
116 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText', | |
117 'URL' => 'Sabre\\VObject\\Property\\Uri', | |
118 'UID' => 'Sabre\\VObject\\Property\\FlatText', | |
119 | |
120 // Recurrence Component Properties | |
121 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
122 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
123 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', | |
124 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545 | |
125 | |
126 // Alarm Component Properties | |
127 'ACTION' => 'Sabre\\VObject\\Property\\FlatText', | |
128 'REPEAT' => 'Sabre\\VObject\\Property\\Integer', | |
129 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration', | |
130 | |
131 // Change Management Component Properties | |
132 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
133 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
134 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
135 'SEQUENCE' => 'Sabre\\VObject\\Property\\Integer', | |
136 | |
137 // Request Status | |
138 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text', | |
139 | |
140 // Additions from draft-daboo-valarm-extensions-04 | |
141 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text', | |
142 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime', | |
143 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text', | |
144 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean', | |
145 | |
146 ); | |
147 | |
148 /** | |
149 * Returns the current document type. | |
150 * | |
151 * @return void | |
152 */ | |
153 function getDocumentType() { | |
154 | |
155 return self::ICALENDAR20; | |
156 | |
157 } | |
158 | |
159 /** | |
160 * Returns a list of all 'base components'. For instance, if an Event has | |
161 * a recurrence rule, and one instance is overridden, the overridden event | |
162 * will have the same UID, but will be excluded from this list. | |
163 * | |
164 * VTIMEZONE components will always be excluded. | |
165 * | |
166 * @param string $componentName filter by component name | |
167 * @return VObject\Component[] | |
168 */ | |
169 function getBaseComponents($componentName = null) { | |
170 | |
171 $components = array(); | |
172 foreach($this->children as $component) { | |
173 | |
174 if (!$component instanceof VObject\Component) | |
175 continue; | |
176 | |
177 if (isset($component->{'RECURRENCE-ID'})) | |
178 continue; | |
179 | |
180 if ($componentName && $component->name !== strtoupper($componentName)) | |
181 continue; | |
182 | |
183 if ($component->name === 'VTIMEZONE') | |
184 continue; | |
185 | |
186 $components[] = $component; | |
187 | |
188 } | |
189 | |
190 return $components; | |
191 | |
192 } | |
193 | |
194 /** | |
195 * Returns the first component that is not a VTIMEZONE, and does not have | |
196 * an RECURRENCE-ID. | |
197 * | |
198 * If there is no such component, null will be returned. | |
199 * | |
200 * @param string $componentName filter by component name | |
201 * @return VObject\Component|null | |
202 */ | |
203 function getBaseComponent($componentName = null) { | |
204 | |
205 foreach($this->children as $component) { | |
206 | |
207 if (!$component instanceof VObject\Component) | |
208 continue; | |
209 | |
210 if (isset($component->{'RECURRENCE-ID'})) | |
211 continue; | |
212 | |
213 if ($componentName && $component->name !== strtoupper($componentName)) | |
214 continue; | |
215 | |
216 if ($component->name === 'VTIMEZONE') | |
217 continue; | |
218 | |
219 return $component; | |
220 | |
221 } | |
222 | |
223 } | |
224 | |
225 /** | |
226 * If this calendar object, has events with recurrence rules, this method | |
227 * can be used to expand the event into multiple sub-events. | |
228 * | |
229 * Each event will be stripped from it's recurrence information, and only | |
230 * the instances of the event in the specified timerange will be left | |
231 * alone. | |
232 * | |
233 * In addition, this method will cause timezone information to be stripped, | |
234 * and normalized to UTC. | |
235 * | |
236 * This method will alter the VCalendar. This cannot be reversed. | |
237 * | |
238 * This functionality is specifically used by the CalDAV standard. It is | |
239 * possible for clients to request expand events, if they are rather simple | |
240 * clients and do not have the possibility to calculate recurrences. | |
241 * | |
242 * @param DateTime $start | |
243 * @param DateTime $end | |
244 * @param DateTimeZone $timeZone reference timezone for floating dates and | |
245 * times. | |
246 * @return void | |
247 */ | |
248 function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null) { | |
249 | |
250 $newEvents = array(); | |
251 | |
252 if (!$timeZone) { | |
253 $timeZone = new DateTimeZone('UTC'); | |
254 } | |
255 | |
256 foreach($this->select('VEVENT') as $key=>$vevent) { | |
257 | |
258 if (isset($vevent->{'RECURRENCE-ID'})) { | |
259 unset($this->children[$key]); | |
260 continue; | |
261 } | |
262 | |
263 | |
264 if (!$vevent->rrule) { | |
265 unset($this->children[$key]); | |
266 if ($vevent->isInTimeRange($start, $end)) { | |
267 $newEvents[] = $vevent; | |
268 } | |
269 continue; | |
270 } | |
271 | |
272 | |
273 | |
274 $uid = (string)$vevent->uid; | |
275 if (!$uid) { | |
276 throw new \LogicException('Event did not have a UID!'); | |
277 } | |
278 | |
279 try { | |
280 $it = new EventIterator($this, $vevent->uid, $timeZone); | |
281 } catch (NoInstancesException $e) { | |
282 // This event is recurring, but it doesn't have a single | |
283 // instance. We are skipping this event from the output | |
284 // entirely. | |
285 unset($this->children[$key]); | |
286 continue; | |
287 } | |
288 $it->fastForward($start); | |
289 | |
290 while($it->valid() && $it->getDTStart() < $end) { | |
291 | |
292 if ($it->getDTEnd() > $start) { | |
293 | |
294 $newEvents[] = $it->getEventObject(); | |
295 | |
296 } | |
297 $it->next(); | |
298 | |
299 } | |
300 | |
301 unset($this->children[$key]); | |
302 | |
303 } | |
304 | |
305 // Setting all properties to UTC time. | |
306 foreach($newEvents as $newEvent) { | |
307 | |
308 foreach($newEvent->children as $child) { | |
309 if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) { | |
310 $dt = $child->getDateTimes($timeZone); | |
311 // We only need to update the first timezone, because | |
312 // setDateTimes will match all other timezones to the | |
313 // first. | |
314 $dt[0]->setTimeZone(new DateTimeZone('UTC')); | |
315 $child->setDateTimes($dt); | |
316 } | |
317 | |
318 } | |
319 | |
320 $this->add($newEvent); | |
321 | |
322 } | |
323 | |
324 // Removing all VTIMEZONE components | |
325 unset($this->VTIMEZONE); | |
326 | |
327 } | |
328 | |
329 /** | |
330 * This method should return a list of default property values. | |
331 * | |
332 * @return array | |
333 */ | |
334 protected function getDefaults() { | |
335 | |
336 return array( | |
337 'VERSION' => '2.0', | |
338 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN', | |
339 'CALSCALE' => 'GREGORIAN', | |
340 ); | |
341 | |
342 } | |
343 | |
344 /** | |
345 * A simple list of validation rules. | |
346 * | |
347 * This is simply a list of properties, and how many times they either | |
348 * must or must not appear. | |
349 * | |
350 * Possible values per property: | |
351 * * 0 - Must not appear. | |
352 * * 1 - Must appear exactly once. | |
353 * * + - Must appear at least once. | |
354 * * * - Can appear any number of times. | |
355 * | |
356 * @var array | |
357 */ | |
358 function getValidationRules() { | |
359 | |
360 return array( | |
361 'PRODID' => 1, | |
362 'VERSION' => 1, | |
363 | |
364 'CALSCALE' => '?', | |
365 'METHOD' => '?', | |
366 ); | |
367 | |
368 } | |
369 | |
370 /** | |
371 * Validates the node for correctness. | |
372 * | |
373 * The following options are supported: | |
374 * Node::REPAIR - May attempt to automatically repair the problem. | |
375 * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. | |
376 * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. | |
377 * | |
378 * This method returns an array with detected problems. | |
379 * Every element has the following properties: | |
380 * | |
381 * * level - problem level. | |
382 * * message - A human-readable string describing the issue. | |
383 * * node - A reference to the problematic node. | |
384 * | |
385 * The level means: | |
386 * 1 - The issue was repaired (only happens if REPAIR was turned on). | |
387 * 2 - A warning. | |
388 * 3 - An error. | |
389 * | |
390 * @param int $options | |
391 * @return array | |
392 */ | |
393 function validate($options = 0) { | |
394 | |
395 $warnings = parent::validate($options); | |
396 | |
397 if ($ver = $this->VERSION) { | |
398 if ((string)$ver !== '2.0') { | |
399 $warnings[] = array( | |
400 'level' => 3, | |
401 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', | |
402 'node' => $this, | |
403 ); | |
404 } | |
405 | |
406 } | |
407 | |
408 $uidList = array(); | |
409 | |
410 $componentsFound = 0; | |
411 | |
412 $componentTypes = array(); | |
413 | |
414 foreach($this->children as $child) { | |
415 if($child instanceof Component) { | |
416 $componentsFound++; | |
417 | |
418 if (!in_array($child->name, array('VEVENT', 'VTODO', 'VJOURNAL'))) { | |
419 continue; | |
420 } | |
421 $componentTypes[] = $child->name; | |
422 | |
423 $uid = (string)$child->UID; | |
424 $isMaster = isset($child->{'RECURRENCE-ID'})?0:1; | |
425 if (isset($uidList[$uid])) { | |
426 $uidList[$uid]['count']++; | |
427 if ($isMaster && $uidList[$uid]['hasMaster']) { | |
428 $warnings[] = array( | |
429 'level' => 3, | |
430 'message' => 'More than one master object was found for the object with UID ' . $uid, | |
431 'node' => $this, | |
432 ); | |
433 } | |
434 $uidList[$uid]['hasMaster']+=$isMaster; | |
435 } else { | |
436 $uidList[$uid] = array( | |
437 'count' => 1, | |
438 'hasMaster' => $isMaster, | |
439 ); | |
440 } | |
441 | |
442 } | |
443 } | |
444 | |
445 if ($componentsFound===0) { | |
446 $warnings[] = array( | |
447 'level' => 3, | |
448 'message' => 'An iCalendar object must have at least 1 component.', | |
449 'node' => $this, | |
450 ); | |
451 } | |
452 | |
453 if ($options & self::PROFILE_CALDAV) { | |
454 if (count($uidList)>1) { | |
455 $warnings[] = array( | |
456 'level' => 3, | |
457 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.', | |
458 'node' => $this, | |
459 ); | |
460 } | |
461 if (count(array_unique($componentTypes))===0) { | |
462 $warnings[] = array( | |
463 'level' => 3, | |
464 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).', | |
465 'node' => $this, | |
466 ); | |
467 } | |
468 if (count(array_unique($componentTypes))>1) { | |
469 $warnings[] = array( | |
470 'level' => 3, | |
471 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).', | |
472 'node' => $this, | |
473 ); | |
474 } | |
475 | |
476 if (isset($this->METHOD)) { | |
477 $warnings[] = array( | |
478 'level' => 3, | |
479 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.', | |
480 'node' => $this, | |
481 ); | |
482 } | |
483 } | |
484 | |
485 return $warnings; | |
486 | |
487 } | |
488 | |
489 } | |
490 |