Пример #1
0
 /**
  * Returns true or false depending on if the event falls in the specified
  * time-range. This is used for filtering purposes.
  *
  * The rules used to determine if an event falls within the specified
  * time-range is based on the CalDAV specification.
  *
  * @param \DateTime $start
  * @param \DateTime $end
  * @return bool
  */
 public function isInTimeRange(\DateTime $start, \DateTime $end)
 {
     if ($this->RRULE) {
         $it = new EventIterator($this);
         $it->fastForward($start);
         // We fast-forwarded to a spot where the end-time of the
         // recurrence instance exceeded the start of the requested
         // time-range.
         //
         // If the starttime of the recurrence did not exceed the
         // end of the time range as well, we have a match.
         return $it->getDTStart() < $end && $it->getDTEnd() > $start;
     }
     $effectiveStart = $this->DTSTART->getDateTime();
     if (isset($this->DTEND)) {
         // The DTEND property is considered non inclusive. So for a 3 day
         // event in july, dtstart and dtend would have to be July 1st and
         // July 4th respectively.
         //
         // See:
         // http://tools.ietf.org/html/rfc5545#page-54
         $effectiveEnd = $this->DTEND->getDateTime();
     } elseif (isset($this->DURATION)) {
         $effectiveEnd = clone $effectiveStart;
         $effectiveEnd->add(VObject\DateTimeParser::parseDuration($this->DURATION));
     } elseif (!$this->DTSTART->hasTime()) {
         $effectiveEnd = clone $effectiveStart;
         $effectiveEnd->modify('+1 day');
     } else {
         $effectiveEnd = clone $effectiveStart;
     }
     return $start <= $effectiveEnd && $end > $effectiveStart;
 }
Пример #2
0
 /**
  * Returns true or false depending on if the event falls in the specified
  * time-range. This is used for filtering purposes.
  *
  * The rules used to determine if an event falls within the specified
  * time-range is based on the CalDAV specification.
  *
  * @param DateTimeInterface $start
  * @param DateTimeInterface $end
  *
  * @return bool
  */
 function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end)
 {
     if ($this->RRULE) {
         try {
             $it = new EventIterator($this, null, $start->getTimezone());
         } catch (NoInstancesException $e) {
             // If we've catched this exception, there are no instances
             // for the event that fall into the specified time-range.
             return false;
         }
         $it->fastForward($start);
         // We fast-forwarded to a spot where the end-time of the
         // recurrence instance exceeded the start of the requested
         // time-range.
         //
         // If the starttime of the recurrence did not exceed the
         // end of the time range as well, we have a match.
         return $it->getDTStart() < $end && $it->getDTEnd() > $start;
     }
     if (!isset($this->DTSTART)) {
         return false;
     }
     $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone());
     if (isset($this->DTEND)) {
         // The DTEND property is considered non inclusive. So for a 3 day
         // event in july, dtstart and dtend would have to be July 1st and
         // July 4th respectively.
         //
         // See:
         // http://tools.ietf.org/html/rfc5545#page-54
         $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone());
     } elseif (isset($this->DURATION)) {
         $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
     } elseif (!$this->DTSTART->hasTime()) {
         $effectiveEnd = $effectiveStart->modify('+1 day');
     } else {
         $effectiveEnd = $effectiveStart;
     }
     return $start < $effectiveEnd && $end > $effectiveStart;
 }
