public function testGeo() { $ical = new Horde_Icalendar(); $ical->parsevCalendar(file_get_contents(__DIR__ . '/fixtures/geo1.vcf')); $this->assertEquals(array('latitude' => -17.87, 'longitude' => 37.24), $ical->getComponent(0)->getAttribute('GEO')); $ical->parsevCalendar(file_get_contents(__DIR__ . '/fixtures/geo2.vcf')); $this->assertEquals(array('latitude' => 37.386013, 'longitude' => -122.082932), $ical->getComponent(0)->getAttribute('GEO')); }
private function _getFixture($element) { $iCal = new Horde_Icalendar(); $iCal->parsevCalendar(file_get_contents(__DIR__ . '/../fixtures/allday.ics')); $components = $iCal->getComponents(); return $components[$element]; }
/** * Variables required in form input: * - imple_submit: vcard action. Contains import and source properties * - mime_id * - muid * * @return boolean True on success. */ protected function _handle(Horde_Variables $vars) { global $registry, $injector, $notification; $iCal = new Horde_Icalendar(); try { $contents = $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices_Mailbox($vars)); if (!($mime_part = $contents->getMimePart($vars->mime_id))) { throw new IMP_Exception(_("Cannot retrieve vCard data from message.")); } elseif (!$iCal->parsevCalendar($mime_part->getContents(), 'VCALENDAR', $mime_part->getCharset())) { throw new IMP_Exception(_("Error reading the contact data.")); } $components = $iCal->getComponents(); } catch (Exception $e) { $notification->push($e, 'horde.error'); } $import = !empty($vars->imple_submit->import) ? $vars->imple_submit->import : false; $source = !empty($vars->imple_submit->source) ? $vars->imple_submit->source : false; if ($import && $source && $registry->hasMethod('contacts/import')) { $count = 0; foreach ($components as $c) { if ($c->getType() == 'vcard') { try { $registry->call('contacts/import', array($c, null, $source)); ++$count; } catch (Horde_Exception $e) { $notification->push(Horde_Core_Translation::t("There was an error importing the contact data:") . ' ' . $e->getMessage(), 'horde.error'); } } } $notification->push(sprintf(Horde_Core_Translation::ngettext("%d contact was successfully added to your address book.", "%d contacts were successfully added to your address book.", $count), $count), 'horde.success'); } }
/** * Yet another problem: Outlook seems to remove the organizer from * the iCal when forwarding -- we put the original sender back in * as organizer. * * @param string $icaltext The ical message. * @param MIME_Headers $from The message sender. */ function _addOrganizer(&$icaltxt, $from) { global $conf; if (isset($conf['kolab']['filter']['email_domain'])) { $email_domain = $conf['kolab']['filter']['email_domain']; } else { $email_domain = 'localhost'; } $iCal = new Horde_Icalendar(); $iCal->parsevCalendar($icaltxt); $vevent =& $iCal->findComponent('VEVENT'); if ($vevent) { $organizer = $vevent->getAttribute('ORGANIZER', true); if (is_a($organizer, 'PEAR_Error')) { $adrs = imap_rfc822_parse_adrlist($from, $email_domain); if (count($adrs) > 0) { $org_email = 'mailto:' . $adrs[0]->mailbox . '@' . $adrs[0]->host; $org_name = $adrs[0]->personal; if ($org_name) { $vevent->setAttribute('ORGANIZER', $org_email, array('CN' => $org_name), false); } else { $vevent->setAttribute('ORGANIZER', $org_email, array(), false); } Horde::log(sprintf("Adding missing organizer '%s <%s>' to iCal.", $org_name, $org_email), 'DEBUG'); $icaltxt = $iCal->exportvCalendar(); } } } }
/** * Return the rendered inline version of the Horde_Mime_Part object. * * @return array See parent::render(). */ protected function _renderInline() { $GLOBALS['page_output']->growler = true; $data = $this->_mimepart->getContents(); $mime_id = $this->_mimepart->getMimeId(); // Parse the iCal file. $vCal = new Horde_Icalendar(); if (!$vCal->parsevCalendar($data, 'VCALENDAR', $this->_mimepart->getCharset())) { $status = new IMP_Mime_Status(_("The calendar data is invalid")); $status->action(IMP_Mime_Status::ERROR); return array($mime_id => array('data' => '', 'status' => $status, 'type' => 'text/html; charset=UTF-8')); } // Check if we got vcard data with the wrong vcalendar mime type. $imp_contents = $this->getConfigParam('imp_contents'); $c = $vCal->getComponentClasses(); if (count($c) == 1 && !empty($c['horde_icalendar_vcard'])) { return $imp_contents->renderMIMEPart($mime_id, IMP_Contents::RENDER_INLINE, array('type' => 'text/x-vcard')); } $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_ItipRequest', array('mime_id' => $mime_id, 'muid' => strval($imp_contents->getIndicesOb()))); // Get the method type. try { $method = $vCal->getAttribute('METHOD'); } catch (Horde_Icalendar_Exception $e) { $method = ''; } $out = array(); $components = $vCal->getComponents(); foreach ($components as $key => $component) { switch ($component->getType()) { case 'vEvent': try { if ($component->getAttribute('RECURRENCE-ID')) { break; } } catch (Horde_ICalendar_Exception $e) { } $out[] = $this->_vEvent($component, $key, $method, $components); break; case 'vTodo': $out[] = $this->_vTodo($component, $key, $method); break; case 'vTimeZone': // Ignore them. break; case 'vFreebusy': $out[] = $this->_vFreebusy($component, $key, $method); break; // @todo: handle stray vcards here as well. // @todo: handle stray vcards here as well. default: $out[] = sprintf(_("Unhandled component of type: %s"), $component->getType()); break; } } $view = $this->_getViewOb(); $view->formid = $imple->getDomId(); $view->out = implode('', $out); return array($mime_id => array('data' => $view->render('base'), 'type' => 'text/html; charset=UTF-8')); }
public function testIgnoringMultipleAttributeValues() { $ical = new Horde_Icalendar(); $ical->parsevCalendar(file_get_contents(__DIR__ . '/fixtures/multiple-summary.ics')); $result = $ical->getComponent(0)->getAttributeSingle('SUMMARY'); $this->assertInternalType('string', $result); $this->assertEquals('Summary 1', $result); }
private function _getFixture($name, $item = 0) { $iCal = new Horde_Icalendar(); $iCal->parsevCalendar(file_get_contents(__DIR__ . '/../fixtures/' . $name)); $components = $iCal->getComponents(); $event = new Kronolith_Event_Sql(new Kronolith_Stub_Driver()); $event->fromiCalendar($components[$item]); return $event; }
public function testFiles() { $test_files = glob(__DIR__ . '/fixtures/charset*.ics'); foreach ($test_files as $file) { $ical = new Horde_Icalendar(); $ical->parsevCalendar(file_get_contents($file)); $this->assertEquals('möchen', $ical->getComponent(0)->getAttribute('SUMMARY')); } }
public function testBug14132() { $ical = new Horde_Icalendar(); $ical->parsevCalendar(file_get_contents(__DIR__ . '/fixtures/bug14132.ics')); $params = $ical->getComponent(1)->getAttribute('DTSTART', true); $tz = $params[0]['TZID']; $start = $ical->getComponent(1)->getAttribute('DTSTART'); $dtstart = new Horde_Date($start, $tz); $this->assertEquals((string) $dtstart, '2015-10-09 03:00:00'); }
/** * @dataProvider timezones */ public function testFile($file) { $result = ''; $ical = new Horde_Icalendar(); $ical->parsevCalendar(file_get_contents($file)); foreach ($ical->getComponents() as $component) { if ($component->getType() != 'vEvent') { continue; } $date = $component->getAttribute('DTSTART'); if (is_array($date)) { continue; } $result .= str_replace("\r", '', $component->getAttribute('SUMMARY')) . "\n"; $d = new Horde_Date($date); $result .= $d->format('H:i') . "\n"; } $this->assertStringEqualsFile(__DIR__ . '/fixtures/vTimezone/' . basename($file, 'ics') . 'txt', $result, 'Failed parsing file ' . basename($file)); }
/** * Parses a string containing vFreebusy data. * * @param string $data The data to parse. * @param $type TODO * @param $charset TODO */ public function parsevCalendar($data, $type = null, $charset = null) { parent::parsevCalendar($data, 'VFREEBUSY', $charset); // Do something with all the busy periods. foreach ($this->_attributes as $key => $attribute) { if ($attribute['name'] != 'FREEBUSY') { continue; } foreach ($attribute['values'] as $value) { $params = isset($attribute['params']) ? $attribute['params'] : array(); if (isset($value['duration'])) { $this->addBusyPeriod('BUSY', $value['start'], null, $value['duration'], $params); } else { $this->addBusyPeriod('BUSY', $value['start'], $value['end'], null, $params); } } unset($this->_attributes[$key]); } }
/** * Retrieve Free/Busy data for the specified resource. * * @param string $resource Fetch the Free/Busy data for this resource. * * @return Horde_Icalendar_Vfreebusy The Free/Busy data. */ public function get($resource) { global $conf; $url = self::getUrl($resource); Horde::log(sprintf('Freebusy URL for resource %s is %s', $resource, $url), 'DEBUG'); list($user, $domain) = explode('@', $resource); if (empty($domain)) { $domain = $conf['kolab']['filter']['email_domain']; } /** * This section matches Kronolith_Freebusy and should be merged with it * again in a single Horde_Freebusy module. */ $options = array('method' => 'GET', 'timeout' => 5, 'allowRedirects' => true); if (!empty($conf['http']['proxy']['proxy_host'])) { $options = array_merge($options, $conf['http']['proxy']); } $http = new HTTP_Request($url, $options); $http->setBasicAuth($conf['kolab']['filter']['calendar_id'] . '@' . $domain, $conf['kolab']['filter']['calendar_pass']); @$http->sendRequest(); if ($http->getResponseCode() != 200) { throw new Horde_Kolab_Resource_Exception(sprintf('Unable to retrieve free/busy information for %s', $resource), Horde_Kolab_Resource_Exception::NO_FREEBUSY); } $vfb_text = $http->getResponseBody(); // Detect the charset of the iCalendar data. $contentType = $http->getResponseHeader('Content-Type'); if ($contentType && strpos($contentType, ';') !== false) { list(, $charset, ) = explode(';', $contentType); $vfb_text = Horde_String::convertCharset($vfb_text, trim(str_replace('charset=', '', $charset)), 'UTF-8'); } $iCal = new Horde_Icalendar(); $iCal->parsevCalendar($vfb_text, 'VCALENDAR'); $vfb =& $iCal->findComponent('VFREEBUSY'); if ($vfb === false) { throw new Horde_Kolab_Resource_Exception(sprintf('Invalid or no free/busy information available for %s', $resource), Horde_Kolab_Resource_Exception::NO_FREEBUSY); } $vfb->simplify(); return $vfb; }
public function testRead() { $ical = new Horde_Icalendar(); $ical->parsevCalendar(file_get_contents(__DIR__ . '/fixtures/vfreebusy1.ics')); // Get the vFreeBusy component $vfb = $ical->getComponent(0); // Dump the type $this->assertEquals('vFreebusy', $vfb->getType()); // Dump the vfreebusy component again (the duration should be // converted to start/end $this->assertStringEqualsFile(__DIR__ . '/fixtures/vfreebusy2.ics', $vfb->exportvCalendar()); // Dump organizer name $this->assertEquals('GunnarWrobel', $vfb->getName()); // Dump organizer mail $this->assertEquals('*****@*****.**', $vfb->getEmail()); // Dump busy periods $this->assertEquals(array(1164258000 => 1164261600, 1164268800 => 1164276000), $vfb->getBusyPeriods()); // Decode the summary information $extra = $vfb->getExtraParams(); $this->assertEquals('testtermin', base64_decode($extra[1164258000]['X-SUMMARY'])); // Dump the free periods in between the two given time stamps $this->assertEquals(array(1164261600 => 1164268800), $vfb->getFreePeriods(1164261500, 1164268900)); // Dump start of the free/busy information $this->assertEquals(1164236400, $vfb->getStart()); // Dump end of the free/busy information $this->assertEquals(1169420400, $vfb->getEnd()); // Free periods don't get added $vfb->addBusyPeriod('FREE', 1164261600, 1164268800); $this->assertEquals(array(1164258000 => 1164261600, 1164268800 => 1164276000), $vfb->getBusyPeriods()); // Add a busy period with start/end (11:00 / 12:00) $vfb->addBusyPeriod('BUSY', 1164279600, 1164283200); // Add a busy period with start/duration (14:00 / 2h) $vfb->addBusyPeriod('BUSY', 1164290400, null, 7200, array('X-SUMMARY' => 'dGVzdA==')); // Dump busy periods $this->assertEquals(array(1164258000 => 1164261600, 1164268800 => 1164276000, 1164279600 => 1164283200, 1164290400 => 1164297600), $vfb->getBusyPeriods()); // Dump the extra parameters $this->assertEquals(array(1164258000 => array('X-UID' => 'MmZlNWU3NDRmMGFjNjZkNjRjZjFkZmFmYTE4NGFiZTQ=', 'X-SUMMARY' => 'dGVzdHRlcm1pbg=='), 1164268800 => array(), 1164279600 => array(), 1164290400 => array('X-SUMMARY' => 'dGVzdA==')), $vfb->getExtraParams()); return $vfb; }
/** * @throws Kronolith_Exception */ public function search($email, $private_only = false) { $server = $GLOBALS['injector']->getInstance('Horde_Kolab_Session')->getFreebusyServer(); if (empty($server)) { throw new Horde_Exception_NotFound(); } $http = $GLOBALS['injector']->getInstance('Horde_Core_Factory_HttpClient')->create(array('request.username' => $GLOBALS['registry']->getAuth(), 'request.password' => $GLOBALS['registry']->getAuthCredential('password'))); try { $response = $http->get(sprintf('%s/%s.xfb', $server, $email)); } catch (Horde_Http_Exception $e) { throw new Horde_Exception_NotFound(); } if ($response->code != 200) { throw new Horde_Exception_NotFound(); } $vfb_text = $response->getBody(); $iCal = new Horde_Icalendar(); $iCal->parsevCalendar($vfb_text); $vfb = $iCal->findComponent('VFREEBUSY'); if ($vfb === false) { throw new Horde_Exception_NotFound(); } return $vfb; }
/** * Return the full rendered version of the Horde_Mime_Part object. * * URL parameters used by this function: * - c: (integer) The VCARD component that contains an image. * - p: (integer) The index of image inside the component to display. * * @return array See parent::render(). * @throws Horde_Exception */ protected function _renderInline() { $vars = $GLOBALS['injector']->getInstance('Horde_Variables'); if (!isset($vars->p)) { $imp_contents = $this->getConfigParam('imp_contents'); $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_VcardImport', array('mime_id' => $this->_mimepart->getMimeId(), 'muid' => strval($imp_contents->getIndicesOb()))); $this->_imageUrl = $this->getConfigParam('imp_contents')->urlView($this->_mimepart, 'download_render', array('params' => array('mode' => IMP_Contents::RENDER_INLINE))); return parent::_renderInline(); } /* Send the requested photo. */ $data = $this->_mimepart->getContents(); $ical = new Horde_Icalendar(); if (!$ical->parsevCalendar($data, 'VCALENDAR', $this->_mimepart->getCharset())) { // TODO: Error reporting return array(); } $components = $ical->getComponents(); if (!isset($components[$vars->c])) { // TODO: Error reporting return array(); } $name = $components[$vars->c]->getAttributeDefault('FN', false); if ($name === false) { $name = $components[$vars->c]->printableName(); } if (empty($name)) { $name = preg_replace('/\\..*?$/', '', $this->_mimepart->getName()); } $photos = $components[$vars->c]->getAllAttributes('PHOTO'); if (!isset($photos[$vars->p])) { // TODO: Error reporting return array(); } $type = 'image/' . Horde_String::lower($photos[$vars->p]['params']['TYPE']); return array($this->_mimepart->getMimeId() => array('data' => base64_decode($photos[$vars->p]['value']), 'name' => $name . '.' . Horde_Mime_Magic::mimeToExt($type), 'type' => $type)); }
protected function toHash($vcard, $map = array()) { $driver = new Turba_Driver(); foreach ($map as $field => $config) { $driver->map[$field] = $config; } $ical = new Horde_Icalendar(); $ical->parsevCalendar($vcard); return $driver->toHash($ical->getComponent(0)); }
/** * Replaces the event identified by UID with the content represented in the * specified contentType. * * @param string $uid Idenfity the event to replace. * @param mixed $content The content of the event. String or * Horde_Icalendar_Vevent * @param string $contentType What format is the data in? Currently supports: * text/calendar * text/x-vcalendar * (Ignored if content is Horde_Icalendar_Vevent) * activesync (Horde_ActiveSync_Message_Appointment) * @param string $calendar Ensure the event is replaced in the specified * calendar. @since 4.2.0 * * @return mixed For EAS operations, an array of 'uid' and 'atchash' * are returned. @since 4.3.0 * @throws Kronolith_Exception */ public function replace($uid, $content, $contentType, $calendar = null) { $event = Kronolith::getDriver(null, $calendar)->getByUID($uid); if (!$event->hasPermission(Horde_Perms::EDIT) || $event->private && $event->creator != $GLOBALS['registry']->getAuth()) { throw new Horde_Exception_PermissionDenied(); } if ($content instanceof Horde_Icalendar_Vevent) { $component = $content; } elseif ($content instanceof Horde_ActiveSync_Message_Appointment) { $event->fromASAppointment($content); $atc_hash = $event->addEASFiles($content); $event->save(); $event->uid = $uid; return array('uid' => $event->uid, 'atchash' => $atc_hash); return; } else { switch ($contentType) { case 'text/calendar': case 'text/x-vcalendar': if (!$content instanceof Horde_Icalendar_Vevent) { $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($content)) { throw new Kronolith_Exception(_("There was an error importing the iCalendar data.")); } $components = $iCal->getComponents(); $component = null; foreach ($components as $content) { if ($content instanceof Horde_Icalendar_Vevent) { if ($component !== null) { throw new Kronolith_Exception(_("Multiple iCalendar components found; only one vEvent is supported.")); } $component = $content; } } if ($component === null) { throw new Kronolith_Exception(_("No iCalendar data was found.")); } } break; default: throw new Kronolith_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } } try { $component->getAttribute('RECURRENCE-ID'); $this->_addiCalEvent($component, Kronolith::getDriver(null, $calendar), true); } catch (Horde_Icalendar_Exception $e) { $event->fromiCalendar($component, true); // Ensure we keep the original UID, even when content does not // contain one and fromiCalendar creates a new one. $event->uid = $uid; $event->save(); } }
private function _getIcalendar() { $part = $this->_getMimeMessage(); if (!($ics = $part[2])) { $this->fail('Missing second message part!'); } $iCal = new Horde_Icalendar(); $iCal->parsevCalendar($ics->getContents()); return $iCal; }
/** * Return the rendered inline version of the Horde_Mime_Part object. * * @return array See parent::render(). */ protected function _renderInline() { $browser = $this->getConfigParam('browser'); $notification = $this->getConfigParam('notification'); $prefs = $this->getConfigParam('prefs'); $registry = $this->getConfigParam('registry'); $data = $this->_mimepart->getContents(); $html = ''; $title = Horde_Core_Translation::t("vCard"); $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($data, 'VCALENDAR', $this->_mimepart->getCharset())) { $notification->push(Horde_Core_Translation::t("There was an error reading the contact data."), 'horde.error'); } if (Horde_Util::getFormData('import') && Horde_Util::getFormData('source') && $registry->hasMethod('contacts/import')) { $source = Horde_Util::getFormData('source'); $count = 0; foreach ($iCal->getComponents() as $c) { if ($c->getType() == 'vcard') { try { $registry->call('contacts/import', array($c, null, $source)); ++$count; } catch (Horde_Exception $e) { $notification->push(Horde_Core_Translation::t("There was an error importing the contact data:") . ' ' . $e->getMessage(), 'horde.error'); } } } $notification->push(sprintf(Horde_Core_Translation::ngettext("%d contact was successfully added to your address book.", "%d contacts were successfully added to your address book.", $count), $count), 'horde.success'); } $html .= '<table class="horde-table" style="width:100%">'; foreach ($iCal->getComponents() as $i => $vc) { if ($i > 0) { $html .= '<tr><td colspan="2"> </td></tr>'; } $addresses = $vc->getAllAttributes('EMAIL'); $html .= '<tr><td colspan="2" class="header">'; if (($fullname = $vc->getAttributeDefault('FN', false)) === false) { $fullname = count($addresses) ? $addresses[0]['value'] : Horde_Core_Translation::t("[No Label]"); } $html .= htmlspecialchars($fullname) . '</td></tr>'; $n = $vc->printableName(); if (!empty($n)) { $html .= $this->_row(Horde_Core_Translation::t("Name"), $n); } try { $html .= $this->_row(Horde_Core_Translation::t("Alias"), implode("\n", $vc->getAttributeValues('ALIAS'))); } catch (Horde_Icalendar_Exception $e) { } try { $birthdays = $vc->getAttributeValues('BDAY'); $birthday = new Horde_Date($birthdays[0]); $html .= $this->_row(Horde_Core_Translation::t("Birthday"), $birthday->strftime($prefs->getValue('date_format'))); } catch (Horde_Icalendar_Exception $e) { } $photos = $vc->getAllAttributes('PHOTO'); foreach ($photos as $p => $photo) { if (isset($photo['params']['VALUE']) && Horde_String::upper($photo['params']['VALUE']) == 'URI') { $html .= $this->_row(Horde_Core_Translation::t("Photo"), '<img src="' . htmlspecialchars($photo['value']) . '" />', false); } elseif (isset($photo['params']['ENCODING']) && Horde_String::upper($photo['params']['ENCODING']) == 'B' && isset($photo['params']['TYPE'])) { if ($browser->hasFeature('datauri') === true || $browser->hasFeature('datauri') >= strlen($photo['value'])) { $html .= $this->_row(Horde_Core_Translation::t("Photo"), '<img src="' . Horde_Url_Data::create($photo['params']['TYPE'], base64_decode($photo['value'])) . '" />', false); } elseif ($this->_imageUrl) { $html .= $this->_row(Horde_Core_Translation::t("Photo"), '<img src="' . $this->_imageUrl->add(array('c' => $i, 'p' => $p)) . '" />', false); } } } $labels = $vc->getAllAttributes('LABEL'); foreach ($labels as $label) { if (isset($label['params']['TYPE'])) { if (!is_array($label['params']['TYPE'])) { $label['params']['TYPE'] = array($label['params']['TYPE']); } } else { $label['params']['TYPE'] = array_keys($label['params']); } $types = array(); foreach ($label['params']['TYPE'] as $type) { switch (Horde_String::upper($type)) { case 'HOME': $types[] = Horde_Core_Translation::t("Home Address"); break; case 'WORK': $types[] = Horde_Core_Translation::t("Work Address"); break; case 'DOM': $types[] = Horde_Core_Translation::t("Domestic Address"); break; case 'INTL': $types[] = Horde_Core_Translation::t("International Address"); break; case 'POSTAL': $types[] = Horde_Core_Translation::t("Postal Address"); break; case 'PARCEL': $types[] = Horde_Core_Translation::t("Parcel Address"); break; case 'PREF': $types[] = Horde_Core_Translation::t("Preferred Address"); break; } } if (!count($types)) { $types = array(Horde_Core_Translation::t("Address")); } $html .= $this->_row(implode('/', $types), $label['value']); } $adrs = $vc->getAllAttributes('ADR'); foreach ($adrs as $item) { if (isset($item['params']['TYPE'])) { if (!is_array($item['params']['TYPE'])) { $item['params']['TYPE'] = array($item['params']['TYPE']); } } else { $item['params']['TYPE'] = array_keys($item['params']); } $address = $item['values']; $a = array(); $a_list = array(Horde_Icalendar_Vcard::ADR_STREET, Horde_Icalendar_Vcard::ADR_LOCALITY, Horde_Icalendar_Vcard::ADR_REGION, Horde_Icalendar_Vcard::ADR_POSTCODE, Horde_Icalendar_Vcard::ADR_COUNTRY); foreach ($a_list as $val) { if (isset($address[$val])) { $a[] = $address[$val]; } } $types = array(); foreach ($item['params']['TYPE'] as $type) { switch (Horde_String::upper($type)) { case 'HOME': $types[] = Horde_Core_Translation::t("Home Address"); break; case 'WORK': $types[] = Horde_Core_Translation::t("Work Address"); break; case 'DOM': $types[] = Horde_Core_Translation::t("Domestic Address"); break; case 'INTL': $types[] = Horde_Core_Translation::t("International Address"); break; case 'POSTAL': $types[] = Horde_Core_Translation::t("Postal Address"); break; case 'PARCEL': $types[] = Horde_Core_Translation::t("Parcel Address"); break; case 'PREF': $types[] = Horde_Core_Translation::t("Preferred Address"); break; } } if (!count($types)) { $types = array(Horde_Core_Translation::t("Address")); } $html .= $this->_row(implode('/', $types), implode("\n", $a)); } $numbers = $vc->getAllAttributes('TEL'); foreach ($numbers as $number) { if (isset($number['params']['TYPE'])) { if (!is_array($number['params']['TYPE'])) { $number['params']['TYPE'] = array($number['params']['TYPE']); } foreach ($number['params']['TYPE'] as $type) { $number['params'][Horde_String::upper($type)] = true; } } if (isset($number['params']['FAX'])) { $html .= $this->_row(Horde_Core_Translation::t("Fax"), $number['value']); } else { if (isset($number['params']['HOME'])) { $html .= $this->_row(Horde_Core_Translation::t("Home Phone"), $number['value']); } elseif (isset($number['params']['WORK'])) { $html .= $this->_row(Horde_Core_Translation::t("Work Phone"), $number['value']); } elseif (isset($number['params']['CELL'])) { $html .= $this->_row(Horde_Core_Translation::t("Cell Phone"), $number['value']); } else { $html .= $this->_row(Horde_Core_Translation::t("Phone"), $number['value']); } } } $emails = array(); foreach ($addresses as $address) { if (isset($address['params']['TYPE'])) { if (!is_array($address['params']['TYPE'])) { $address['params']['TYPE'] = array($address['params']['TYPE']); } foreach ($address['params']['TYPE'] as $type) { $address['params'][Horde_String::upper($type)] = true; } } $email = '<a href="'; if ($registry->hasMethod('mail/compose')) { $email .= $registry->call('mail/compose', array(array('to' => $address['value']))); } else { $email .= 'mailto:' . htmlspecialchars($address['value']); } $email .= '">' . htmlspecialchars($address['value']) . '</a>'; if (isset($address['params']['PREF'])) { array_unshift($emails, $email); } else { $emails[] = $email; } } if (count($emails)) { $html .= $this->_row(Horde_Core_Translation::t("Email"), implode("\n", $emails), false); } try { $title = $vc->getAttributeValues('TITLE'); $html .= $this->_row(Horde_Core_Translation::t("Title"), $title[0]); } catch (Horde_Icalendar_Exception $e) { } try { $role = $vc->getAttributeValues('ROLE'); $html .= $this->_row(Horde_Core_Translation::t("Role"), $role[0]); } catch (Horde_Icalendar_Exception $e) { } try { $org = $vc->getAttributeValues('ORG'); $html .= $this->_row(Horde_Core_Translation::t("Company"), $org[0]); if (isset($org[1])) { $html .= $this->_row(Horde_Core_Translation::t("Department"), $org[1]); } } catch (Horde_Icalendar_Exception $e) { } try { $notes = $vc->getAttributeValues('NOTE'); $html .= $this->_row(Horde_Core_Translation::t("Notes"), $notes[0]); } catch (Horde_Icalendar_Exception $e) { } try { $url = $vc->getAttributeValues('URL'); $html .= $this->_row(Horde_Core_Translation::t("URL"), '<a href="' . htmlspecialchars($url[0]) . '" target="_blank">' . htmlspecialchars($url[0]) . '</a>', false); } catch (Horde_Icalendar_Exception $e) { } } $html .= '</table>'; if ($registry->hasMethod('contacts/import') && $registry->hasMethod('contacts/sources')) { $html .= '<div class="horde-form-buttons"><form action="' . Horde::selfUrl() . '" method="get" name="vcard_import" id="vcard_import">' . Horde_Util::formInput(); foreach ($_GET as $key => $val) { $html .= '<input type="hidden" name="' . htmlspecialchars($key) . '" value="' . htmlspecialchars($val) . '" />'; } $sources = $registry->call('contacts/sources', array(true)); if (count($sources) > 1) { $html .= '<input type="submit" class="horde-default" name="import" value="' . Horde_Core_Translation::t("Add to address book:") . '" />' . ' <label for="add_source" class="hidden">' . Horde_Core_Translation::t("Address Book") . '</label>' . '<select id="add_source" name="source">'; foreach ($sources as $key => $label) { $selected = $key == $prefs->getValue('add_source') ? ' selected="selected"' : ''; $html .= '<option value="' . htmlspecialchars($key) . '"' . $selected . '>' . htmlspecialchars($label) . '</option>'; } $html .= '</select>'; } else { reset($sources); $html .= '<input type="submit" class="horde-default" name="import" value="' . Horde_Core_Translation::t("Add to my address book") . '" />' . '<input type="hidden" name="source" value="' . htmlspecialchars(key($sources)) . '" />'; } $html .= '</form></div>'; } Horde::startBuffer(); $notification->notify(array('listeners' => 'status')); return $this->_renderReturn(Horde::endBuffer() . $html, 'text/html; charset=' . $this->getConfigParam('charset')); }
/** * Add or update an event from DAV * * @param string $collection An external collection ID. * @param string $object An external object ID. * @param string $data Icalendar data */ public function davPutObject($collection, $object, $data) { $dav = $GLOBALS['injector']->getInstance('Horde_Dav_Storage'); $internal = $dav->getInternalCollectionId($collection, 'calendar') ?: $collection; if (!Kronolith::hasPermission($internal, Horde_Perms::EDIT)) { throw new Kronolith_Exception(_("Calendar does not exist or no permission to edit")); } $ical = new Horde_Icalendar(); if (!$ical->parsevCalendar($data)) { throw new Kronolith_Exception(_("There was an error importing the iCalendar data.")); } $importer = new Kronolith_Icalendar_Handler_Dav($ical, Kronolith::getDriver(null, $internal), array('object' => $object)); $importer->process(); }
/** * Replaces the contact identified by UID with the content represented in * the specified contentType. * * @param string $uid Idenfity the contact to replace. * @param mixed $content The content of the contact. * @param string $contentType What format is the data in? Currently * supports array, text/directory, * text/vcard, text/x-vcard and activesync. * @param string|array $sources The source(s) where the contact will be * replaced. * * @return boolean Success or failure. * @throws Turba_Exception */ public function replace($uid, $content, $contentType, $sources = null) { if (empty($uid)) { throw new Turba_Exception(_("Invalid contact unique ID")); } $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver'); foreach ($this->_getSources($sources) as $source) { $sdriver = $driver->create($source); // Check permissions. if (!$sdriver->hasPermission(Horde_Perms::EDIT)) { continue; } $result = $sdriver->search(array('__uid' => $uid)); if (!count($result)) { continue; } elseif (count($result) > 1) { throw new Turba_Exception(sprintf(_("Multiple contacts found with same unique ID %s."), $uid)); } $object = $result->objects[0]; switch ($contentType) { case 'activesync': $content = $sdriver->fromASContact($content); foreach ($content as $attribute => $value) { if ($attribute != '__key') { $object->setValue($attribute, $value); } } return $object->store(); case 'array': break; case 'text/x-vcard': case 'text/vcard': case 'text/directory': $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($content)) { throw new Turba_Exception(_("There was an error importing the iCalendar data.")); } switch ($iCal->getComponentCount()) { case 0: throw new Turba_Exception(_("No vCard data was found.")); case 1: $content = $sdriver->toHash($iCal->getComponent(0)); break; default: throw new Turba_Exception(_("Only one vcard supported.")); } break; default: throw new Turba_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } foreach ($content as $attribute => $value) { if ($attribute != '__key') { $object->setValue($attribute, $value); } } return $object->store(); } throw new Turba_Exception(sprintf(_("Object %s not found."), $uid)); }
/** * TODO * * @param $data TODO */ public function parsevCalendar($text, $base = 'VCALENDAR', $clear = true) { parent::parsevCalendar($text, 'STANDARD'); }
/** * Builds a proper AS mail message object. * * @param Horde_Imap_Client_Mailbox $mbox The IMAP mailbox. * @param Horde_Imap_Client_Data_Fetch $data The fetch results. * @param array $options Additional Options: * - truncation: (integer) Truncate the message body to this length. * DEFAULT: No truncation. * - bodyprefs: (array) Bodyprefs, if sent from device. * DEFAULT: none (No body prefs sent or enforced). * - bodypartprefs: (array) Bodypartprefs, if sent from device. * DEFAULT: none (No body part prefs sent or enforced). * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol version to support. * DEFAULT: 2.5 * * @return Horde_ActiveSync_Message_Mail The message object suitable for * streaming to the device. */ protected function _buildMailMessage(Horde_Imap_Client_Mailbox $mbox, Horde_Imap_Client_Data_Fetch $data, $options = array()) { $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; $imap_message = new Horde_ActiveSync_Imap_Message($this->_getImapOb(), $mbox, $data); $eas_message = Horde_ActiveSync::messageFactory('Mail'); // Build To: data (POOMMAIL_TO has a max length of 32768). $to = $imap_message->getToAddresses(); $eas_message->to = array_pop($to['to']); foreach ($to['to'] as $to_atom) { if (strlen($eas_message->to) + strlen($to_atom) > 32768) { break; } $eas_message->to .= ',' . $to_atom; } $eas_message->displayto = implode(';', $to['displayto']); if (empty($eas_message->displayto)) { $eas_message->displayto = $eas_message->to; } // Ensure we don't send broken UTF8 data to the client. It makes clients // angry. And we don't like angry clients. $hdr_charset = $imap_message->getStructure()->getHeaderCharset(); // Fill in other header data try { $eas_message->from = $imap_message->getFromAddress(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } try { $eas_message->cc = $imap_message->getCc(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } try { $eas_message->reply_to = $imap_message->getReplyTo(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } $eas_message->subject = Horde_ActiveSync_Utils::ensureUtf8($imap_message->getSubject(), $hdr_charset); $eas_message->threadtopic = $eas_message->subject; $eas_message->datereceived = $imap_message->getDate(); $eas_message->read = $imap_message->getFlag(Horde_Imap_Client::FLAG_SEEN); // Default to IPM.Note - may change below depending on message content. $eas_message->messageclass = 'IPM.Note'; // Codepage id. MS recommends to always set to UTF-8 when possible. // See http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx $eas_message->cpid = Horde_ActiveSync_Message_Mail::INTERNET_CPID_UTF8; // Message importance. First try X-Priority, then Importance since // Outlook sends the later. if ($priority = $imap_message->getHeaders()->getValue('X-priority')) { $priority = preg_replace('/\\D+/', '', $priority); } else { $priority = $imap_message->getHeaders()->getValue('Importance'); } $eas_message->importance = $this->_getEASImportance($priority); // Get the body data. $mbd = $imap_message->getMessageBodyDataObject($options); if ($version == Horde_ActiveSync::VERSION_TWOFIVE) { $eas_message->body = $mbd->plain['body']->stream; $eas_message->bodysize = $mbd->plain['body']->length(true); $eas_message->bodytruncated = $mbd->plain['truncated']; $eas_message->attachments = $imap_message->getAttachments($version); } else { // Get the message body and determine original type. if ($mbd->html) { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_HTML; } else { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; } $airsync_body = Horde_ActiveSync::messageFactory('AirSyncBaseBody'); $body_type_pref = $mbd->getBodyTypePreference(); if ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_MIME) { $this->_logger->info(sprintf('[%s] Sending MIME Message.', $this->_procid)); // ActiveSync *REQUIRES* all data sent to be in UTF-8, so we // must convert the body parts to UTF-8. Unfortunately if the // email is signed (or encrypted for that matter) we can't // alter the data in anyway or the signature will not be // verified, so we fetch the entire message and hope for the best. if (!$imap_message->isSigned() && !$imap_message->isEncrypted()) { $mime = new Horde_Mime_Part(); if ($mbd->plain) { $plain_mime = new Horde_Mime_Part(); $plain_mime->setType('text/plain'); $plain_mime->setContents($mbd->plain['body']->stream, array('usestream' => true)); $plain_mime->setCharset('UTF-8'); } if ($mbd->html) { $html_mime = new Horde_Mime_Part(); $html_mime->setType('text/html'); $html_mime->setContents($mbd->html['body']->stream, array('usestream' => true)); $html_mime->setCharset('UTF-8'); } // Sanity check the mime type if (!$mbd->html && !empty($plain_mime)) { $mime = $plain_mime; } elseif (!$mbd->plain && !empty($html_mime)) { $mime = $html_mime; } elseif (!empty($plain_mime) && !empty($html_mime)) { $mime->setType('multipart/alternative'); $mime->addPart($plain_mime); $mime->addPart($html_mime); } $html_mime = null; $plain_mime = null; // If we have attachments, create a multipart/mixed wrapper. if ($imap_message->hasAttachments()) { $base = new Horde_Mime_Part(); $base->setType('multipart/mixed'); $base->addPart($mime); $atc = $imap_message->getAttachmentsMimeParts(); foreach ($atc as $atc_part) { $base->addPart($atc_part); } $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } else { $base = $mime; } $mime = null; // Populate the EAS body structure with the MIME data, but // remove the Content-Type and Content-Transfer-Encoding // headers since we are building this ourselves. $headers = $imap_message->getHeaders(); $headers->removeHeader('Content-Type'); $headers->removeHeader('Content-Transfer-Encoding'); $airsync_body->data = $base->toString(array('headers' => $headers, 'stream' => true)); $airsync_body->estimateddatasize = $base->getBytes(); } else { // Signed/Encrypted message - can't mess with it at all. $raw = new Horde_ActiveSync_Rfc822($imap_message->getFullMsg(true), false); $airsync_body->estimateddatasize = $raw->getBytes(); $airsync_body->data = $raw->getString(); $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_MIME; // MIME Truncation // @todo Remove this sanity-check hack in 3.0. This is needed // since truncationsize incorrectly defaulted to a // MIME_TRUNCATION constant and could be cached in the sync-cache. $ts = !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize']) ? $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize'] : false; $mime_truncation = !empty($ts) && $ts > 9 ? $ts : (!empty($options['truncation']) && $options['truncation'] > 9 ? $options['truncation'] : false); $this->_logger->info(sprintf('[%s] Checking MIMETRUNCATION: %s, ServerData: %s', $this->_procid, $mime_truncation, $airsync_body->estimateddatasize)); if (!empty($mime_truncation) && $airsync_body->estimateddatasize > $mime_truncation) { ftruncate($airsync_body->data, $mime_truncation); $airsync_body->truncated = '1'; } else { $airsync_body->truncated = '0'; } $eas_message->airsyncbasebody = $airsync_body; } elseif ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_HTML) { // Sending non MIME encoded HTML message text. $eas_message->airsyncbasebody = $this->_buildHtmlPart($mbd, $airsync_body); $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } elseif ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_PLAIN) { // Non MIME encoded plaintext $this->_logger->info(sprintf('[%s] Sending PLAINTEXT Message.', $this->_procid)); if (!empty($mbd->plain['size'])) { $airsync_body->estimateddatasize = $mbd->plain['size']; $airsync_body->truncated = $mbd->plain['truncated']; $airsync_body->data = $mbd->plain['body']->stream; $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; $eas_message->airsyncbasebody = $airsync_body; } $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } // It's legal to have both a BODY and a BODYPART, so we must also // check for that. if ($version > Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodypartprefs'])) { $body_part = Horde_ActiveSync::messageFactory('AirSyncBaseBodypart'); $eas_message->airsyncbasebodypart = $this->_buildBodyPart($mbd, $options, $body_part); } if ($version > Horde_ActiveSync::VERSION_TWELVEONE) { $flags = array(); $msgFlags = $this->_getMsgFlags(); foreach ($imap_message->getFlags() as $flag) { if (!empty($msgFlags[Horde_String::lower($flag)])) { $flags[] = $msgFlags[Horde_String::lower($flag)]; } } $eas_message->categories = $flags; } } // Body Preview? Note that this is different from BodyPart's preview if ($version >= Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodyprefs']['preview'])) { $mbd->plain['body']->rewind(); $eas_message->airsyncbasebody->preview = $mbd->plain['body']->substring(0, $options['bodyprefs']['preview']); } $mbd = null; // Check for special message types. if ($imap_message->isEncrypted()) { $eas_message->messageclass = 'IPM.Note.SMIME'; } elseif ($imap_message->isSigned()) { $eas_message->messageclass = 'IPM.Note.SMIME.MultipartSigned'; } $part = $imap_message->getStructure(); if ($part->getType() == 'multipart/report') { $ids = array_keys($imap_message->contentTypeMap()); reset($ids); $part1_id = next($ids); $part2_id = Horde_Mime::mimeIdArithmetic($part1_id, 'next'); $lines = explode(chr(13), $imap_message->getBodyPart($part2_id, array('decode' => true))); switch ($part->getContentTypeParameter('report-type')) { case 'delivery-status': foreach ($lines as $line) { if (strpos(trim($line), 'Action:') === 0) { switch (trim(substr(trim($line), 7))) { case 'failed': $eas_message->messageclass = 'REPORT.IPM.NOTE.NDR'; break 2; case 'delayed': $eas_message->messageclass = 'REPORT.IPM.NOTE.DELAYED'; break 2; case 'delivered': $eas_message->messageclass = 'REPORT.IPM.NOTE.DR'; break 2; } } } break; case 'disposition-notification': foreach ($lines as $line) { if (strpos(trim($line), 'Disposition:') === 0) { if (strpos($line, 'displayed') !== false) { $eas_message->messageclass = 'REPORT.IPM.NOTE.IPNRN'; } elseif (strpos($line, 'deleted') !== false) { $eas_message->messageclass = 'REPORT.IPM.NOTE.IPNNRN'; } break; } } } } $part = null; // Check for meeting requests and POOMMAIL_FLAG data if ($version >= Horde_ActiveSync::VERSION_TWELVE) { $eas_message->contentclass = 'urn:content-classes:message'; if ($mime_part = $imap_message->hasiCalendar()) { $data = Horde_ActiveSync_Utils::ensureUtf8($mime_part->getContents(), $mime_part->getCharset()); $vCal = new Horde_Icalendar(); if ($vCal->parsevCalendar($data, 'VCALENDAR', $mime_part->getCharset())) { $classes = $vCal->getComponentClasses(); } else { $classes = array(); } if (!empty($classes['horde_icalendar_vevent'])) { try { $method = $vCal->getAttribute('METHOD'); $eas_message->contentclass = 'urn:content-classes:calendarmessage'; } catch (Horde_Icalendar_Exception $e) { } switch ($method) { case 'REQUEST': case 'PUBLISH': $eas_message->messageclass = 'IPM.Schedule.Meeting.Request'; $mtg = Horde_ActiveSync::messageFactory('MeetingRequest'); $mtg->fromvEvent($vCal); $eas_message->meetingrequest = $mtg; break; case 'REPLY': try { $reply_status = $this->_getiTipStatus($vCal); switch ($reply_status) { case 'ACCEPTED': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Pos'; break; case 'DECLINED': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Neg'; break; case 'TENTATIVE': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Tent'; } $mtg = Horde_ActiveSync::messageFactory('MeetingRequest'); $mtg->fromvEvent($vCal); $eas_message->meetingrequest = $mtg; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } } } } if ($imap_message->getFlag(Horde_Imap_Client::FLAG_FLAGGED)) { $poommail_flag = Horde_ActiveSync::messageFactory('Flag'); $poommail_flag->subject = $imap_message->getSubject(); $poommail_flag->flagstatus = Horde_ActiveSync_Message_Flag::FLAG_STATUS_ACTIVE; $poommail_flag->flagtype = Horde_Imap_Client::FLAG_FLAGGED; $eas_message->flag = $poommail_flag; } } if ($version >= Horde_ActiveSync::VERSION_FOURTEEN) { $eas_message->messageid = $imap_message->getHeaders()->getValue('Message-ID'); $eas_message->forwarded = $imap_message->getFlag(Horde_Imap_Client::FLAG_FORWARDED); $eas_message->answered = $imap_message->getFlag(Horde_Imap_Client::FLAG_ANSWERED); } $imap_message = null; return $eas_message; }
/** * Replace the memo identified by UID with the content represented in * the specified contentType. * * @param string $uid Idenfity the memo to replace. * @param string $content The content of the memo. * @param string $contentType What format is the data in? Currently supports: * text/plain * text/x-vnote * activesync * @throws Mnemo_Exception * @throws Horde_Exception_PermissionDenied */ public function replace($uid, $content, $contentType) { $storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create(); $memo = $storage->getByUID($uid); if (!array_key_exists($memo['memolist_id'], Mnemo::listNotepads(false, Horde_Perms::EDIT))) { throw new Horde_Exception_PermissionDenied(); } switch ($contentType) { case 'text/plain': $storage->modify($memo['memo_id'], $storage->getMemoDescription($content), $content, null); break; case 'text/x-vnote': if (!$content instanceof Horde_Icalendar_Vnote) { $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($content)) { throw new Mnemo_Exception(_("There was an error importing the iCalendar data.")); } $components = $iCal->getComponents(); switch (count($components)) { case 0: throw new Mnemo_Exception(_("No iCalendar data was found.")); case 1: $content = $components[0]; break; default: throw new Mnemo_Exception(_("Multiple iCalendar components found; only one vNote is supported.")); } } $note = $storage->fromiCalendar($content); $storage->modify($memo['memo_id'], $note['desc'], $note['body'], !empty($note['tags']) ? $note['tags'] : ''); break; case 'activesync': $storage->modify($memo['memo_id'], $content->subject, $content->body->data, $content->categories); break; default: throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } }
public function vtodo2sif($vcard) { $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($vcard)) { return PEAR::raiseError('There was an error importing the data.'); } $components = $iCal->getComponents(); switch (count($components)) { case 0: return PEAR::raiseError('No data was found'); case 1: $content = $components[0]; break; default: return PEAR::raiseError('Multiple components found; only one is supported.'); } $hash['Complete'] = 0; $due = false; $attr = $content->getAllAttributes(); foreach ($attr as $item) { switch ($item['name']) { case 'SUMMARY': $hash['Subject'] = $item['value']; break; case 'DESCRIPTION': $hash['Body'] = $item['value']; break; case 'PRIORITY': if ($item['value'] == 1) { $hash['Importance'] = 2; } elseif ($item['value'] == 5) { $hash['Importance'] = 0; } else { $hash['Importance'] = 1; } break; case 'DTSTART': $hash['StartDate'] = Horde_Icalendar::_exportDateTime($item['value']); break; case 'DUE': $hash['DueDate'] = Horde_Icalendar::_exportDateTime($item['value']); $due = $item['value']; break; case 'AALARM': $hash['ReminderTime'] = $item['value']; $hash['ReminderSet'] = 1; break; case 'STATUS': $hash['Complete'] = $item['value'] == 'COMPLETED' ? 1 : 0; break; case 'CATEGORIES': $hash['Categories'] = $item['value']; break; case 'CLASS': switch (Horde_String::upper($item['value'])) { case 'PUBLIC': $hash['Sensitivity'] = 0; break; case 'PRIVATE': $hash['Sensitivity'] = 2; break; case 'CONFIDENTIAL': $hash['Sensitivity'] = 3; break; } break; } } if ($due && !isset($hash['ReminderSet'])) { // Parse VALARM components. foreach ($content->getComponents() as $component) { if ($component->getType() != 'vAlarm') { continue; } try { $trigger = $component->getAttribute('TRIGGER'); } catch (Horde_Icalendar_Exception $e) { continue; } if (is_array($trigger) || empty($trigger)) { continue; } $hash['ReminderSet'] = 1; $hash['ReminderTime'] = Horde_Icalendar::_exportDateTime($due - $trigger); } } return Horde_SyncMl_Device_sync4j::array2sif($hash, '<?xml version="1.0"?><task>', '</task>'); }
/** * Retrieves the free/busy information for a given email address, if any * information is available. * * @param string $email The email address to look for. * @param boolean $json Whether to return the free/busy data as a simple * object suitable to be transferred as json. * * @return Horde_Icalendar_Vfreebusy|object Free/busy component. * @throws Kronolith_Exception */ public static function get($email, $json = false) { $default_domain = empty($GLOBALS['conf']['storage']['default_domain']) ? null : $GLOBALS['conf']['storage']['default_domain']; $rfc822 = new Horde_Mail_Rfc822(); try { $res = $rfc822->parseAddressList($email, array('default_domain' => $default_domain)); } catch (Horde_Mail_Exception $e) { throw new Kronolith_Exception($e); } if (!($tmp = $res[0])) { throw new Kronolith_Exception(_("No valid email address found")); } $email = $tmp->bare_address; /* Check if we can retrieve a VFB from the Free/Busy URL, if one is * set. */ $url = self::getUrl($email); if ($url) { $url = trim($url); $http = $GLOBALS['injector']->getInstance('Horde_Core_Factory_HttpClient')->create(array('request.verifyPeer' => false)); try { $response = $http->get($url); } catch (Horde_Http_Exception $e) { throw new Kronolith_Exception(sprintf(_("The free/busy url for %s cannot be retrieved."), $email)); } if ($response->code == 200 && ($data = $response->getBody())) { // Detect the charset of the iCalendar data. $contentType = $response->getHeader('Content-Type'); if ($contentType && strpos($contentType, ';') !== false) { list(, $charset, ) = explode(';', $contentType); $data = Horde_String::convertCharset($data, trim(str_replace('charset=', '', $charset)), 'UTF-8'); } $vCal = new Horde_Icalendar(); $vCal->parsevCalendar($data, 'VCALENDAR'); $components = $vCal->getComponents(); $vCal = new Horde_Icalendar(); $vFb = Horde_Icalendar::newComponent('vfreebusy', $vCal); $vFb->setAttribute('ORGANIZER', $email); $found = false; foreach ($components as $component) { if ($component instanceof Horde_Icalendar_Vfreebusy) { $found = true; $vFb->merge($component); } } if ($found) { // @todo: actually store the results in the storage, so // that they can be retrieved later. We should store the // plain iCalendar data though, to avoid versioning // problems with serialize iCalendar objects. return $json ? self::toJson($vFb) : $vFb; } } } /* Check storage driver. */ $storage = $GLOBALS['injector']->getInstance('Kronolith_Factory_Storage')->create(); try { $fb = $storage->search($email); return $json ? self::toJson($fb) : $fb; } catch (Horde_Exception_NotFound $e) { if ($url) { throw new Kronolith_Exception(sprintf(_("No free/busy information found at the free/busy url of %s."), $email)); } throw new Kronolith_Exception(sprintf(_("No free/busy url found for %s."), $email)); } }
/** * Handle meeting responses. * * @param array $response The response data. Contains: * - requestid: The identifier of the meeting request. Used by the server * to fetch the original meeting request details. * - response: The user's response to the request. One of the response * code constants. * - folderid: The collection id that contains the meeting request. * - * * @return string The UID of any created calendar entries, otherwise false. * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ public function meetingResponse(array $response) { if (empty($response['folderid']) || empty($response['requestid']) || empty($response['response'])) { throw new Horde_ActiveSync_Exception('Invalid meeting response.'); } // First thing we need is to obtain the meeting request. $imap_message = $this->_imap->getImapMessage($response['folderid'], $response['requestid']); if (empty($imap_message)) { throw new Horde_Exception_NotFound(); } $imap_message = $imap_message[$response['requestid']]; // Find the request if (!($part = $imap_message->hasiCalendar())) { $this->_logger->err('Unable to find the meeting request.'); throw new Horde_Exception_NotFound(); } // Parse the vCal $vCal = new Horde_Icalendar(); $data = $part->getContents(); if (!$vCal->parsevCalendar($data, 'VCALENDAR', $part->getCharset())) { throw new Horde_ActiveSync_Exception('Unknown error parsing vCal data.'); } if (!($vEvent = $vCal->findComponent('vEvent'))) { throw new Horde_ActiveSync_Exception('Unknown error locating vEvent.'); } // Create an event from the vEvent. // Note we don't use self::changeMessage since we don't want to treat // this as an incoming message addition from the PIM. Otherwise, the // message may not get synched back to the PIM. try { $uid = $this->_connector->calendar_import_vevent($vEvent); } catch (Horde_Exception $e) { $this->_logger->err($e->getMessage()); throw new Horde_ActiveSync_Exception($e); } // Start building the iTip response email. try { $organizer = parse_url($vEvent->getAttribute('ORGANIZER')); $organizer = $organizer['path']; } catch (Horde_Icalendar_Exception $e) { $this->_logger->err('Unable to find organizer.'); throw new Horde_ActiveSync_Exception($e); } $ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->_user); $cn = $ident->getValue('fullname'); $email = $ident->getValue('from_addr'); // Can't use Horde_Itip_Resource_Identity since it takes an IMP identity $resource = new Horde_Itip_Resource_Base($email, $cn); switch ($response['response']) { case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_ACCEPTED: $type = new Horde_Itip_Response_Type_Accept($resource); break; case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_DECLINED: $type = new Horde_Itip_Response_Type_Decline($resource); break; case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_TENTATIVE: $type = new Horde_Itip_Response_Type_Tentative($resource); break; } // Delete the original request. EAS Specs require this. Most clients // will remove the email from the UI as soon as the response is sent. // Failure to remove it from the server will result in an inconsistent // sync state. $this->_imap->deleteMessages(array($response['requestid']), $response['folderid']); return $uid; }
/** * @requires extension bcmath */ public function testMeetingTnef() { $winmail = file_get_contents(__DIR__ . '/fixtures/winmail2.dat'); $tnef = Horde_Compress::factory('Tnef'); $tnef_data = $tnef->decompress($winmail); // Test the meta data $this->assertEquals($tnef_data[0]['type'], 'text'); $this->assertEquals($tnef_data[0]['subtype'], 'calendar'); $this->assertEquals($tnef_data[0]['name'], 'Test Meeting'); // Test the generated iCalendar. $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($tnef_data[0]['stream'])) { throw new Horde_Compress_Exception(_("There was an error importing the iCalendar data.")); } $components = $iCal->getComponents(); if (count($components) == 0) { throw new Horde_Compress_Exception(_("No iCalendar data was found.")); } $iTip = current($components); $this->assertEquals($iTip->getAttribute('SUMMARY'), 'Test Meeting'); $this->assertEquals($iTip->getAttribute('DESCRIPTION'), 'This is a test meeting.'); $this->assertEquals($iTip->getAttribute('ORGANIZER'), 'mailto:mike@theupstairsroom.com'); $this->assertEquals($iTip->getAttribute('UID'), 'D38D34D34D34F36D347B4D34EF87396F00000000367B4D3CD34D34D34D34EF4774D3877BF3ADDA774D35D34D34D34D34D34D34D34D34D74D34D34D34D3471CF747DB6F469FE5EE34F386FCE75F79DFC6B675FE9D'); $this->assertEquals($iTip->getAttribute('ATTENDEE'), '*****@*****.**'); $params = $iTip->getAttribute('ATTENDEE', true); if (!$params) { throw new Horde_Compress_Exception('Could not find expected parameters.'); } $this->assertEquals($params[0]['ROLE'], 'REQ-PARTICIPANT'); $this->assertEquals($params[0]['PARTSTAT'], 'NEEDS-ACTION'); $this->assertEquals($params[0]['RSVP'], 'TRUE'); }
/** * Variables required in form input: * - identity (TODO: ? Code uses it, but it is never set anywhere) * - imple_submit: itip_action(s) * - mime_id * - muid * * @return boolean True on success. */ protected function _handle(Horde_Variables $vars) { global $injector, $notification, $registry; $actions = (array) $vars->imple_submit; $result = false; $vCal = new Horde_Icalendar(); /* Retrieve the calendar data from the message. */ try { $contents = $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices_Mailbox($vars)); $mime_part = $contents->getMIMEPart($vars->mime_id); if (empty($mime_part)) { throw new IMP_Exception(_("Cannot retrieve calendar data from message.")); } elseif (!$vCal->parsevCalendar($mime_part->getContents(), 'VCALENDAR', $mime_part->getCharset())) { throw new IMP_Exception(_("The calendar data is invalid")); } $components = $vCal->getComponents(); } catch (Exception $e) { $notification->push($e, 'horde.error'); $actions = array(); } foreach ($actions as $key => $action) { $pos = strpos($key, '['); $key = substr($key, $pos + 1, strlen($key) - $pos - 2); switch ($action) { case 'delete': // vEvent cancellation. if ($registry->hasMethod('calendar/delete')) { $guid = $components[$key]->getAttribute('UID'); $recurrenceId = null; try { // This is a cancellation of a recurring event instance. $recurrenceId = $components[$key]->getAttribute('RECURRENCE-ID'); $atts = $components[$key]->getAttribute('RECURRENCE-ID', true); $range = null; foreach ($atts as $att) { if (array_key_exists('RANGE', $att)) { $range = $att['RANGE']; } } } catch (Horde_Icalendar_Exception $e) { } try { $registry->call('calendar/delete', array($guid, $recurrenceId, $range)); $notification->push(_("Event successfully deleted."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error deleting the event: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'update': // vEvent reply. if ($registry->hasMethod('calendar/updateAttendee')) { try { $from = $contents->getHeader()->getOb('from'); $registry->call('calendar/updateAttendee', array($components[$key], $from[0]->bare_address)); $notification->push(_("Respondent Status Updated."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error updating the event: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'import': case 'accept-import': // vFreebusy reply. // vFreebusy publish. // vEvent request. // vEvent publish. // vTodo publish. // vJournal publish. switch ($components[$key]->getType()) { case 'vEvent': $result = $this->_handlevEvent($key, $components, $mime_part); // Must check for exceptions. foreach ($components as $k => $component) { try { if ($component->getType() == 'vEvent' && $component->getAttribute('RECURRENCE-ID')) { $uid = $component->getAttribute('UID'); if ($uid == $components[$key]->getAttribute('UID')) { $this->_handlevEvent($k, $components, $mime_part); } } } catch (Horde_Icalendar_Exception $e) { } } break; case 'vFreebusy': // Import into Kronolith. if ($registry->hasMethod('calendar/import_vfreebusy')) { try { $registry->call('calendar/import_vfreebusy', array($components[$key])); $notification->push(_("The user's free/busy information was sucessfully stored."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error importing user's free/busy information: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'vTodo': // Import into Nag. if ($registry->hasMethod('tasks/import')) { try { $guid = $registry->call('tasks/import', array($components[$key], $mime_part->getType())); $url = Horde::url($registry->link('tasks/show', array('uid' => $guid))); $notification->push(_("The task has been added to your tasklist.") . ' ' . Horde::link($url, _("View task"), null, '_blank') . Horde_Themes_Image::tag('mime/icalendar.png', array('alt' => _("View task"))) . '</a>', 'horde.success', array('content.raw')); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error importing the task: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'vJournal': default: $notification->push(_("This action is not supported."), 'horde.warning'); } if ($action == 'import') { break; } // Fall-through for 'accept-import' // Fall-through for 'accept-import' case 'accept': case 'deny': case 'tentative': // vEvent request. if (isset($components[$key]) && $components[$key]->getType() == 'vEvent') { $vEvent = $components[$key]; $resource = new Horde_Itip_Resource_Identity($injector->getInstance('IMP_Identity'), $vEvent->getAttribute('ATTENDEE'), $vars->identity); switch ($action) { case 'accept': case 'accept-import': $type = new Horde_Itip_Response_Type_Accept($resource); break; case 'deny': $type = new Horde_Itip_Response_Type_Decline($resource); break; case 'tentative': $type = new Horde_Itip_Response_Type_Tentative($resource); break; } try { // Send the reply. Horde_Itip::factory($vEvent, $resource)->sendMultiPartResponse($type, new Horde_Core_Itip_Response_Options_Horde('UTF-8', array()), $injector->getInstance('IMP_Mail')); $notification->push(_("Reply Sent."), 'horde.success'); $result = true; } catch (Horde_Itip_Exception $e) { $notification->push(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'send': case 'reply': case 'reply2m': // vfreebusy request. if (isset($components[$key]) && $components[$key]->getType() == 'vFreebusy') { $vFb = $components[$key]; // Get the organizer details. try { $organizer = parse_url($vFb->getAttribute('ORGANIZER')); } catch (Horde_Icalendar_Exception $e) { break; } $organizerEmail = $organizer['path']; $organizer = $vFb->getAttribute('ORGANIZER', true); $organizerFullEmail = new Horde_Mail_Rfc822_Address($organizerEmail); if (isset($organizer['cn'])) { $organizerFullEmail->personal = $organizer['cn']; } if ($action == 'reply2m') { $startStamp = time(); $endStamp = $startStamp + 60 * 24 * 3600; } else { try { $startStamp = $vFb->getAttribute('DTSTART'); } catch (Horde_Icalendar_Exception $e) { $startStamp = time(); } try { $endStamp = $vFb->getAttribute('DTEND'); } catch (Horde_Icalendar_Exception $e) { } if (!$endStamp) { try { $duration = $vFb->getAttribute('DURATION'); $endStamp = $startStamp + $duration; } catch (Horde_Icalendar_Exception $e) { $endStamp = $startStamp + 60 * 24 * 3600; } } } $vfb_reply = $registry->call('calendar/getFreeBusy', array($startStamp, $endStamp)); // Find out who we are and update status. $identity = $injector->getInstance('IMP_Identity'); $email = $identity->getFromAddress(); // Build the reply. $msg_headers = new Horde_Mime_Headers(); $vCal = new Horde_Icalendar(); $vCal->setAttribute('PRODID', '-//The Horde Project//' . $msg_headers->getUserAgent() . '//EN'); $vCal->setAttribute('METHOD', 'REPLY'); $vCal->addComponent($vfb_reply); $message = _("Attached is a reply to a calendar request you sent."); $body = new Horde_Mime_Part(); $body->setType('text/plain'); $body->setCharset('UTF-8'); $body->setContents(Horde_String::wrap($message, 76)); $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setCharset('UTF-8'); $ics->setContents($vCal->exportvCalendar()); $ics->setName('icalendar.ics'); $ics->setContentTypeParameter('METHOD', 'REPLY'); $mime = new Horde_Mime_Part(); $mime->addPart($body); $mime->addPart($ics); // Build the reply headers. $msg_headers->addReceivedHeader(array('dns' => $injector->getInstance('Net_DNS2_Resolver'), 'server' => $conf['server']['name'])); $msg_headers->addMessageIdHeader(); $msg_headers->addHeader('Date', date('r')); $msg_headers->addHeader('From', $email); $msg_headers->addHeader('To', $organizerFullEmail); $identity->setDefault($vars->identity); $replyto = $identity->getValue('replyto_addr'); if (!empty($replyto) && !$email->match($replyto)) { $msg_headers->addHeader('Reply-To', $replyto); } $msg_headers->addHeader('Subject', _("Free/Busy Request Response")); // Send the reply. try { $mime->send($organizerEmail, $msg_headers, $injector->getInstance('IMP_Mail')); $notification->push(_("Reply Sent."), 'horde.success'); $result = true; } catch (Exception $e) { $notification->push(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("Invalid Action selected for this component."), 'horde.warning'); } break; case 'nosup': // vFreebusy request. // vFreebusy request. default: $notification->push(_("This action is not supported."), 'horde.warning'); break; } } return $result; }
/** */ public function davPutObject($collection, $object, $data) { $dav = $GLOBALS['injector']->getInstance('Horde_Dav_Storage'); $internal = $dav->getInternalCollectionId($collection, 'contacts') ?: $collection; $driver = $GLOBALS['injector']->getInstance('Turba_Factory_Driver')->create($internal); if (!$driver->hasPermission(Horde_Perms::EDIT)) { throw new Turba_Exception("Address Book does not exist or no permission to edit"); } $ical = new Horde_Icalendar(); if (!$ical->parsevCalendar($data)) { throw new Turba_Exception(_("There was an error importing the vCard data.")); } foreach ($ical->getComponents() as $content) { if (!$content instanceof Horde_Icalendar_Vcard) { continue; } $contact = $driver->toHash($content); try { try { $existing_id = $dav->getInternalObjectId($object, $internal) ?: preg_replace('/\\.vcf$/', '', $object); } catch (Horde_Dav_Exception $e) { $existing_id = $object; } $existing_contact = $driver->getObject($existing_id); /* Check if our contact is newer then the existing - get the * contact's history. */ $modified = $existing_contact->lastModification(); try { if (!empty($modified) && $content->getAttribute('LAST-MODIFIED')->before($modified)) { /* LAST-MODIFIED timestamp of existing entry is newer: * don't replace it. */ continue; } } catch (Horde_Icalendar_Exception $e) { } foreach ($contact as $attribute => $value) { if ($attribute != '__key') { $existing_contact->setValue($attribute, $value); } } $existing_contact->store(); } catch (Horde_Exception_NotFound $e) { $id = $driver->add($contact); $dav->addObjectMap($id, $object, $internal); } } }