valid() public method

This is called after next, to see if the iterator is still at a valid position, or if it's at the end.
public valid ( ) : boolean
return boolean
Exemplo n.º 1
0
    /**
     * A pretty slow test. Had to be marked as 'medium' for phpunit to not die
     * after 1 second. Would be good to optimize later.
     *
     * @medium
     */
    function testGetDTEnd()
    {
        $ics = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
TRANSP:OPAQUE
DTEND;TZID=America/New_York:20070925T170000
UID:uuid
DTSTAMP:19700101T000000Z
LOCATION:
DESCRIPTION:
STATUS:CONFIRMED
SEQUENCE:18
SUMMARY:Stuff
DTSTART;TZID=America/New_York:20070925T160000
CREATED:20071004T144642Z
RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU
END:VEVENT
END:VCALENDAR
ICS;
        $vObject = Reader::read($ics);
        $it = new Recur\EventIterator($vObject, (string) $vObject->VEVENT->UID);
        while ($it->valid()) {
            $it->next();
        }
        // If we got here, it means we were successful. The bug that was in the
        // system before would fail on the 5th tuesday of the month, if the 5th
        // tuesday did not exist.
        $this->assertTrue(true);
    }
 /**
  * Different bug, also likely an infinite loop.
  */
 function testYearlyByMonthLoop()
 {
     $ev = $this->vcal->createComponent('VEVENT');
     $ev->UID = 'uuid';
     $ev->DTSTART = '20120101T154500';
     $ev->DTSTART['TZID'] = 'Europe/Berlin';
     $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA';
     $ev->DTEND = '20120101T164500';
     $ev->DTEND['TZID'] = 'Europe/Berlin';
     // This recurrence rule by itself is a yearly rule that should happen
     // every february.
     //
     // The BYDAY part expands this to every day of the month, but the
     // BYSETPOS limits this to only the 1st day of the month. Very crazy
     // way to specify this, and could have certainly been a lot easier.
     $this->vcal->add($ev);
     $it = new Recur\EventIterator($this->vcal, 'uuid');
     $it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC')));
     $collect = array();
     while ($it->valid()) {
         $collect[] = $it->getDTSTART();
         if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) {
             break;
         }
         $it->next();
     }
     $this->assertEquals(array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))), $collect);
 }
Exemplo n.º 3
0
 /**
  * Processes incoming REPLY messages.
  *
  * The message is a reply. This is for example an attendee telling
  * an organizer he accepted the invite, or declined it.
  *
  * @param Message $itipMessage
  * @param VCalendar $existingObject
  * @return VCalendar|null
  */
 protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null)
 {
     // A reply can only be processed based on an existing object.
     // If the object is not available, the reply is ignored.
     if (!$existingObject) {
         return null;
     }
     $instances = array();
     $requestStatus = '2.0';
     // Finding all the instances the attendee replied to.
     foreach ($itipMessage->message->VEVENT as $vevent) {
         $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
         $attendee = $vevent->ATTENDEE;
         $instances[$recurId] = $attendee['PARTSTAT']->getValue();
         if (isset($vevent->{'REQUEST-STATUS'})) {
             $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
             list($requestStatus) = explode(';', $requestStatus);
         }
     }
     // Now we need to loop through the original organizer event, to find
     // all the instances where we have a reply for.
     $masterObject = null;
     foreach ($existingObject->VEVENT as $vevent) {
         $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
         if ($recurId === 'master') {
             $masterObject = $vevent;
         }
         if (isset($instances[$recurId])) {
             $attendeeFound = false;
             if (isset($vevent->ATTENDEE)) {
                 foreach ($vevent->ATTENDEE as $attendee) {
                     if ($attendee->getValue() === $itipMessage->sender) {
                         $attendeeFound = true;
                         $attendee['PARTSTAT'] = $instances[$recurId];
                         $attendee['SCHEDULE-STATUS'] = $requestStatus;
                         // Un-setting the RSVP status, because we now know
                         // that the attende already replied.
                         unset($attendee['RSVP']);
                         break;
                     }
                 }
             }
             if (!$attendeeFound) {
                 // Adding a new attendee. The iTip documentation calls this
                 // a party crasher.
                 $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array('PARTSTAT' => $instances[$recurId]));
                 if ($itipMessage->senderName) {
                     $attendee['CN'] = $itipMessage->senderName;
                 }
             }
             unset($instances[$recurId]);
         }
     }
     if (!$masterObject) {
         // No master object, we can't add new instances.
         return null;
     }
     // If we got replies to instances that did not exist in the
     // original list, it means that new exceptions must be created.
     foreach ($instances as $recurId => $partstat) {
         $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
         $found = false;
         $iterations = 1000;
         do {
             $newObject = $recurrenceIterator->getEventObject();
             $recurrenceIterator->next();
             if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
                 $found = true;
             }
             $iterations--;
         } while ($recurrenceIterator->valid() && !$found && $iterations);
         // Invalid recurrence id. Skipping this object.
         if (!$found) {
             continue;
         }
         unset($newObject->RRULE, $newObject->EXDATE, $newObject->RDATE);
         $attendeeFound = false;
         if (isset($newObject->ATTENDEE)) {
             foreach ($newObject->ATTENDEE as $attendee) {
                 if ($attendee->getValue() === $itipMessage->sender) {
                     $attendeeFound = true;
                     $attendee['PARTSTAT'] = $partstat;
                     break;
                 }
             }
         }
         if (!$attendeeFound) {
             // Adding a new attendee
             $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array('PARTSTAT' => $partstat));
             if ($itipMessage->senderName) {
                 $attendee['CN'] = $itipMessage->senderName;
             }
         }
         $existingObject->add($newObject);
     }
     return $existingObject;
 }