Пример #3
0
 /**
  * If this calendar object, has events with recurrence rules, this method
  * can be used to expand the event into multiple sub-events.
  *
  * Each event will be stripped from it's recurrence information, and only
  * the instances of the event in the specified timerange will be left
  * alone.
  *
  * In addition, this method will cause timezone information to be stripped,
  * and normalized to UTC.
  *
  * This method will alter the VCalendar. This cannot be reversed.
  *
  * This functionality is specifically used by the CalDAV standard. It is
  * possible for clients to request expand events, if they are rather simple
  * clients and do not have the possibility to calculate recurrences.
  *
  * @param DateTime $start
  * @param DateTime $end
  * @param DateTimeZone $timeZone reference timezone for floating dates and
  *                     times.
  * @return void
  */
 function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null)
 {
     $newEvents = array();
     if (!$timeZone) {
         $timeZone = new DateTimeZone('UTC');
     }
     // An array of events. Events are indexed by UID. Each item in this
     // array is a list of one or more events that match the UID.
     $recurringEvents = array();
     foreach ($this->select('VEVENT') as $key => $vevent) {
         $uid = (string) $vevent->UID;
         if (!$uid) {
             throw new \LogicException('Event did not have a UID!');
         }
         if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE)) {
             if (isset($recurringEvents[$uid])) {
                 $recurringEvents[$uid][] = $vevent;
             } else {
                 $recurringEvents[$uid] = array($vevent);
             }
             continue;
         }
         if (!isset($vevent->RRULE)) {
             if ($vevent->isInTimeRange($start, $end)) {
                 $newEvents[] = $vevent;
             }
             continue;
         }
     }
     foreach ($recurringEvents as $events) {
         try {
             $it = new EventIterator($events, $timeZone);
         } catch (NoInstancesException $e) {
             // This event is recurring, but it doesn't have a single
             // instance. We are skipping this event from the output
             // entirely.
             continue;
         }
         $it->fastForward($start);
         while ($it->valid() && $it->getDTStart() < $end) {
             if ($it->getDTEnd() > $start) {
                 $newEvents[] = $it->getEventObject();
             }
             $it->next();
         }
     }
     // Wiping out all old VEVENT objects
     unset($this->VEVENT);
     // Setting all properties to UTC time.
     foreach ($newEvents as $newEvent) {
         foreach ($newEvent->children as $child) {
             if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
                 $dt = $child->getDateTimes($timeZone);
                 // We only need to update the first timezone, because
                 // setDateTimes will match all other timezones to the
                 // first.
                 $dt[0]->setTimeZone(new DateTimeZone('UTC'));
                 $child->setDateTimes($dt);
             }
         }
         $this->add($newEvent);
     }
     // Removing all VTIMEZONE components
     unset($this->VTIMEZONE);
 }
