ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
VCalendar.php
Go to the documentation of this file.
1<?php
2
4
5use DateTimeInterface;
6use DateTimeZone;
13
24
32 static $defaultName = 'VCALENDAR';
33
39 static $componentMap = [
40 'VCALENDAR' => 'Sabre\\VObject\\Component\\VCalendar',
41 'VALARM' => 'Sabre\\VObject\\Component\\VAlarm',
42 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent',
43 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
44 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability',
45 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available',
46 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal',
47 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone',
48 'VTODO' => 'Sabre\\VObject\\Component\\VTodo',
49 ];
50
56 static $valueMap = [
57 'BINARY' => 'Sabre\\VObject\\Property\\Binary',
58 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
59 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
60 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date',
61 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
62 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
63 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
64 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
65 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
66 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
67 'TEXT' => 'Sabre\\VObject\\Property\\Text',
68 'TIME' => 'Sabre\\VObject\\Property\\Time',
69 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
70 'URI' => 'Sabre\\VObject\\Property\\Uri',
71 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
72 ];
73
79 static $propertyMap = [
80 // Calendar properties
81 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText',
82 'METHOD' => 'Sabre\\VObject\\Property\\FlatText',
83 'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
84 'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
85
86 // Component properties
87 'ATTACH' => 'Sabre\\VObject\\Property\\Uri',
88 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
89 'CLASS' => 'Sabre\\VObject\\Property\\FlatText',
90 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText',
91 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText',
92 'GEO' => 'Sabre\\VObject\\Property\\FloatValue',
93 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText',
94 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue',
95 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue',
96 'RESOURCES' => 'Sabre\\VObject\\Property\\Text',
97 'STATUS' => 'Sabre\\VObject\\Property\\FlatText',
98 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText',
99
100 // Date and Time Component Properties
101 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
102 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
103 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
104 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
105 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
106 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
107 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText',
108
109 // Time Zone Component Properties
110 'TZID' => 'Sabre\\VObject\\Property\\FlatText',
111 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText',
112 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset',
113 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset',
114 'TZURL' => 'Sabre\\VObject\\Property\\Uri',
115
116 // Relationship Component Properties
117 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
118 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText',
119 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
120 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
121 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText',
122 'URL' => 'Sabre\\VObject\\Property\\Uri',
123 'UID' => 'Sabre\\VObject\\Property\\FlatText',
124
125 // Recurrence Component Properties
126 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
127 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
128 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
129 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
130
131 // Alarm Component Properties
132 'ACTION' => 'Sabre\\VObject\\Property\\FlatText',
133 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue',
134 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
135
136 // Change Management Component Properties
137 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
138 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
139 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
140 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue',
141
142 // Request Status
143 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
144
145 // Additions from draft-daboo-valarm-extensions-04
146 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text',
147 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
148 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text',
149 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean',
150
151 // Additions from draft-daboo-calendar-availability-05
152 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text',
153
154 ];
155
161 function getDocumentType() {
162
163 return self::ICALENDAR20;
164
165 }
166
178 function getBaseComponents($componentName = null) {
179
180 $isBaseComponent = function($component) {
181
182 if (!$component instanceof VObject\Component) {
183 return false;
184 }
185 if ($component->name === 'VTIMEZONE') {
186 return false;
187 }
188 if (isset($component->{'RECURRENCE-ID'})) {
189 return false;
190 }
191 return true;
192
193 };
194
195 if ($componentName) {
196 // Early exit
197 return array_filter(
198 $this->select($componentName),
199 $isBaseComponent
200 );
201 }
202
203 $components = [];
204 foreach ($this->children as $childGroup) {
205
206 foreach ($childGroup as $child) {
207
208 if (!$child instanceof Component) {
209 // If one child is not a component, they all are so we skip
210 // the entire group.
211 continue 2;
212 }
213 if ($isBaseComponent($child)) {
214 $components[] = $child;
215 }
216
217 }
218
219 }
220 return $components;
221
222 }
223
234 function getBaseComponent($componentName = null) {
235
236 $isBaseComponent = function($component) {
237
238 if (!$component instanceof VObject\Component) {
239 return false;
240 }
241 if ($component->name === 'VTIMEZONE') {
242 return false;
243 }
244 if (isset($component->{'RECURRENCE-ID'})) {
245 return false;
246 }
247 return true;
248
249 };
250
251 if ($componentName) {
252 foreach ($this->select($componentName) as $child) {
253 if ($isBaseComponent($child)) {
254 return $child;
255 }
256 }
257 return null;
258 }
259
260 // Searching all components
261 foreach ($this->children as $childGroup) {
262 foreach ($childGroup as $child) {
263 if ($isBaseComponent($child)) {
264 return $child;
265 }
266 }
267
268 }
269 return null;
270
271 }
272
293 function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null) {
294
295 $newChildren = [];
296 $recurringEvents = [];
297
298 if (!$timeZone) {
299 $timeZone = new DateTimeZone('UTC');
300 }
301
302 $stripTimezones = function(Component $component) use ($timeZone, &$stripTimezones) {
303
304 foreach ($component->children() as $componentChild) {
305 if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) {
306
307 $dt = $componentChild->getDateTimes($timeZone);
308 // We only need to update the first timezone, because
309 // setDateTimes will match all other timezones to the
310 // first.
311 $dt[0] = $dt[0]->setTimeZone(new DateTimeZone('UTC'));
312 $componentChild->setDateTimes($dt);
313 } elseif ($componentChild instanceof Component) {
314 $stripTimezones($componentChild);
315 }
316
317 }
318 return $component;
319
320 };
321
322 foreach ($this->children() as $child) {
323
324 if ($child instanceof Property && $child->name !== 'PRODID') {
325 // We explictly want to ignore PRODID, because we want to
326 // overwrite it with our own.
327 $newChildren[] = clone $child;
328 } elseif ($child instanceof Component && $child->name !== 'VTIMEZONE') {
329
330 // We're also stripping all VTIMEZONE objects because we're
331 // converting everything to UTC.
332 if ($child->name === 'VEVENT' && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) {
333 // Handle these a bit later.
334 $uid = (string)$child->UID;
335 if (!$uid) {
336 throw new InvalidDataException('Every VEVENT object must have a UID property');
337 }
338 if (isset($recurringEvents[$uid])) {
339 $recurringEvents[$uid][] = clone $child;
340 } else {
341 $recurringEvents[$uid] = [clone $child];
342 }
343 } elseif ($child->name === 'VEVENT' && $child->isInTimeRange($start, $end)) {
344 $newChildren[] = $stripTimezones(clone $child);
345 }
346
347 }
348
349 }
350
351 foreach ($recurringEvents as $events) {
352
353 try {
354 $it = new EventIterator($events, $timeZone);
355
356 } catch (NoInstancesException $e) {
357 // This event is recurring, but it doesn't have a single
358 // instance. We are skipping this event from the output
359 // entirely.
360 continue;
361 }
362 $it->fastForward($start);
363
364 while ($it->valid() && $it->getDTStart() < $end) {
365
366 if ($it->getDTEnd() > $start) {
367
368 $newChildren[] = $stripTimezones($it->getEventObject());
369
370 }
371 $it->next();
372
373 }
374
375 }
376
377 return new self($newChildren);
378
379 }
380
386 protected function getDefaults() {
387
388 return [
389 'VERSION' => '2.0',
390 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
391 'CALSCALE' => 'GREGORIAN',
392 ];
393
394 }
395
412
413 return [
414 'PRODID' => 1,
415 'VERSION' => 1,
416
417 'CALSCALE' => '?',
418 'METHOD' => '?',
419 ];
420
421 }
422
447 function validate($options = 0) {
448
449 $warnings = parent::validate($options);
450
451 if ($ver = $this->VERSION) {
452 if ((string)$ver !== '2.0') {
453 $warnings[] = [
454 'level' => 3,
455 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
456 'node' => $this,
457 ];
458 }
459
460 }
461
462 $uidList = [];
463 $componentsFound = 0;
464 $componentTypes = [];
465
466 foreach ($this->children() as $child) {
467 if ($child instanceof Component) {
468 $componentsFound++;
469
470 if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
471 continue;
472 }
473 $componentTypes[] = $child->name;
474
475 $uid = (string)$child->UID;
476 $isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1;
477 if (isset($uidList[$uid])) {
478 $uidList[$uid]['count']++;
479 if ($isMaster && $uidList[$uid]['hasMaster']) {
480 $warnings[] = [
481 'level' => 3,
482 'message' => 'More than one master object was found for the object with UID ' . $uid,
483 'node' => $this,
484 ];
485 }
486 $uidList[$uid]['hasMaster'] += $isMaster;
487 } else {
488 $uidList[$uid] = [
489 'count' => 1,
490 'hasMaster' => $isMaster,
491 ];
492 }
493
494 }
495 }
496
497 if ($componentsFound === 0) {
498 $warnings[] = [
499 'level' => 3,
500 'message' => 'An iCalendar object must have at least 1 component.',
501 'node' => $this,
502 ];
503 }
504
505 if ($options & self::PROFILE_CALDAV) {
506 if (count($uidList) > 1) {
507 $warnings[] = [
508 'level' => 3,
509 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
510 'node' => $this,
511 ];
512 }
513 if (count($componentTypes) === 0) {
514 $warnings[] = [
515 'level' => 3,
516 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
517 'node' => $this,
518 ];
519 }
520 if (count(array_unique($componentTypes)) > 1) {
521 $warnings[] = [
522 'level' => 3,
523 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
524 'node' => $this,
525 ];
526 }
527
528 if (isset($this->METHOD)) {
529 $warnings[] = [
530 'level' => 3,
531 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
532 'node' => $this,
533 ];
534 }
535 }
536
537 return $warnings;
538
539 }
540
546 function getByUID($uid) {
547
548 return array_filter($this->getComponents(), function($item) use ($uid) {
549
550 if (!$itemUid = $item->select('UID')) {
551 return false;
552 }
553 $itemUid = current($itemUid)->getValue();
554 return $uid === $itemUid;
555
556 });
557
558 }
559
560
561}
An exception for terminatinating execution or to throw for unit testing.
The VCalendar component.
Definition: VCalendar.php:23
getDefaults()
This method should return a list of default property values.
Definition: VCalendar.php:386
getDocumentType()
Returns the current document type.
Definition: VCalendar.php:161
getByUID($uid)
Returns all components with a specific UID value.
Definition: VCalendar.php:546
getBaseComponents($componentName=null)
Returns a list of all 'base components'.
Definition: VCalendar.php:178
validate($options=0)
Validates the node for correctness.
Definition: VCalendar.php:447
getBaseComponent($componentName=null)
Returns the first component that is not a VTIMEZONE, and does not have an RECURRENCE-ID.
Definition: VCalendar.php:234
expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone=null)
Expand all events in this VCalendar object and return a new VCalendar with the expanded events.
Definition: VCalendar.php:293
children()
Returns a flat list of all the properties and components in this component.
Definition: Component.php:187
select($name)
Returns an array with elements that match the specified name.
Definition: Component.php:231
const ICALENDAR20
iCalendar 2.0.
Definition: Document.php:34
This exception is thrown whenever an invalid value is found anywhere in a iCalendar or vCard object.
This class is used to determine new for a recurring event, when the next events occur.
This exception gets thrown when a recurrence iterator produces 0 instances.
if(!file_exists(getcwd() . '/ilias.ini.php'))
registration confirmation script for ilias
Definition: confirmReg.php:12
if($argc< 2) $events
$start
Definition: bench.php:8