Exemplo n.º 4
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);
 }
Exemplo n.º 5
0
 /**
  * @depends testValues
  */
 function testOverridenEventNoValuesExpected()
 {
     $vcal = new VCalendar();
     $ev1 = $vcal->createComponent('VEVENT');
     $ev1->UID = 'overridden';
     $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3';
     $ev1->DTSTART = '20120124T120000Z';
     $ev1->SUMMARY = 'baseEvent';
     $vcal->add($ev1);
     // ev2 overrides an event, and puts it 6 days earlier instead.
     $ev2 = $vcal->createComponent('VEVENT');
     $ev2->UID = 'overridden';
     $ev2->{'RECURRENCE-ID'} = '20120131T120000Z';
     $ev2->DTSTART = '20120125T120000Z';
     $ev2->SUMMARY = 'Override!';
     $vcal->add($ev2);
     $it = new EventIterator($vcal, 'overridden');
     $dates = array();
     $summaries = array();
     // The reported problem was specifically related to the VCALENDAR
     // expansion. In this parcitular case, we had to forward to the 28th of
     // january.
     $it->fastForward(new DateTime('2012-01-28 23:00:00'));
     // We stop the loop when it hits the 6th of februari. Normally this
     // iterator would hit 24, 25 (overriden from 31) and 7 feb but because
     // we 'filter' from the 28th till the 6th, we should get 0 results.
     while ($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) {
         $dates[] = $it->getDTStart();
         $summaries[] = (string) $it->getEventObject()->SUMMARY;
         $it->next();
     }
     $this->assertEquals(array(), $dates);
     $this->assertEquals(array(), $summaries);
 }
Exemplo n.º 6
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;
 }
Exemplo n.º 7
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);
 }
Exemplo n.º 8
0
 /**
  * Parses some information from calendar objects, used for optimized
  * calendar-queries.
  *
  * Returns an array with the following keys:
  *   * etag - An md5 checksum of the object without the quotes.
  *   * size - Size of the object in bytes
  *   * componentType - VEVENT, VTODO or VJOURNAL
  *   * firstOccurence
  *   * lastOccurence
  *   * uid - value of the UID property
  *
  * @param string $calendarData
  * @return array
  */
 public function getDenormalizedData($calendarData)
 {
     $vObject = Reader::read($calendarData);
     $componentType = null;
     $component = null;
     $firstOccurrence = null;
     $lastOccurrence = null;
     $uid = null;
     $classification = self::CLASSIFICATION_PUBLIC;
     foreach ($vObject->getComponents() as $component) {
         if ($component->name !== 'VTIMEZONE') {
             $componentType = $component->name;
             $uid = (string) $component->UID;
             break;
         }
     }
     if (!$componentType) {
         throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
     }
     if ($componentType === 'VEVENT' && $component->DTSTART) {
         $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
         // Finding the last occurrence is a bit harder
         if (!isset($component->RRULE)) {
             if (isset($component->DTEND)) {
                 $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
             } elseif (isset($component->DURATION)) {
                 $endDate = clone $component->DTSTART->getDateTime();
                 $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
                 $lastOccurrence = $endDate->getTimeStamp();
             } elseif (!$component->DTSTART->hasTime()) {
                 $endDate = clone $component->DTSTART->getDateTime();
                 $endDate->modify('+1 day');
                 $lastOccurrence = $endDate->getTimeStamp();
             } else {
                 $lastOccurrence = $firstOccurrence;
             }
         } else {
             $it = new EventIterator($vObject, (string) $component->UID);
             $maxDate = new \DateTime(self::MAX_DATE);
             if ($it->isInfinite()) {
                 $lastOccurrence = $maxDate->getTimeStamp();
             } else {
                 $end = $it->getDtEnd();
                 while ($it->valid() && $end < $maxDate) {
                     $end = $it->getDtEnd();
                     $it->next();
                 }
                 $lastOccurrence = $end->getTimeStamp();
             }
         }
     }
     if ($component->CLASS) {
         $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
         switch ($component->CLASS->getValue()) {
             case 'PUBLIC':
                 $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
                 break;
             case 'CONFIDENTIAL':
                 $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
                 break;
         }
     }
     return ['etag' => md5($calendarData), 'size' => strlen($calendarData), 'componentType' => $componentType, 'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence), 'lastOccurence' => $lastOccurrence, 'uid' => $uid, 'classification' => $classification];
 }
Exemplo n.º 9
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;
             }
         }
     }
 }
