From 0b02a6d123b2014705998c94ddf3d460948d3eac Mon Sep 17 00:00:00 2001 From: redmatrix Date: Tue, 10 May 2016 17:26:44 -0700 Subject: initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import) --- vendor/sabre/vobject/lib/Recur/EventIterator.php | 507 +++++++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100644 vendor/sabre/vobject/lib/Recur/EventIterator.php (limited to 'vendor/sabre/vobject/lib/Recur/EventIterator.php') diff --git a/vendor/sabre/vobject/lib/Recur/EventIterator.php b/vendor/sabre/vobject/lib/Recur/EventIterator.php new file mode 100644 index 000000000..86c996aec --- /dev/null +++ b/vendor/sabre/vobject/lib/Recur/EventIterator.php @@ -0,0 +1,507 @@ +timeZone = $timeZone; + + if (is_array($input)) { + $events = $input; + } elseif ($input instanceof VEvent) { + // Single instance mode. + $events = [$input]; + } else { + // Calendar + UID mode. + $uid = (string)$uid; + if (!$uid) { + throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); + } + if (!isset($input->VEVENT)) { + throw new InvalidArgumentException('No events found in this calendar'); + } + $events = $input->getByUID($uid); + + } + + foreach ($events as $vevent) { + + if (!isset($vevent->{'RECURRENCE-ID'})) { + + $this->masterEvent = $vevent; + + } else { + + $this->exceptions[ + $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() + ] = true; + $this->overriddenEvents[] = $vevent; + + } + + } + + if (!$this->masterEvent) { + // No base event was found. CalDAV does allow cases where only + // overridden instances are stored. + // + // In this particular case, we're just going to grab the first + // event and use that instead. This may not always give the + // desired result. + if (!count($this->overriddenEvents)) { + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); + } + $this->masterEvent = array_shift($this->overriddenEvents); + } + + $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); + $this->allDay = !$this->masterEvent->DTSTART->hasTime(); + + if (isset($this->masterEvent->EXDATE)) { + + foreach ($this->masterEvent->EXDATE as $exDate) { + + foreach ($exDate->getDateTimes($this->timeZone) as $dt) { + $this->exceptions[$dt->getTimeStamp()] = true; + } + + } + + } + + if (isset($this->masterEvent->DTEND)) { + $this->eventDuration = + $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - + $this->startDate->getTimeStamp(); + } elseif (isset($this->masterEvent->DURATION)) { + $duration = $this->masterEvent->DURATION->getDateInterval(); + $end = clone $this->startDate; + $end = $end->add($duration); + $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); + } elseif ($this->allDay) { + $this->eventDuration = 3600 * 24; + } else { + $this->eventDuration = 0; + } + + if (isset($this->masterEvent->RDATE)) { + $this->recurIterator = new RDateIterator( + $this->masterEvent->RDATE->getParts(), + $this->startDate + ); + } elseif (isset($this->masterEvent->RRULE)) { + $this->recurIterator = new RRuleIterator( + $this->masterEvent->RRULE->getParts(), + $this->startDate + ); + } else { + $this->recurIterator = new RRuleIterator( + [ + 'FREQ' => 'DAILY', + 'COUNT' => 1, + ], + $this->startDate + ); + } + + $this->rewind(); + if (!$this->valid()) { + throw new NoInstancesException('This recurrence rule does not generate any valid instances'); + } + + } + + /** + * Returns the date for the current position of the iterator. + * + * @return DateTimeImmutable + */ + function current() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the start date for the current iteration of the + * event. + * + * @return DateTimeImmutable + */ + function getDtStart() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the end date for the current iteration of the + * event. + * + * @return DateTimeImmutable + */ + function getDtEnd() { + + if (!$this->valid()) { + return; + } + $end = clone $this->currentDate; + return $end->modify('+' . $this->eventDuration . ' seconds'); + + } + + /** + * Returns a VEVENT for the current iterations of the event. + * + * This VEVENT will have a recurrence id, and it's DTSTART and DTEND + * altered. + * + * @return VEvent + */ + function getEventObject() { + + if ($this->currentOverriddenEvent) { + return $this->currentOverriddenEvent; + } + + $event = clone $this->masterEvent; + + // Ignoring the following block, because PHPUnit's code coverage + // ignores most of these lines, and this messes with our stats. + // + // @codeCoverageIgnoreStart + unset( + $event->RRULE, + $event->EXDATE, + $event->RDATE, + $event->EXRULE, + $event->{'RECURRENCE-ID'} + ); + // @codeCoverageIgnoreEnd + + $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); + if (isset($event->DTEND)) { + $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); + } + $recurid = clone $event->DTSTART; + $recurid->name = 'RECURRENCE-ID'; + $event->add($recurid); + return $event; + + } + + /** + * Returns the current position of the iterator. + * + * This is for us simply a 0-based index. + * + * @return int + */ + function key() { + + // The counter is always 1 ahead. + return $this->counter - 1; + + } + + /** + * This is called after next, to see if the iterator is still at a valid + * position, or if it's at the end. + * + * @return bool + */ + function valid() { + + if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) { + throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences); + } + return !!$this->currentDate; + + } + + /** + * Sets the iterator back to the starting point. + */ + function rewind() { + + $this->recurIterator->rewind(); + // re-creating overridden event index. + $index = []; + foreach ($this->overriddenEvents as $key => $event) { + $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); + $index[$stamp] = $key; + } + krsort($index); + $this->counter = 0; + $this->overriddenEventsIndex = $index; + $this->currentOverriddenEvent = null; + + $this->nextDate = null; + $this->currentDate = clone $this->startDate; + + $this->next(); + + } + + /** + * Advances the iterator with one step. + * + * @return void + */ + function next() { + + $this->currentOverriddenEvent = null; + $this->counter++; + if ($this->nextDate) { + // We had a stored value. + $nextDate = $this->nextDate; + $this->nextDate = null; + } else { + // We need to ask rruleparser for the next date. + // We need to do this until we find a date that's not in the + // exception list. + do { + if (!$this->recurIterator->valid()) { + $nextDate = null; + break; + } + $nextDate = $this->recurIterator->current(); + $this->recurIterator->next(); + } while (isset($this->exceptions[$nextDate->getTimeStamp()])); + + } + + + // $nextDate now contains what rrule thinks is the next one, but an + // overridden event may cut ahead. + if ($this->overriddenEventsIndex) { + + $offset = end($this->overriddenEventsIndex); + $timestamp = key($this->overriddenEventsIndex); + if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { + // Overridden event comes first. + $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; + + // Putting the rrule next date aside. + $this->nextDate = $nextDate; + $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); + + // Ensuring that this item will only be used once. + array_pop($this->overriddenEventsIndex); + + // Exit point! + return; + + } + + } + + $this->currentDate = $nextDate; + + } + + /** + * Quickly jump to a date in the future. + * + * @param DateTimeInterface $dateTime + */ + function fastForward(DateTimeInterface $dateTime) { + + while ($this->valid() && $this->getDtEnd() < $dateTime) { + $this->next(); + } + + } + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + function isInfinite() { + + return $this->recurIterator->isInfinite(); + + } + + /** + * RRULE parser. + * + * @var RRuleIterator + */ + protected $recurIterator; + + /** + * The duration, in seconds, of the master event. + * + * We use this to calculate the DTEND for subsequent events. + */ + protected $eventDuration; + + /** + * A reference to the main (master) event. + * + * @var VEVENT + */ + protected $masterEvent; + + /** + * List of overridden events. + * + * @var array + */ + protected $overriddenEvents = []; + + /** + * Overridden event index. + * + * Key is timestamp, value is the index of the item in the $overriddenEvent + * property. + * + * @var array + */ + protected $overriddenEventsIndex; + + /** + * A list of recurrence-id's that are either part of EXDATE, or are + * overridden. + * + * @var array + */ + protected $exceptions = []; + + /** + * Internal event counter. + * + * @var int + */ + protected $counter; + + /** + * The very start of the iteration process. + * + * @var DateTimeImmutable + */ + protected $startDate; + + /** + * Where we are currently in the iteration process. + * + * @var DateTimeImmutable + */ + protected $currentDate; + + /** + * The next date from the rrule parser. + * + * Sometimes we need to temporary store the next date, because an + * overridden event came before. + * + * @var DateTimeImmutable + */ + protected $nextDate; + + /** + * The event that overwrites the current iteration + * + * @var VEVENT + */ + protected $currentOverriddenEvent; + +} -- cgit v1.2.3