/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['name' => null, 'is-not-defined' => false, 'comp-filters' => [], 'prop-filters' => [], 'time-range' => false]; $att = $reader->parseAttributes(); $result['name'] = $att['name']; $elems = $reader->parseInnerTree(); if (is_array($elems)) { foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CALDAV . '}comp-filter': $result['comp-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CALDAV . '}prop-filter': $result['prop-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CALDAV . '}is-not-defined': $result['is-not-defined'] = true; break; case '{' . Plugin::NS_CALDAV . '}time-range': if ($result['name'] === 'VCALENDAR') { throw new BadRequest('You cannot add time-range filters on the VCALENDAR component'); } $result['time-range'] = ['start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null]; if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { throw new BadRequest('The end-date must be larger than the start-date'); } break; } } } return $result; }
/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['name' => null, 'is-not-defined' => false, 'param-filters' => [], 'text-match' => null, 'time-range' => false]; $att = $reader->parseAttributes(); $result['name'] = $att['name']; $elems = $reader->parseInnerTree(); if (is_array($elems)) { foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CALDAV . '}param-filter': $result['param-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CALDAV . '}is-not-defined': $result['is-not-defined'] = true; break; case '{' . Plugin::NS_CALDAV . '}time-range': $result['time-range'] = ['start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null]; if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { throw new BadRequest('The end-date must be larger than the start-date'); } break; case '{' . Plugin::NS_CALDAV . '}text-match': $result['text-match'] = ['negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', 'value' => $elem['value']]; break; } } } return $result; }
/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $timeRange = '{' . Plugin::NS_CALDAV . '}time-range'; $start = null; $end = null; foreach ((array) $reader->parseInnerTree([]) as $elem) { if ($elem['name'] !== $timeRange) { continue; } $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start']; $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end']; } if (!$start && !$end) { throw new BadRequest('The freebusy report must have a time-range element'); } if ($start) { $start = DateTimeParser::parseDateTime($start); } if ($end) { $end = DateTimeParser::parseDateTime($end); } $result = new self(); $result->start = $start; $result->end = $end; return $result; }
/** * Returns the value, in the format it should be encoded for json. * * This method must always return an array. * * @return array */ public function getJsonValue() { $return = array(); foreach ($this->getParts() as $item) { list($start, $end) = explode('/', $item, 2); $start = DateTimeParser::parseDateTime($start); // This is a duration value. if ($end[0] === 'P') { $return[] = array($start->format('Y-m-d\\TH:i:s'), $end); } else { $end = DateTimeParser::parseDateTime($end); $return[] = array($start->format('Y-m-d\\TH:i:s'), $end->format('Y-m-d\\TH:i:s')); } } return $return; }
/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['contentType' => $reader->getAttribute('content-type') ?: 'text/calendar', 'version' => $reader->getAttribute('version') ?: '2.0']; $elems = (array) $reader->parseInnerTree(); foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CALDAV . '}expand': $result['expand'] = ['start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null]; if (!$result['expand']['start'] || !$result['expand']['end']) { throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data'); } if ($result['expand']['end'] <= $result['expand']['start']) { throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data'); } break; } } return $result; }
/** * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events */ function testParseICalendarDateTimeGreaterThan4000() { $dateTime = DateTimeParser::parseDateTime('45001231T235959'); $expected = new DateTime('4500-12-31 23:59:59', new DateTimeZone('UTC')); $this->assertEquals($expected, $dateTime); $dateTime = DateTimeParser::parse('45001231T235959'); $this->assertEquals($expected, $dateTime); }
/** * 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; }
/** * Parses the CALDAV:expand element * * @param \DOMElement $parentNode * @return void */ protected function parseExpand(\DOMElement $parentNode) { $start = $parentNode->getAttribute('start'); if (!$start) { throw new \Sabre\DAV\Exception\BadRequest('The "start" attribute is required for the CALDAV:expand element'); } $start = VObject\DateTimeParser::parseDateTime($start); $end = $parentNode->getAttribute('end'); if (!$end) { throw new \Sabre\DAV\Exception\BadRequest('The "end" attribute is required for the CALDAV:expand element'); } $end = VObject\DateTimeParser::parseDateTime($end); if ($end <= $start) { throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); } return array('start' => $start, 'end' => $end); }
/** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * * @return array */ function validate($options = 0) { $messages = parent::validate($options); $valueType = $this->getValueType(); $values = $this->getParts(); try { foreach ($values as $value) { switch ($valueType) { case 'DATE': DateTimeParser::parseDate($value); break; case 'DATE-TIME': DateTimeParser::parseDateTime($value); break; } } } catch (InvalidDataException $e) { $messages[] = ['level' => 3, 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, 'node' => $this]; } return $messages; }
/** * 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; } } } }
/** * This method is responsible for parsing the request and generating the * response for the CALDAV:free-busy-query REPORT. * * @param \DOMNode $dom * @return void */ protected function freeBusyQueryReport(\DOMNode $dom) { $start = null; $end = null; foreach ($dom->firstChild->childNodes as $childNode) { $clark = DAV\XMLUtil::toClarkNotation($childNode); if ($clark == '{' . self::NS_CALDAV . '}time-range') { $start = $childNode->getAttribute('start'); $end = $childNode->getAttribute('end'); break; } } if ($start) { $start = VObject\DateTimeParser::parseDateTime($start); } if ($end) { $end = VObject\DateTimeParser::parseDateTime($end); } $uri = $this->server->getRequestUri(); if (!$start && !$end) { throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); } $acl = $this->server->getPlugin('acl'); if ($acl) { $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); } $calendar = $this->server->tree->getNodeForPath($uri); if (!$calendar instanceof ICalendar) { throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); } $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; // Figuring out the default timezone for the calendar, for floating // times. $calendarProps = $this->server->getProperties($uri, [$tzProp]); if (isset($calendarProps[$tzProp])) { $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]); $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); } else { $calendarTimeZone = new DateTimeZone('UTC'); } // Doing a calendar-query first, to make sure we get the most // performance. $urls = $calendar->calendarQuery(['name' => 'VCALENDAR', 'comp-filters' => [['name' => 'VEVENT', 'comp-filters' => [], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => ['start' => $start, 'end' => $end]]], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => null]); $objects = array_map(function ($url) use($calendar) { $obj = $calendar->getChild($url)->get(); return $obj; }, $urls); $generator = new VObject\FreeBusyGenerator(); $generator->setObjects($objects); $generator->setTimeRange($start, $end); $generator->setTimeZone($calendarTimeZone); $result = $generator->getResult(); $result = $result->serialize(); $this->server->httpResponse->setStatus(200); $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); $this->server->httpResponse->setHeader('Content-Length', strlen($result)); $this->server->httpResponse->setBody($result); }
/** * This method is responsible for parsing the request and generating the * response for the CALDAV:free-busy-query REPORT. * * @param \DOMNode $dom * @return void */ protected function freeBusyQueryReport(\DOMNode $dom) { $start = null; $end = null; foreach ($dom->firstChild->childNodes as $childNode) { $clark = DAV\XMLUtil::toClarkNotation($childNode); if ($clark == '{' . self::NS_CALDAV . '}time-range') { $start = $childNode->getAttribute('start'); $end = $childNode->getAttribute('end'); break; } } if ($start) { $start = VObject\DateTimeParser::parseDateTime($start); } if ($end) { $end = VObject\DateTimeParser::parseDateTime($end); } if (!$start && !$end) { throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); } $acl = $this->server->getPlugin('acl'); if (!$acl) { throw new DAV\Exception('The ACL plugin must be loaded for free-busy queries to work'); } $uri = $this->server->getRequestUri(); $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); $calendar = $this->server->tree->getNodeForPath($uri); if (!$calendar instanceof ICalendar) { throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); } // Doing a calendar-query first, to make sure we get the most // performance. $urls = $calendar->calendarQuery(array('name' => 'VCALENDAR', 'comp-filters' => array(array('name' => 'VEVENT', 'comp-filters' => array(), 'prop-filters' => array(), 'is-not-defined' => false, 'time-range' => array('start' => $start, 'end' => $end))), 'prop-filters' => array(), 'is-not-defined' => false, 'time-range' => null)); $objects = array_map(function ($url) use($calendar) { $obj = $calendar->getChild($url)->get(); return $obj; }, $urls); $generator = new VObject\FreeBusyGenerator(); $generator->setObjects($objects); $generator->setTimeRange($start, $end); $result = $generator->getResult(); $result = $result->serialize(); $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); $this->server->httpResponse->setHeader('Content-Length', strlen($result)); $this->server->httpResponse->sendBody($result); }
/** * Validates the node for correctness. * * The following options are supported: * Node::REPAIR - May attempt to automatically repair the problem. * * This method returns an array with detected problems. * Every element has the following properties: * * * level - problem level. * * message - A human-readable string describing the issue. * * node - A reference to the problematic node. * * The level means: * 1 - The issue was repaired (only happens if REPAIR was turned on) * 2 - An inconsequential issue * 3 - A severe issue. * * @param int $options * @return array */ public function validate($options = 0) { $messages = parent::validate($options); $valueType = $this->getValueType(); $value = $this->getValue(); try { switch ($valueType) { case 'DATE': $foo = DateTimeParser::parseDate($value); break; case 'DATE-TIME': $foo = DateTimeParser::parseDateTime($value); break; } } catch (\LogicException $e) { $messages[] = array('level' => 3, 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, 'node' => $this); } return $messages; }
/** * @depends testParseICalendarDateTime */ function testParseICalendarDateTimeCustomTimeZone() { $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam')); $compare = new DateTime('2010-03-16 13:14:05', new DateTimeZone('UTC')); $this->assertEquals($compare, $dateTime); }
/** * Parse an event update for an attendee. * * This function figures out if we need to send a reply to an organizer. * * @param VCalendar $calendar * @param array $eventInfo * @param array $oldEventInfo * @param string $attendee * @return Message[] */ protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) { $instances = array(); foreach ($oldEventInfo['attendees'][$attendee]['instances'] as $instance) { $instances[$instance['id']] = array('id' => $instance['id'], 'oldstatus' => $instance['partstat'], 'newstatus' => null); } foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) { if (isset($instances[$instance['id']])) { $instances[$instance['id']]['newstatus'] = $instance['partstat']; } else { $instances[$instance['id']] = array('id' => $instance['id'], 'oldstatus' => null, 'newstatus' => $instance['partstat']); } } // We need to also look for differences in EXDATE. If there are new // items in EXDATE, it means that an attendee deleted instances of an // event, which means we need to send DECLINED specifically for those // instances. // We only need to do that though, if the master event is not declined. if ($instances['master']['newstatus'] !== 'DECLINED') { foreach ($eventInfo['exdate'] as $exDate) { if (!in_array($exDate, $oldEventInfo['exdate'])) { $instances[$exDate] = array('id' => $exDate, 'oldstatus' => $instances['master']['oldstatus'], 'newstatus' => 'DECLINED'); } } } $message = new Message(); $message->uid = $eventInfo['uid']; $message->method = 'REPLY'; $message->component = 'VEVENT'; $message->sequence = $eventInfo['sequence']; $message->sender = $attendee; $message->senderName = $eventInfo['attendees'][$attendee]['name']; $message->recipient = $eventInfo['organizer']; $message->recipientName = $eventInfo['organizerName']; $icalMsg = new VCalendar(); $icalMsg->METHOD = 'REPLY'; $hasReply = false; foreach ($instances as $instance) { if ($instance['oldstatus'] == $instance['newstatus']) { // Skip continue; } $event = $icalMsg->add('VEVENT', array('UID' => $message->uid, 'SEQUENCE' => $message->sequence)); if ($instance['id'] !== 'master') { $event->{'RECURRENCE-ID'} = DateTimeParser::parseDateTime($instance['id'], $eventInfo['timezone']); } $organizer = $event->add('ORGANIZER', $message->recipient); if ($message->recipientName) { $organizer['CN'] = $message->recipientName; } $attendee = $event->add('ATTENDEE', $message->sender, array('PARTSTAT' => $instance['newstatus'])); if ($message->senderName) { $attendee['CN'] = $message->senderName; } $hasReply = true; } if ($hasReply) { $message->message = $icalMsg; return array($message); } else { return array(); } }
/** * This method is responsible for parsing the request and generating the * response for the CALDAV:free-busy-query REPORT. * * @param DOMNode $dom * @return void */ protected function freeBusyQueryReport(DOMNode $dom) { $start = null; $end = null; foreach ($dom->firstChild->childNodes as $childNode) { $clark = Sabre_DAV_XMLUtil::toClarkNotation($childNode); if ($clark == '{' . self::NS_CALDAV . '}time-range') { $start = $childNode->getAttribute('start'); $end = $childNode->getAttribute('end'); break; } } if ($start) { $start = VObject\DateTimeParser::parseDateTime($start); } if ($end) { $end = VObject\DateTimeParser::parseDateTime($end); } if (!$start && !$end) { throw new Sabre_DAV_Exception_BadRequest('The freebusy report must have a time-range filter'); } $acl = $this->server->getPlugin('acl'); if (!$acl) { throw new Sabre_DAV_Exception('The ACL plugin must be loaded for free-busy queries to work'); } $uri = $this->server->getRequestUri(); $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); $calendar = $this->server->tree->getNodeForPath($uri); if (!$calendar instanceof Sabre_CalDAV_ICalendar) { throw new Sabre_DAV_Exception_NotImplemented('The free-busy-query REPORT is only implemented on calendars'); } $objects = array_map(function ($child) { $obj = $child->get(); if (is_resource($obj)) { $obj = stream_get_contents($obj); } return $obj; }, $calendar->getChildren()); $generator = new VObject\FreeBusyGenerator(); $generator->setObjects($objects); $generator->setTimeRange($start, $end); $result = $generator->getResult(); $result = $result->serialize(); $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); $this->server->httpResponse->setHeader('Content-Length', strlen($result)); $this->server->httpResponse->sendBody($result); }