Пример #4
0
 /**
  * Parses the input data and returns a correct VFREEBUSY object, wrapped in
  * a VCALENDAR.
  *
  * @return Component
  */
 function getResult()
 {
     $busyTimes = [];
     foreach ($this->objects as $key => $object) {
         foreach ($object->getBaseComponents() as $component) {
             switch ($component->name) {
                 case 'VEVENT':
                     $FBTYPE = 'BUSY';
                     if (isset($component->TRANSP) && strtoupper($component->TRANSP) === 'TRANSPARENT') {
                         break;
                     }
                     if (isset($component->STATUS)) {
                         $status = strtoupper($component->STATUS);
                         if ($status === 'CANCELLED') {
                             break;
                         }
                         if ($status === 'TENTATIVE') {
                             $FBTYPE = 'BUSY-TENTATIVE';
                         }
                     }
                     $times = [];
                     if ($component->RRULE) {
                         try {
                             $iterator = new EventIterator($object, (string) $component->uid, $this->timeZone);
                         } catch (NoInstancesException $e) {
                             // This event is recurring, but it doesn't have a single
                             // instance. We are skipping this event from the output
                             // entirely.
                             unset($this->objects[$key]);
                             continue;
                         }
                         if ($this->start) {
                             $iterator->fastForward($this->start);
                         }
                         $maxRecurrences = 200;
                         while ($iterator->valid() && --$maxRecurrences) {
                             $startTime = $iterator->getDTStart();
                             if ($this->end && $startTime > $this->end) {
                                 break;
                             }
                             $times[] = [$iterator->getDTStart(), $iterator->getDTEnd()];
                             $iterator->next();
                         }
                     } else {
                         $startTime = $component->DTSTART->getDateTime($this->timeZone);
                         if ($this->end && $startTime > $this->end) {
                             break;
                         }
                         $endTime = null;
                         if (isset($component->DTEND)) {
                             $endTime = $component->DTEND->getDateTime($this->timeZone);
                         } elseif (isset($component->DURATION)) {
                             $duration = DateTimeParser::parseDuration((string) $component->DURATION);
                             $endTime = clone $startTime;
                             $endTime = $endTime->add($duration);
                         } elseif (!$component->DTSTART->hasTime()) {
                             $endTime = clone $startTime;
                             $endTime = $endTime->modify('+1 day');
                         } else {
                             // The event had no duration (0 seconds)
                             break;
                         }
                         $times[] = [$startTime, $endTime];
                     }
                     foreach ($times as $time) {
                         if ($this->end && $time[0] > $this->end) {
                             break;
                         }
                         if ($this->start && $time[1] < $this->start) {
                             break;
                         }
                         $busyTimes[] = [$time[0], $time[1], $FBTYPE];
                     }
                     break;
                 case 'VFREEBUSY':
                     foreach ($component->FREEBUSY as $freebusy) {
                         $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';
                         // Skipping intervals marked as 'free'
                         if ($fbType === 'FREE') {
                             continue;
                         }
                         $values = explode(',', $freebusy);
                         foreach ($values as $value) {
                             list($startTime, $endTime) = explode('/', $value);
                             $startTime = DateTimeParser::parseDateTime($startTime);
                             if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
                                 $duration = DateTimeParser::parseDuration($endTime);
                                 $endTime = clone $startTime;
                                 $endTime = $endTime->add($duration);
                             } else {
                                 $endTime = DateTimeParser::parseDateTime($endTime);
                             }
                             if ($this->start && $this->start > $endTime) {
                                 continue;
                             }
                             if ($this->end && $this->end < $startTime) {
                                 continue;
                             }
                             $busyTimes[] = [$startTime, $endTime, $fbType];
                         }
                     }
                     break;
             }
         }
     }
     if ($this->baseObject) {
         $calendar = $this->baseObject;
     } else {
         $calendar = new VCalendar();
     }
     $vfreebusy = $calendar->createComponent('VFREEBUSY');
     $calendar->add($vfreebusy);
     if ($this->start) {
         $dtstart = $calendar->createProperty('DTSTART');
         $dtstart->setDateTime($this->start);
         $vfreebusy->add($dtstart);
     }
     if ($this->end) {
         $dtend = $calendar->createProperty('DTEND');
         $dtend->setDateTime($this->end);
         $vfreebusy->add($dtend);
     }
     $dtstamp = $calendar->createProperty('DTSTAMP');
     $dtstamp->setDateTime(new DateTimeImmutable('now', new \DateTimeZone('UTC')));
     $vfreebusy->add($dtstamp);
     foreach ($busyTimes as $busyTime) {
         $busyTime[0] = $busyTime[0]->setTimeZone(new \DateTimeZone('UTC'));
         $busyTime[1] = $busyTime[1]->setTimeZone(new \DateTimeZone('UTC'));
         $prop = $calendar->createProperty('FREEBUSY', $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z'));
         $prop['FBTYPE'] = $busyTime[2];
         $vfreebusy->add($prop);
     }
     return $calendar;
 }
