ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
VCalendar.php
Go to the documentation of this file.
1 <?php
2 
4 
6 use DateTimeZone;
7 use Sabre\VObject;
13 
23 class VCalendar extends VObject\Document {
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 
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 
411  function getValidationRules() {
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 }
select($name)
Returns an array with elements that match the specified name.
Definition: Component.php:231
getComponents()
This method only returns a list of sub-components.
Definition: Component.php:203
foreach($paths as $path) if(!class_exists( 'Sabre\\VObject\\Version'))
const VERSION
Full version number.
Definition: Version.php:17
static getDocumentType()
Returns the current document type.
Definition: VCalendar.php:161
if($argc< 2) $events
The VCalendar component.
Definition: VCalendar.php:23
getDefaults()
This method should return a list of default property values.
Definition: VCalendar.php:386
$start
Definition: bench.php:8
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.
getByUID($uid)
Returns all components with a specific UID value.
Definition: VCalendar.php:546
validate($options=0)
Validates the node for correctness.
Definition: VCalendar.php:447
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
getBaseComponents($componentName=null)
Returns a list of all &#39;base components&#39;.
Definition: VCalendar.php:178
getBaseComponent($componentName=null)
Returns the first component that is not a VTIMEZONE, and does not have an RECURRENCE-ID.
Definition: VCalendar.php:234
count()
Returns the number of elements.
Definition: Node.php:177
children()
Returns a flat list of all the properties and components in this component.
Definition: Component.php:187
This exception is thrown whenever an invalid value is found anywhere in a iCalendar or vCard object...