/** * Constructor * * The splitter should receive an readable file stream as it's input. * * @param resource $input * @param int $options Parser options, see the OPTIONS constants. */ public function __construct($input, $options = 0) { $data = VObject\Reader::read($input, $options); $vtimezones = array(); $components = array(); foreach ($data->children() as $component) { if (!$component instanceof VObject\Component) { continue; } // Get all timezones if ($component->name === 'VTIMEZONE') { $this->vtimezones[(string) $component->TZID] = $component; continue; } // Get component UID for recurring Events search if ($component->UID) { $uid = (string) $component->UID; } else { // Generating a random UID $uid = sha1(microtime()) . '-vobjectimport'; } // Take care of recurring events if (!array_key_exists($uid, $this->objects)) { $this->objects[$uid] = new VCalendar(); } $this->objects[$uid]->add(clone $component); } }
/** * Merges all vcard objects, and builds one big vcf export * * @param array $nodes * @return string */ public function generateVCF(array $nodes) { $output = ""; foreach ($nodes as $node) { if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) { continue; } $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data']; // Parsing this node so VObject can clean up the output. $output .= VObject\Reader::read($nodeData)->serialize(); } return $output; }
/** * Parses some information from calendar objects, used for optimized * calendar-queries. * * Returns an array with the following keys: * * etag * * size * * componentType * * firstOccurence * * lastOccurence * * @param string $calendarData * @return array */ protected function getDenormalizedData($calendarData) { $vObject = VObject\Reader::read($calendarData); $componentType = null; $component = null; $firstOccurence = null; $lastOccurence = null; foreach ($vObject->getComponents() as $component) { if ($component->name !== 'VTIMEZONE') { $componentType = $component->name; break; } } if (!$componentType) { throw new \SabreForRainLoop\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->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); $lastOccurence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); $lastOccurence = $endDate->getTimeStamp(); } else { $lastOccurence = $firstOccurence; } } else { $it = new VObject\RecurrenceIterator($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(); } } } return array('etag' => md5($calendarData), 'size' => strlen($calendarData), 'componentType' => $componentType, 'firstOccurence' => $firstOccurence, 'lastOccurence' => $lastOccurence); }
/** * This method handles POST requests to the schedule-outbox. * * Currently, two types of requests are support: * * FREEBUSY requests from RFC 6638 * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04 * * The latter is from an expired early draft of the CalDAV scheduling * extensions, but iCal depends on a feature from that spec, so we * implement it. * * @param Schedule\IOutbox $outboxNode * @param string $outboxUri * @return void */ public function outboxRequest(Schedule\IOutbox $outboxNode, $outboxUri) { // Parsing the request body try { $vObject = VObject\Reader::read($this->server->httpRequest->getBody(true)); } catch (VObject\ParseException $e) { throw new DAV\Exception\BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage()); } // The incoming iCalendar object must have a METHOD property, and a // component. The combination of both determines what type of request // this is. $componentType = null; foreach ($vObject->getComponents() as $component) { if ($component->name !== 'VTIMEZONE') { $componentType = $component->name; break; } } if (is_null($componentType)) { throw new DAV\Exception\BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component'); } // Validating the METHOD $method = strtoupper((string) $vObject->METHOD); if (!$method) { throw new DAV\Exception\BadRequest('A METHOD property must be specified in iTIP messages'); } // So we support two types of requests: // // REQUEST with a VFREEBUSY component // REQUEST, REPLY, ADD, CANCEL on VEVENT components $acl = $this->server->getPlugin('acl'); if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') { $acl && $acl->checkPrivileges($outboxUri, '{' . Plugin::NS_CALDAV . '}schedule-query-freebusy'); $this->handleFreeBusyRequest($outboxNode, $vObject); } elseif ($componentType === 'VEVENT' && in_array($method, array('REQUEST', 'REPLY', 'ADD', 'CANCEL'))) { $acl && $acl->checkPrivileges($outboxUri, '{' . Plugin::NS_CALDAV . '}schedule-post-vevent'); $this->handleEventNotification($outboxNode, $vObject); } else { throw new DAV\Exception\NotImplemented('SabreDAV supports only VFREEBUSY (REQUEST) and VEVENT (REQUEST, REPLY, ADD, CANCEL)'); } }
/** * Sets the input objects * * You must either specify a valendar object as a strong, or as the parse * Component. * It's also possible to specify multiple objects as an array. * * @param mixed $objects * @return void */ public function setObjects($objects) { if (!is_array($objects)) { $objects = array($objects); } $this->objects = array(); foreach ($objects as $object) { if (is_string($object)) { $this->objects[] = Reader::read($object); } elseif ($object instanceof Component) { $this->objects[] = $object; } else { throw new \InvalidArgumentException('You can only pass strings or \\SabreForRainLoop\\VObject\\Component arguments to setObjects'); } } }
/** * This method validates if a filters (as passed to calendarQuery) matches * the given object. * * @param array $object * @param array $filters * @return bool */ protected function validateFilterForObject(array $object, array $filters) { // Unfortunately, setting the 'calendardata' here is optional. If // it was excluded, we actually need another call to get this as // well. if (!isset($object['calendardata'])) { $object = $this->getCalendarObject($object['calendarid'], $object['uri']); } $data = is_resource($object['calendardata']) ? stream_get_contents($object['calendardata']) : $object['calendardata']; $vObject = VObject\Reader::read($data); $validator = new CalDAV\CalendarQueryValidator(); return $validator->validate($vObject, $filters); }
/** * Validates if a vcard makes it throught a list of filters. * * @param string $vcardData * @param array $filters * @param string $test anyof or allof (which means OR or AND) * @return bool */ public function validateFilters($vcardData, array $filters, $test) { $vcard = VObject\Reader::read($vcardData); if (!$filters) { return true; } foreach ($filters as $filter) { $isDefined = isset($vcard->{$filter['name']}); if ($filter['is-not-defined']) { if ($isDefined) { $success = false; } else { $success = true; } } elseif (!$filter['param-filters'] && !$filter['text-matches'] || !$isDefined) { // We only need to check for existence $success = $isDefined; } else { $vProperties = $vcard->select($filter['name']); $results = array(); if ($filter['param-filters']) { $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']); } if ($filter['text-matches']) { $texts = array(); foreach ($vProperties as $vProperty) { $texts[] = $vProperty->getValue(); } $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']); } if (count($results) === 1) { $success = $results[0]; } else { if ($filter['test'] === 'anyof') { $success = $results[0] || $results[1]; } else { $success = $results[0] && $results[1]; } } } // else // There are two conditions where we can already determine whether // or not this filter succeeds. if ($test === 'anyof' && $success) { return true; } if ($test === 'allof' && !$success) { return false; } } // foreach // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return $test === 'allof'; }
public function PopulateByVCard($sUid, $sVCard, $sEtag = '', $oLogger = null) { if ("" === \substr($sVCard, 0, 3)) { $sVCard = \substr($sVCard, 3); } $this->Properties = array(); if (!\class_exists('SabreForRainLoop\\DAV\\Client')) { return false; } if (!empty($sEtag)) { $this->Etag = $sEtag; } $this->IdContactStr = $sUid; try { $oVCard = \SabreForRainLoop\VObject\Reader::read($sVCard); } catch (\Exception $oExc) { if ($oLogger) { $oLogger->WriteException($oExc); $oLogger->WriteDump($sVCard); } } // if ($oLogger) // { // $oLogger->WriteDump($sVCard); // } $bOwnCloud = false; $aProperties = array(); if ($oVCard) { $bOwnCloud = empty($oVCard->PRODID) ? false : false !== \strpos(\strtolower($oVCard->PRODID), 'owncloud'); $bOldVersion = empty($oVCard->VERSION) ? false : \in_array((string) $oVCard->VERSION, array('2.1', '2.0', '1.0')); if (isset($oVCard->FN) && '' !== \trim($oVCard->FN)) { $sValue = $this->getPropertyValueHelper($oVCard->FN, $bOldVersion); $aProperties[] = new Property(PropertyType::FULLNAME, $sValue); } if (isset($oVCard->NICKNAME) && '' !== \trim($oVCard->NICKNAME)) { $sValue = $sValue = $this->getPropertyValueHelper($oVCard->NICKNAME, $bOldVersion); $aProperties[] = new Property(PropertyType::NICK_NAME, $sValue); } if (isset($oVCard->NOTE) && '' !== \trim($oVCard->NOTE)) { $sValue = $this->getPropertyValueHelper($oVCard->NOTE, $bOldVersion); $aProperties[] = new Property(PropertyType::NOTE, $sValue); } if (isset($oVCard->N)) { $aNames = $oVCard->N->getParts(); foreach ($aNames as $iIndex => $sValue) { $sValue = \trim($sValue); if ($bOldVersion && !isset($oVCard->N->parameters['CHARSET'])) { if (0 < \strlen($sValue)) { $sEncValue = @\utf8_encode($sValue); if (0 === \strlen($sEncValue)) { $sEncValue = $sValue; } $sValue = $sEncValue; } } $sValue = \MailSo\Base\Utils::Utf8Clear($sValue); switch ($iIndex) { case 0: $aProperties[] = new Property(PropertyType::LAST_NAME, $sValue); break; case 1: $aProperties[] = new Property(PropertyType::FIRST_NAME, $sValue); break; case 2: $aProperties[] = new Property(PropertyType::MIDDLE_NAME, $sValue); break; case 3: $aProperties[] = new Property(PropertyType::NAME_PREFIX, $sValue); break; case 4: $aProperties[] = new Property(PropertyType::NAME_SUFFIX, $sValue); break; } } } if (isset($oVCard->EMAIL)) { $this->addArrayPropertyHelper($aProperties, $oVCard->EMAIL, PropertyType::EMAIl); } if (isset($oVCard->URL)) { $this->addArrayPropertyHelper($aProperties, $oVCard->URL, PropertyType::WEB_PAGE); } if (isset($oVCard->TEL)) { $this->addArrayPropertyHelper($aProperties, $oVCard->TEL, PropertyType::PHONE); } $sUidValue = $oVCard->UID ? (string) $oVCard->UID : \SabreForRainLoop\DAV\UUIDUtil::getUUID(); $aProperties[] = new Property(PropertyType::UID, $sUidValue); if (empty($this->IdContactStr)) { $this->IdContactStr = $sUidValue; } $this->Properties = $aProperties; } $this->UpdateDependentValues(); return true; }
/** * Merges all calendar objects, and builds one big ics export * * @param array $nodes * @return string */ public function generateICS(array $nodes) { $calendar = new VObject\Component\VCalendar(); $calendar->version = '2.0'; if (DAV\Server::$exposeVersion) { $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN'; } else { $calendar->prodid = '-//SabreDAV//SabreDAV//EN'; } $calendar->calscale = 'GREGORIAN'; $collectedTimezones = array(); $timezones = array(); $objects = array(); foreach ($nodes as $node) { if (!isset($node[200]['{' . Plugin::NS_CALDAV . '}calendar-data'])) { continue; } $nodeData = $node[200]['{' . Plugin::NS_CALDAV . '}calendar-data']; $nodeComp = VObject\Reader::read($nodeData); foreach ($nodeComp->children() as $child) { switch ($child->name) { case 'VEVENT': case 'VTODO': case 'VJOURNAL': $objects[] = $child; break; // VTIMEZONE is special, because we need to filter out the duplicates // VTIMEZONE is special, because we need to filter out the duplicates case 'VTIMEZONE': // Naively just checking tzid. if (in_array((string) $child->TZID, $collectedTimezones)) { continue; } $timezones[] = $child; $collectedTimezones[] = $child->TZID; break; } } } foreach ($timezones as $tz) { $calendar->add($tz); } foreach ($objects as $obj) { $calendar->add($obj); } return $calendar->serialize(); }