ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
FreeBusyGenerator.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Sabre\VObject;
4 
7 use DateTimeZone;
11 
27 
33  protected $objects = [];
34 
40  protected $start;
41 
47  protected $end;
48 
54  protected $baseObject;
55 
69  protected $timeZone;
70 
79  protected $vavailability;
80 
93 
94  $this->setTimeRange($start, $end);
95 
96  if ($objects) {
97  $this->setObjects($objects);
98  }
99  if (is_null($timeZone)) {
100  $timeZone = new DateTimeZone('UTC');
101  }
102  $this->setTimeZone($timeZone);
103 
104  }
105 
117  function setBaseObject(Document $vcalendar) {
118 
119  $this->baseObject = $vcalendar;
120 
121  }
122 
129  function setVAvailability(Document $vcalendar) {
130 
131  $this->vavailability = $vcalendar;
132 
133  }
134 
146  function setObjects($objects) {
147 
148  if (!is_array($objects)) {
149  $objects = [$objects];
150  }
151 
152  $this->objects = [];
153  foreach ($objects as $object) {
154 
155  if (is_string($object) || is_resource($object)) {
156  $this->objects[] = Reader::read($object);
157  } elseif ($object instanceof Component) {
158  $this->objects[] = $object;
159  } else {
160  throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
161  }
162 
163  }
164 
165  }
166 
178 
179  if (!$start) {
181  }
182  if (!$end) {
184  }
185  $this->start = $start;
186  $this->end = $end;
187 
188  }
189 
198 
199  $this->timeZone = $timeZone;
200 
201  }
202 
209  function getResult() {
210 
211  $fbData = new FreeBusyData(
212  $this->start->getTimeStamp(),
213  $this->end->getTimeStamp()
214  );
215  if ($this->vavailability) {
216 
217  $this->calculateAvailability($fbData, $this->vavailability);
218 
219  }
220 
221  $this->calculateBusy($fbData, $this->objects);
222 
223  return $this->generateFreeBusyCalendar($fbData);
224 
225 
226  }
227 
237 
238  $vavailComps = iterator_to_array($vavailability->VAVAILABILITY);
239  usort(
240  $vavailComps,
241  function($a, $b) {
242 
243  // We need to order the components by priority. Priority 1
244  // comes first, up until priority 9. Priority 0 comes after
245  // priority 9. No priority implies priority 0.
246  //
247  // Yes, I'm serious.
248  $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0;
249  $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0;
250 
251  if ($priorityA === 0) $priorityA = 10;
252  if ($priorityB === 0) $priorityB = 10;
253 
254  return $priorityA - $priorityB;
255 
256  }
257  );
258 
259  // Now we go over all the VAVAILABILITY components and figure if
260  // there's any we don't need to consider.
261  //
262  // This is can be because of one of two reasons: either the
263  // VAVAILABILITY component falls outside the time we are interested in,
264  // or a different VAVAILABILITY component with a higher priority has
265  // already completely covered the time-range.
266  $old = $vavailComps;
267  $new = [];
268 
269  foreach ($old as $vavail) {
270 
271  list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
272 
273  // We don't care about datetimes that are earlier or later than the
274  // start and end of the freebusy report, so this gets normalized
275  // first.
276  if (is_null($compStart) || $compStart < $this->start) {
277  $compStart = $this->start;
278  }
279  if (is_null($compEnd) || $compEnd > $this->end) {
280  $compEnd = $this->end;
281  }
282 
283  // If the item fell out of the timerange, we can just skip it.
284  if ($compStart > $this->end || $compEnd < $this->start) {
285  continue;
286  }
287 
288  // Going through our existing list of components to see if there's
289  // a higher priority component that already fully covers this one.
290  foreach ($new as $higherVavail) {
291 
292  list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
293  if (
294  (is_null($higherStart) || $higherStart < $compStart) &&
295  (is_null($higherEnd) || $higherEnd > $compEnd)
296  ) {
297 
298  // Component is fully covered by a higher priority
299  // component. We can skip this component.
300  continue 2;
301 
302  }
303 
304  }
305 
306  // We're keeping it!
307  $new[] = $vavail;
308 
309  }
310 
311  // Lastly, we need to traverse the remaining components and fill in the
312  // freebusydata slots.
313  //
314  // We traverse the components in reverse, because we want the higher
315  // priority components to override the lower ones.
316  foreach (array_reverse($new) as $vavail) {
317 
318  $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
319  list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
320 
321  // Making the component size no larger than the requested free-busy
322  // report range.
323  if (!$vavailStart || $vavailStart < $this->start) {
324  $vavailStart = $this->start;
325  }
326  if (!$vavailEnd || $vavailEnd > $this->end) {
327  $vavailEnd = $this->end;
328  }
329 
330  // Marking the entire time range of the VAVAILABILITY component as
331  // busy.
332  $fbData->add(
333  $vavailStart->getTimeStamp(),
334  $vavailEnd->getTimeStamp(),
335  $busyType
336  );
337 
338  // Looping over the AVAILABLE components.
339  if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) {
340 
341  list($availStart, $availEnd) = $available->getEffectiveStartEnd();
342  $fbData->add(
343  $availStart->getTimeStamp(),
344  $availEnd->getTimeStamp(),
345  'FREE'
346  );
347 
348  if ($available->RRULE) {
349  // Our favourite thing: recurrence!!
350 
351  $rruleIterator = new Recur\RRuleIterator(
352  $available->RRULE->getValue(),
353  $availStart
354  );
355  $rruleIterator->fastForward($vavailStart);
356 
357  $startEndDiff = $availStart->diff($availEnd);
358 
359  while ($rruleIterator->valid()) {
360 
361  $recurStart = $rruleIterator->current();
362  $recurEnd = $recurStart->add($startEndDiff);
363 
364  if ($recurStart > $vavailEnd) {
365  // We're beyond the legal timerange.
366  break;
367  }
368 
369  if ($recurEnd > $vavailEnd) {
370  // Truncating the end if it exceeds the
371  // VAVAILABILITY end.
372  $recurEnd = $vavailEnd;
373  }
374 
375  $fbData->add(
376  $recurStart->getTimeStamp(),
377  $recurEnd->getTimeStamp(),
378  'FREE'
379  );
380 
381  $rruleIterator->next();
382 
383  }
384  }
385 
386  }
387 
388  }
389 
390  }
391 
399  protected function calculateBusy(FreeBusyData $fbData, array $objects) {
400 
401  foreach ($objects as $key => $object) {
402 
403  foreach ($object->getBaseComponents() as $component) {
404 
405  switch ($component->name) {
406 
407  case 'VEVENT' :
408 
409  $FBTYPE = 'BUSY';
410  if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
411  break;
412  }
413  if (isset($component->STATUS)) {
414  $status = strtoupper($component->STATUS);
415  if ($status === 'CANCELLED') {
416  break;
417  }
418  if ($status === 'TENTATIVE') {
419  $FBTYPE = 'BUSY-TENTATIVE';
420  }
421  }
422 
423  $times = [];
424 
425  if ($component->RRULE) {
426  try {
427  $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone);
428  } catch (NoInstancesException $e) {
429  // This event is recurring, but it doesn't have a single
430  // instance. We are skipping this event from the output
431  // entirely.
432  unset($this->objects[$key]);
433  continue;
434  }
435 
436  if ($this->start) {
437  $iterator->fastForward($this->start);
438  }
439 
440  $maxRecurrences = Settings::$maxRecurrences;
441 
442  while ($iterator->valid() && --$maxRecurrences) {
443 
444  $startTime = $iterator->getDTStart();
445  if ($this->end && $startTime > $this->end) {
446  break;
447  }
448  $times[] = [
449  $iterator->getDTStart(),
450  $iterator->getDTEnd(),
451  ];
452 
453  $iterator->next();
454 
455  }
456 
457  } else {
458 
459  $startTime = $component->DTSTART->getDateTime($this->timeZone);
460  if ($this->end && $startTime > $this->end) {
461  break;
462  }
463  $endTime = null;
464  if (isset($component->DTEND)) {
465  $endTime = $component->DTEND->getDateTime($this->timeZone);
466  } elseif (isset($component->DURATION)) {
467  $duration = DateTimeParser::parseDuration((string)$component->DURATION);
468  $endTime = clone $startTime;
469  $endTime = $endTime->add($duration);
470  } elseif (!$component->DTSTART->hasTime()) {
471  $endTime = clone $startTime;
472  $endTime = $endTime->modify('+1 day');
473  } else {
474  // The event had no duration (0 seconds)
475  break;
476  }
477 
478  $times[] = [$startTime, $endTime];
479 
480  }
481 
482  foreach ($times as $time) {
483 
484  if ($this->end && $time[0] > $this->end) break;
485  if ($this->start && $time[1] < $this->start) break;
486 
487  $fbData->add(
488  $time[0]->getTimeStamp(),
489  $time[1]->getTimeStamp(),
490  $FBTYPE
491  );
492  }
493  break;
494 
495  case 'VFREEBUSY' :
496  foreach ($component->FREEBUSY as $freebusy) {
497 
498  $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';
499 
500  // Skipping intervals marked as 'free'
501  if ($fbType === 'FREE')
502  continue;
503 
504  $values = explode(',', $freebusy);
505  foreach ($values as $value) {
506  list($startTime, $endTime) = explode('/', $value);
507  $startTime = DateTimeParser::parseDateTime($startTime);
508 
509  if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
510  $duration = DateTimeParser::parseDuration($endTime);
511  $endTime = clone $startTime;
512  $endTime = $endTime->add($duration);
513  } else {
514  $endTime = DateTimeParser::parseDateTime($endTime);
515  }
516 
517  if ($this->start && $this->start > $endTime) continue;
518  if ($this->end && $this->end < $startTime) continue;
519  $fbData->add(
520  $startTime->getTimeStamp(),
521  $endTime->getTimeStamp(),
522  $fbType
523  );
524 
525  }
526 
527 
528  }
529  break;
530 
531  }
532 
533 
534  }
535 
536  }
537 
538  }
539 
546  protected function generateFreeBusyCalendar(FreeBusyData $fbData) {
547 
548  if ($this->baseObject) {
550  } else {
551  $calendar = new VCalendar();
552  }
553 
554  $vfreebusy = $calendar->createComponent('VFREEBUSY');
555  $calendar->add($vfreebusy);
556 
557  if ($this->start) {
558  $dtstart = $calendar->createProperty('DTSTART');
559  $dtstart->setDateTime($this->start);
560  $vfreebusy->add($dtstart);
561  }
562  if ($this->end) {
563  $dtend = $calendar->createProperty('DTEND');
564  $dtend->setDateTime($this->end);
565  $vfreebusy->add($dtend);
566  }
567 
568  $tz = new \DateTimeZone('UTC');
569  $dtstamp = $calendar->createProperty('DTSTAMP');
570  $dtstamp->setDateTime(new DateTimeImmutable('now', $tz));
571  $vfreebusy->add($dtstamp);
572 
573  foreach ($fbData->getData() as $busyTime) {
574 
575  $busyType = strtoupper($busyTime['type']);
576 
577  // Ignoring all the FREE parts, because those are already assumed.
578  if ($busyType === 'FREE') {
579  continue;
580  }
581 
582  $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz);
583  $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz);
584 
585  $prop = $calendar->createProperty(
586  'FREEBUSY',
587  $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
588  );
589 
590  // Only setting FBTYPE if it's not BUSY, because BUSY is the
591  // default anyway.
592  if ($busyType !== 'BUSY') {
593  $prop['FBTYPE'] = $busyType;
594  }
595  $vfreebusy->add($prop);
596 
597  }
598 
599  return $calendar;
600 
601 
602  }
603 
604 }
static $maxDate
The maximum date we accept for various calculations with dates, such as recurrences.
Definition: Settings.php:37
static $minDate
The minimum date we accept for various calculations with dates, such as recurrences.
Definition: Settings.php:28
static parseDateTime($dt, DateTimeZone $tz=null)
Parses an iCalendar (rfc5545) formatted datetime and returns a DateTimeImmutable object.
setTimeRange(DateTimeInterface $start=null, DateTimeInterface $end=null)
Sets the time range.
This class helps with generating FREEBUSY reports based on existing sets of objects.
FreeBusyData is a helper class that manages freebusy information.
The VCalendar component.
Definition: VCalendar.php:23
static $maxRecurrences
The maximum number of recurrences that will be generated.
Definition: Settings.php:54
calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability)
This method takes a VAVAILABILITY component and figures out all the available times.
generateFreeBusyCalendar(FreeBusyData $fbData)
This method takes a FreeBusyData object and generates the VCALENDAR object associated with it...
setBaseObject(Document $vcalendar)
Sets the VCALENDAR object.
$time
Definition: cron.php:21
setVAvailability(Document $vcalendar)
Sets a VAVAILABILITY document.
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.
$values
__construct(DateTimeInterface $start=null, DateTimeInterface $end=null, $objects=null, DateTimeZone $timeZone=null)
Creates the generator.
setObjects($objects)
Sets the input objects.
add($start, $end, $type)
Adds free or busytime to the data.
setTimeZone(DateTimeZone $timeZone)
Sets the reference timezone for floating times.
fastForward(DateTimeInterface $dt)
This method allows you to quickly go to the next occurrence after the specified date.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
static parseDuration($duration, $asString=false)
Parses an iCalendar (RFC5545) formatted duration value.
calculateBusy(FreeBusyData $fbData, array $objects)
This method takes an array of iCalendar objects and applies its busy times on fbData.
$key
Definition: croninfo.php:18
getResult()
Parses the input data and returns a correct VFREEBUSY object, wrapped in a VCALENDAR.