ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
FreeBusyGenerator.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\VObject;
4
5use DateTimeImmutable;
6use DateTimeInterface;
7use 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
92 function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) {
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
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
177 function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) {
178
179 if (!$start) {
180 $start = new DateTimeImmutable(Settings::$minDate);
181 }
182 if (!$end) {
183 $end = new DateTimeImmutable(Settings::$maxDate);
184 }
185 $this->start = $start;
186 $this->end = $end;
187
188 }
189
197 function setTimeZone(DateTimeZone $timeZone) {
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}
An exception for terminatinating execution or to throw for unit testing.
The VCalendar component.
Definition: VCalendar.php:23
static parseDuration($duration, $asString=false)
Parses an iCalendar (RFC5545) formatted duration value.
static parseDateTime($dt, DateTimeZone $tz=null)
Parses an iCalendar (rfc5545) formatted datetime and returns a DateTimeImmutable object.
FreeBusyData is a helper class that manages freebusy information.
add($start, $end, $type)
Adds free or busytime to the data.
This class helps with generating FREEBUSY reports based on existing sets of objects.
setVAvailability(Document $vcalendar)
Sets a VAVAILABILITY document.
setBaseObject(Document $vcalendar)
Sets the VCALENDAR object.
generateFreeBusyCalendar(FreeBusyData $fbData)
This method takes a FreeBusyData object and generates the VCALENDAR object associated with it.
setObjects($objects)
Sets the input objects.
calculateBusy(FreeBusyData $fbData, array $objects)
This method takes an array of iCalendar objects and applies its busy times on fbData.
setTimeZone(DateTimeZone $timeZone)
Sets the reference timezone for floating times.
__construct(DateTimeInterface $start=null, DateTimeInterface $end=null, $objects=null, DateTimeZone $timeZone=null)
Creates the generator.
setTimeRange(DateTimeInterface $start=null, DateTimeInterface $end=null)
Sets the time range.
calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability)
This method takes a VAVAILABILITY component and figures out all the available times.
getResult()
Parses the input data and returns a correct VFREEBUSY object, wrapped in a VCALENDAR.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
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.
static $maxDate
The maximum date we accept for various calculations with dates, such as recurrences.
Definition: Settings.php:37
static $maxRecurrences
The maximum number of recurrences that will be generated.
Definition: Settings.php:54
static $minDate
The minimum date we accept for various calculations with dates, such as recurrences.
Definition: Settings.php:28
$key
Definition: croninfo.php:18
$time
Definition: cron.php:21
$values