Exemplo n.º 10
0
 /**
  * Parses some information from calendar objects, used for optimized
  * calendar-queries.
  *
  * Returns an array with the following keys:
  *   * etag - An md5 checksum of the object without the quotes.
  *   * size - Size of the object in bytes
  *   * componentType - VEVENT, VTODO or VJOURNAL
  *   * firstOccurence
  *   * lastOccurence
  *   * uid - value of the UID property
  *
  * @param string $calendarData
  * @return array
  */
 protected function getDenormalizedData($calendarData)
 {
     $vObject = VObject\Reader::read($calendarData);
     $componentType = null;
     $component = null;
     $firstOccurence = null;
     $lastOccurence = null;
     $uid = null;
     foreach ($vObject->getComponents() as $component) {
         if ($component->name !== 'VTIMEZONE') {
             $componentType = $component->name;
             $uid = (string) $component->UID;
             break;
         }
     }
     if (!$componentType) {
         throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
     }
     if ($componentType === 'VEVENT') {
         $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
         // Finding the last occurence is a bit harder
         if (!isset($component->RRULE)) {
             if (isset($component->DTEND)) {
                 $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
             } elseif (isset($component->DURATION)) {
                 $endDate = clone $component->DTSTART->getDateTime();
                 $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
                 $lastOccurence = $endDate->getTimeStamp();
             } elseif (!$component->DTSTART->hasTime()) {
                 $endDate = clone $component->DTSTART->getDateTime();
                 $endDate = $endDate->modify('+1 day');
                 $lastOccurence = $endDate->getTimeStamp();
             } else {
                 $lastOccurence = $firstOccurence;
             }
         } else {
             $it = new VObject\Recur\EventIterator($vObject, (string) $component->UID);
             $maxDate = new \DateTime(self::MAX_DATE);
             if ($it->isInfinite()) {
                 $lastOccurence = $maxDate->getTimeStamp();
             } else {
                 $end = $it->getDtEnd();
                 while ($it->valid() && $end < $maxDate) {
                     $end = $it->getDtEnd();
                     $it->next();
                 }
                 $lastOccurence = $end->getTimeStamp();
             }
         }
     }
     // Destroy circular references to PHP will GC the object.
     $vObject->destroy();
     return ['etag' => md5($calendarData), 'size' => strlen($calendarData), 'componentType' => $componentType, 'firstOccurence' => $firstOccurence, 'lastOccurence' => $lastOccurence, 'uid' => $uid];
 }
Exemplo n.º 11
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);
 }