Пример #5
0
 /**
  * If this calendar object, has events with recurrence rules, this method
  * can be used to expand the event into multiple sub-events.
  *
  * Each event will be stripped from it's recurrence information, and only
  * the instances of the event in the specified timerange will be left
  * alone.
  *
  * In addition, this method will cause timezone information to be stripped,
  * and normalized to UTC.
  *
  * This method will alter the VCalendar. This cannot be reversed.
  *
  * This functionality is specifically used by the CalDAV standard. It is
  * possible for clients to request expand events, if they are rather simple
  * clients and do not have the possibility to calculate recurrences.
  *
  * @param DateTime $start
  * @param DateTime $end
  * @return void
  */
 public function expand(\DateTime $start, \DateTime $end)
 {
     $newEvents = array();
     foreach ($this->select('VEVENT') as $key => $vevent) {
         if (isset($vevent->{'RECURRENCE-ID'})) {
             unset($this->children[$key]);
             continue;
         }
         if (!$vevent->rrule) {
             unset($this->children[$key]);
             if ($vevent->isInTimeRange($start, $end)) {
                 $newEvents[] = $vevent;
             }
             continue;
         }
         $uid = (string) $vevent->uid;
         if (!$uid) {
             throw new \LogicException('Event did not have a UID!');
         }
         $it = new EventIterator($this, $vevent->uid);
         $it->fastForward($start);
         while ($it->valid() && $it->getDTStart() < $end) {
             if ($it->getDTEnd() > $start) {
                 $newEvents[] = $it->getEventObject();
             }
             $it->next();
         }
         unset($this->children[$key]);
     }
     // Setting all properties to UTC time.
     foreach ($newEvents as $newEvent) {
         foreach ($newEvent->children as $child) {
             if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
                 $dt = $child->getDateTimes();
                 // We only need to update the first timezone, because
                 // setDateTimes will match all other timezones to the
                 // first.
                 $dt[0]->setTimeZone(new \DateTimeZone('UTC'));
                 $child->setDateTimes($dt);
             }
         }
         $this->add($newEvent);
     }
     // Removing all VTIMEZONE components
     unset($this->VTIMEZONE);
 }
Пример #6
0
 /**
  * This method takes an array of iCalendar objects and applies its busy
  * times on fbData.
  *
  * @param FreeBusyData $fbData
  * @param VCalendar[] $objects
  */
 protected function calculateBusy(FreeBusyData $fbData, array $objects)
 {
     foreach ($objects as $key => $object) {
         foreach ($object->getBaseComponents() as $component) {
             switch ($component->name) {
                 case 'VEVENT':
                     $FBTYPE = 'BUSY';
                     if (isset($component->TRANSP) && strtoupper($component->TRANSP) === 'TRANSPARENT') {
                         break;
                     }
                     if (isset($component->STATUS)) {
                         $status = strtoupper($component->STATUS);
                         if ($status === 'CANCELLED') {
                             break;
                         }
                         if ($status === 'TENTATIVE') {
                             $FBTYPE = 'BUSY-TENTATIVE';
                         }
                     }
                     $times = [];
                     if ($component->RRULE) {
                         try {
                             $iterator = new EventIterator($object, (string) $component->uid, $this->timeZone);
                         } catch (NoInstancesException $e) {
                             // This event is recurring, but it doesn't have a single
                             // instance. We are skipping this event from the output
                             // entirely.
                             unset($this->objects[$key]);
                             continue;
                         }
                         if ($this->start) {
                             $iterator->fastForward($this->start);
                         }
                         $maxRecurrences = Settings::$maxRecurrences;
                         while ($iterator->valid() && --$maxRecurrences) {
                             $startTime = $iterator->getDTStart();
                             if ($this->end && $startTime > $this->end) {
                                 break;
                             }
                             $times[] = [$iterator->getDTStart(), $iterator->getDTEnd()];
                             $iterator->next();
                         }
                     } else {
                         $startTime = $component->DTSTART->getDateTime($this->timeZone);
                         if ($this->end && $startTime > $this->end) {
                             break;
                         }
                         $endTime = null;
                         if (isset($component->DTEND)) {
                             $endTime = $component->DTEND->getDateTime($this->timeZone);
                         } elseif (isset($component->DURATION)) {
                             $duration = DateTimeParser::parseDuration((string) $component->DURATION);
                             $endTime = clone $startTime;
                             $endTime = $endTime->add($duration);
                         } elseif (!$component->DTSTART->hasTime()) {
                             $endTime = clone $startTime;
                             $endTime = $endTime->modify('+1 day');
                         } else {
                             // The event had no duration (0 seconds)
                             break;
                         }
                         $times[] = [$startTime, $endTime];
                     }
                     foreach ($times as $time) {
                         if ($this->end && $time[0] > $this->end) {
                             break;
                         }
                         if ($this->start && $time[1] < $this->start) {
                             break;
                         }
                         $fbData->add($time[0]->getTimeStamp(), $time[1]->getTimeStamp(), $FBTYPE);
                     }
                     break;
                 case 'VFREEBUSY':
                     foreach ($component->FREEBUSY as $freebusy) {
                         $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';
                         // Skipping intervals marked as 'free'
                         if ($fbType === 'FREE') {
                             continue;
                         }
                         $values = explode(',', $freebusy);
                         foreach ($values as $value) {
                             list($startTime, $endTime) = explode('/', $value);
                             $startTime = DateTimeParser::parseDateTime($startTime);
                             if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
                                 $duration = DateTimeParser::parseDuration($endTime);
                                 $endTime = clone $startTime;
                                 $endTime = $endTime->add($duration);
                             } else {
                                 $endTime = DateTimeParser::parseDateTime($endTime);
                             }
                             if ($this->start && $this->start > $endTime) {
                                 continue;
                             }
                             if ($this->end && $this->end < $startTime) {
                                 continue;
                             }
                             $fbData->add($startTime->getTimeStamp(), $endTime->getTimeStamp(), $fbType);
                         }
                     }
                     break;
             }
         }
     }
 }