Exemplo n.º 12
0
 /**
  * Validates if a component matches the given time range.
  *
  * This is all based on the rules specified in rfc4791, which are quite
  * complex.
  *
  * @param VObject\Node $component
  * @param DateTime $start
  * @param DateTime $end
  * @return bool
  */
 protected function validateTimeRange(VObject\Node $component, $start, $end)
 {
     if (is_null($start)) {
         $start = new DateTime('1900-01-01');
     }
     if (is_null($end)) {
         $end = new DateTime('3000-01-01');
     }
     switch ($component->name) {
         case 'VEVENT':
         case 'VTODO':
         case 'VJOURNAL':
             return $component->isInTimeRange($start, $end);
         case 'VALARM':
             // If the valarm is wrapped in a recurring event, we need to
             // expand the recursions, and validate each.
             //
             // Our datamodel doesn't easily allow us to do this straight
             // in the VALARM component code, so this is a hack, and an
             // expensive one too.
             if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
                 // Fire up the iterator!
                 $it = new VObject\Recur\EventIterator($component->parent->parent, (string) $component->parent->UID);
                 while ($it->valid()) {
                     $expandedEvent = $it->getEventObject();
                     // We need to check from these expanded alarms, which
                     // one is the first to trigger. Based on this, we can
                     // determine if we can 'give up' expanding events.
                     $firstAlarm = null;
                     if ($expandedEvent->VALARM !== null) {
                         foreach ($expandedEvent->VALARM as $expandedAlarm) {
                             $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
                             if ($expandedAlarm->isInTimeRange($start, $end)) {
                                 return true;
                             }
                             if ((string) $expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
                                 // This is an alarm with a non-relative trigger
                                 // time, likely created by a buggy client. The
                                 // implication is that every alarm in this
                                 // recurring event trigger at the exact same
                                 // time. It doesn't make sense to traverse
                                 // further.
                             } else {
                                 // We store the first alarm as a means to
                                 // figure out when we can stop traversing.
                                 if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
                                     $firstAlarm = $effectiveTrigger;
                                 }
                             }
                         }
                     }
                     if (is_null($firstAlarm)) {
                         // No alarm was found.
                         //
                         // Or technically: No alarm that will change for
                         // every instance of the recurrence was found,
                         // which means we can assume there was no match.
                         return false;
                     }
                     if ($firstAlarm > $end) {
                         return false;
                     }
                     $it->next();
                 }
                 return false;
             } else {
                 return $component->isInTimeRange($start, $end);
             }
         case 'VFREEBUSY':
             throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
         case 'COMPLETED':
         case 'CREATED':
         case 'DTEND':
         case 'DTSTAMP':
         case 'DTSTART':
         case 'DUE':
         case 'LAST-MODIFIED':
             return $start <= $component->getDateTime() && $end >= $component->getDateTime();
         default:
             throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
     }
 }
Exemplo n.º 13
0
 /**
  * Processes incoming REPLY messages.
  *
  * The message is a reply. This is for example an attendee telling
  * an organizer he accepted the invite, or declined it.
  *
  * @param Message $itipMessage
  * @param VCalendar $existingObject
  * @return VCalendar|null
  */
 protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null)
 {
     // A reply can only be processed based on an existing object.
     // If the object is not available, the reply is ignored.
     if (!$existingObject) {
         return null;
     }
     $instances = array();
     foreach ($itipMessage->message->VEVENT as $vevent) {
         $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
         $attendee = $vevent->ATTENDEE;
         $instances[$recurId] = $attendee['PARTSTAT']->getValue();
     }
     $masterObject = null;
     foreach ($existingObject->VEVENT as $vevent) {
         $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
         if ($recurId === 'master') {
             $masterObject = $vevent;
         }
         if (isset($instances[$recurId])) {
             $attendeeFound = false;
             if (isset($vevent->ATTENDEE)) {
                 foreach ($vevent->ATTENDEE as $attendee) {
                     if ($attendee->getValue() === $itipMessage->sender) {
                         $attendeeFound = true;
                         $attendee['PARTSTAT'] = $instances[$recurId];
                         break;
                     }
                 }
             }
             if (!$attendeeFound) {
                 // Adding a new attendee. The iTip documentation calls this
                 // a party crasher.
                 $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array('PARTSTAT' => $instances[$recurId]));
                 if ($itipMessage->senderName) {
                     $attendee['CN'] = $itipMessage->senderName;
                 }
             }
             unset($instances[$recurId]);
         }
     }
     if (!$masterObject) {
         // No master object, we can't add new instances.
         return null;
     }
     // If we got replies to instances that did not exist in the
     // original list, it means that new exceptions must be created.
     foreach ($instances as $recurId => $partstat) {
         $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
         $found = false;
         $iterations = 1000;
         do {
             $newObject = $recurrenceIterator->getEventObject();
             $recurrenceIterator->next();
             if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
                 $found = true;
             }
             $iterations--;
         } while ($recurrenceIterator->valid() && !$found && $iterations);
         // Invalid recurrence id. Skipping this object.
         if (!$found) {
             continue;
         }
         unset($newObject->RRULE, $newObject->EXDATE, $newObject->RDATE);
         $newObject->{'RECURRENCE-ID'} = $recurId;
         $attendeeFound = false;
         if (isset($newObject->ATTENDEE)) {
             foreach ($newObject->ATTENDEE as $attendee) {
                 if ($attendee->getValue() === $itipMessage->sender) {
                     $attendeeFound = true;
                     $attendee['PARTSTAT'] = $partstat;
                     break;
                 }
             }
         }
         if (!$attendeeFound) {
             // Adding a new attendee
             $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array('PARTSTAT' => $partstat));
             if ($itipMessage->senderName) {
                 $attendee['CN'] = $itipMessage->senderName;
             }
         }
         $existingObject->add($newObject);
     }
     return $existingObject;
 }