Пример #7
0
 /**
  * Expand all events in this VCalendar object and return a new VCalendar
  * with the expanded events.
  *
  * If this calendar object, has events with recurrence rules, this method
  * can be used to expand the event into multiple sub-events.
  *
  * Each event will be stripped from it's recurrence information, and only
  * the instances of the event in the specified timerange will be left
  * alone.
  *
  * In addition, this method will cause timezone information to be stripped,
  * and normalized to UTC.
  *
  * @param DateTimeInterface $start
  * @param DateTimeInterface $end
  * @param DateTimeZone $timeZone reference timezone for floating dates and
  *                     times.
  * @return VCalendar
  */
 function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null)
 {
     $newChildren = [];
     $recurringEvents = [];
     if (!$timeZone) {
         $timeZone = new DateTimeZone('UTC');
     }
     $stripTimezones = function (Component $component) use($timeZone, &$stripTimezones) {
         foreach ($component->children() as $componentChild) {
             if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) {
                 $dt = $componentChild->getDateTimes($timeZone);
                 // We only need to update the first timezone, because
                 // setDateTimes will match all other timezones to the
                 // first.
                 $dt[0] = $dt[0]->setTimeZone(new DateTimeZone('UTC'));
                 $componentChild->setDateTimes($dt);
             } elseif ($componentChild instanceof Component) {
                 $stripTimezones($componentChild);
             }
         }
         return $component;
     };
     foreach ($this->children() as $child) {
         if ($child instanceof Property && $child->name !== 'PRODID') {
             // We explictly want to ignore PRODID, because we want to
             // overwrite it with our own.
             $newChildren[] = clone $child;
         } elseif ($child instanceof Component && $child->name !== 'VTIMEZONE') {
             // We're also stripping all VTIMEZONE objects because we're
             // converting everything to UTC.
             if ($child->name === 'VEVENT' && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) {
                 // Handle these a bit later.
                 $uid = (string) $child->UID;
                 if (!$uid) {
                     throw new InvalidDataException('Every VEVENT object must have a UID property');
                 }
                 if (isset($recurringEvents[$uid])) {
                     $recurringEvents[$uid][] = clone $child;
                 } else {
                     $recurringEvents[$uid] = [clone $child];
                 }
             } elseif ($child->name === 'VEVENT' && $child->isInTimeRange($start, $end)) {
                 $newChildren[] = $stripTimezones(clone $child);
             }
         }
     }
     foreach ($recurringEvents as $events) {
         try {
             $it = new EventIterator($events, $timeZone);
         } catch (NoInstancesException $e) {
             // This event is recurring, but it doesn't have a single
             // instance. We are skipping this event from the output
             // entirely.
             continue;
         }
         $it->fastForward($start);
         while ($it->valid() && $it->getDTStart() < $end) {
             if ($it->getDTEnd() > $start) {
                 $newChildren[] = $stripTimezones($it->getEventObject());
             }
             $it->next();
         }
     }
     return new self($newChildren);
 }