public function testBug12787() { $js = "function foo(a) { return/\\//.test(a); }"; $this->assertEquals('function foo(a){return/\\//.test(a);}', Horde_Text_Filter::filter($js, 'JavascriptMinify')); $js2 = 'var a = 0, b = c / 100 | 0;'; $this->assertNotEquals($js2, Horde_Text_Filter::filter($js2, 'JavascriptMinify')); }
public function testBug12253() { // ISO-8859-2 encoded data. $text = base64_decode('a/ZubmVu'); $filter = Horde_Text_Filter::filter($text, 'text2html', array('charset' => 'iso-8859-2', 'parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL)); $this->assertGreaterThan(0, strlen($filter)); }
public function testBug12152() { $text = 'http://imslp.org/wiki/Symphony_No.5,_D.485_(Schubert,_Franz)'; $old_ini = ini_get('pcre.backtrack_limit'); ini_set('pcre.backtrack_limit', 1000); $this->assertEquals($text, Horde_Text_Filter::filter($text, 'linkurls')); ini_set('pcre.backtrack_limit', $old_ini); }
public function testBug9567() { $text = quoted_printable_decode("pr=E9parer =E0 vendre d’ao=FBt"); $this->assertEquals($text, Horde_Text_Filter::filter('<html><body>' . $text . '</body></html>', 'xss', array('charset' => 'iso-8859-1'))); $this->assertEquals($text, Horde_Text_Filter::filter('<html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"></head><body>' . $text . '</body></html>', 'xss', array('charset' => 'iso-8859-1'))); $text = Horde_String::convertCharset(quoted_printable_decode("pr=E9parer =E0 vendre d’ao=FBt ;"), 'windows-1252', 'UTF-8'); $expected = "préparer à vendre d’août ;"; $this->assertEquals($expected, Horde_Text_Filter::filter('<html><body>' . $text . '</body></html>', 'xss', array('charset' => 'utf-8'))); }
/** * Applies a set of patterns to a block of text. * * @param string $text The text to filter. * @param mixed $filters The list of filters (or a single filter). * @param mixed $params The list of params to use with each filter. * * @return string The transformed text. */ public function filter($text, $filters = array(), $params = array()) { if (!is_array($filters)) { $filters = array($filters); $params = array($params); } $filter_list = array(); $params = array_values($params); foreach (array_values($filters) as $num => $filter) { list($driver, $driv_param) = $this->_getDriver($filter, isset($params[$num]) ? $params[$num] : array()); $filter_list[$driver] = $driv_param; } return Horde_Text_Filter::filter($text, array_keys($filter_list), array_values($filter_list)); }
public function testCombination() { $html = <<<HTML <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body> <p class="MsoNormal"><span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'>Danke für die rasche Erledigung!</span></p> <p class="MsoNormal"><span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'><o:p> </o:p></span></p> <p class="MsoNormal foo"><span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'>Wünsche ein schönes Weihnachtsfest und für 2015 alles Gute!<o:p> </o:p></span></p> <p class="foo MsoNormal"><span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'> </span></p> </body></html> HTML; $expected = <<<HTML <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body> <span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'>Danke für die rasche Erledigung!</span><br> <div class="foo"><span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'>Wünsche ein schönes Weihnachtsfest und für 2015 alles Gute!</span></div> <div class="foo"><span style='font-size:11.0pt;font-family:"Calibri","sans-serif";color:#1F497D'> </span></div> </body></html> HTML; $filtered = Horde_Text_Filter::filter($html, 'Msoffice'); $this->assertEquals($expected, $filtered); }
/** * Create an AS memo from this task * * @param array $memo A memo array. * @param array $options * * @return Horde_ActiveSync_Message_Note */ public function toASNote($memo, $options = array()) { $message = new Horde_ActiveSync_Message_Note(array('protocolversion' => $options['protocolversion'])); $message->subject = $memo['desc']; $bp = $options['bodyprefs']; $body = new Horde_ActiveSync_Message_AirSyncBaseBody(); // When the note is encrypted, we won't have the passphrase so the // body will be a Mnemo_Exception. if ($memo['body'] instanceof Mnemo_Exception) { $memo['body'] = $memo['body']->getMessage(); } if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $body->type = Horde_ActiveSync::BODYPREF_TYPE_HTML; $memo['body'] = Horde_Text_Filter::filter($memo['body'], 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']) && Horde_String::length($memo['body']) > $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']) { $body->data = Horde_String::substr($memo['body'], $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']); $body->truncated = 1; } else { $body->data = $memo['body']; } } else { $body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']) && Horde_String::length($memo['body']) > $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']) { $body->data = Horde_String::substr($memo['body'], 0, $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']); $body->truncated = 1; } else { $body->data = $memo['body']; } } $body->estimateddatasize = Horde_String::length($memo['body']); $message->body = $body; if (!empty($memo['tags'])) { $message->categories = $memo['tags']; } $history = $GLOBALS['injector']->getInstance('Horde_History'); $last = $history->getActionTimeStamp('mnemo:' . $memo['memolist_id'] . ':' . $memo['uid'], 'modify'); if (empty($last)) { $last = $history->getActionTimeStamp('mnemo:' . $memo['memolist_id'] . ':' . $memo['uid'], 'add'); } $message->lastmodified = new Horde_Date($last); return $message; }
/** * @dataProvider markupExamples */ public function testSimplemarkup($markup, $output, $html) { $this->assertEquals($output, Horde_Text_Filter::filter($markup, 'simplemarkup', array('html' => $html))); }
protected function _tovTodo() { $iCal = new Horde_Icalendar(); $iCal->setAttribute('METHOD', $this->_method); $vtodo = Horde_Icalendar::newComponent('vtodo', $iCal); $vtodo->setAttribute('UID', $this->_guid); // For REQUESTS, we MUST have the ORGANIZER and an ATTENDEE. if ($this->_state == self::STATE_ASSIGNERS_COPY || $this->_ownership == self::OWNERSHIP_ASSIGNERS_COPY) { // When sending a REQUEST the lastUser to edit it should be the // ORGANIZER. I can't find any of the other properties that work // consistently. $vtodo->setAttribute('ORGANIZER', 'mailto: ' . $this->_lastUser); $list = new Horde_Mail_Rfc822_List($this->_owner); foreach ($list as $email) { $vtodo->setAttribute('ATTENDEE', $email, array('ROLE' => 'REQ-PARTICIPANT')); } } if ($this->_due) { $vtodo->setAttribute('DUE', $this->_due); } if ($this->_start) { $vtodo->setAttribute('DTSTART', $this->_start); } if ($this->_completed) { $vtodo->setAttribute('COMPLETED', $this->_completed); } if (isset($this->_percentComplete)) { $vtodo->setAttribute('PERCENT-COMPLETE', $this->_percentComplete); } // Summary is stored in the message data. $msg = $this->_options['parent']->getMsgInfo(); if ($msg->subject) { $vtodo->setAttribute('SUMMARY', $msg->subject); } // Figure out the body. if ($this->_bodyPlain) { $vtodo->setAttribute('DESCRIPTION', $this->_bodyPlain); } elseif ($this->_bodyHtml) { $vtodo->setAttribute('DESCRIPTION', Horde_Text_Filter::filter($this->_bodyHtml, 'html2text')); } $iCal->addComponent($vtodo); return array('type' => 'text', 'subtype' => 'calendar', 'name' => $msg->subject ? $msg->subject . '.vtodo' : 'Untitled.vtodo', 'stream' => $iCal->exportvCalendar()); }
public static function html2text($msg) { return Horde_Text_Filter::filter($msg, 'Html2text', array('nestingLimit' => 1000)); }
/** * Parse a MIME message and create a new ticket. * * @param string $text This is the full text of the MIME message. * @param array $info An array of information for the new ticket. * This should include: * - 'queue' => queue id * - 'type' => type id * - 'state' => state id * - 'priority' => priority id * - 'ticket' => ticket id (prevents creation * of new tickets) * @param string $auth_user This will be the Horde user that creates the * ticket. If null, we will try to deduce from * the message's From: header. We do NOT default * to $GLOBALS['registry']->getAuth(). * * @return Whups_Ticket Ticket. */ public static function processMail($text, array $info, $auth_user = null) { global $conf; $message = Horde_Mime_Part::parseMessage($text); if (preg_match("/^(.*?)\r?\n\r?\n/s", $text, $matches)) { $hdrText = $matches[1]; } else { $hdrText = $text; } $headers = Horde_Mime_Headers::parseHeaders($hdrText); // If this message was generated by Whups, don't process it. if ($headers->getValue('X-Whups-Generated')) { return true; } // Try to avoid bounces, auto-replies, and mailing list responses. $from = $headers->getValue('from'); if (strpos($headers->getValue('Content-Type'), 'multipart/report') !== false || stripos($from, 'mailer-daemon@') !== false || stripos($from, 'postmaster@') !== false || !is_null($headers->getValue('X-Failed-Recipients')) || !is_null($headers->getValue('X-Autoreply-Domain')) || $headers->getValue('Auto-Submitted') == 'auto-replied' || $headers->getValue('Precedence') == 'auto_reply' || $headers->getValue('X-Precedence') == 'auto_reply' || $headers->getValue('X-Auto-Response-Suppress') == 'All' || $headers->getValue('X-List-Administrivia') == 'Yes') { return true; } // Use the message subject as the ticket summary. $info['summary'] = trim($headers->getValue('subject')); if (empty($info['summary'])) { $info['summary'] = _("[No Subject]"); } // Format the message into a comment. $comment = _("Received message:") . "\n\n"; if (!empty($GLOBALS['conf']['mail']['include_headers'])) { foreach ($headers->toArray(array('nowrap' => true)) as $name => $vals) { if (!in_array(strtolower($name), array('subject', 'from', 'to', 'cc', 'date'))) { if (is_array($vals)) { foreach ($vals as $val) { $comment .= $name . ': ' . $val . "\n"; } } else { $comment .= $name . ': ' . $vals . "\n"; } } } $comment .= "\n"; } // Look for the body part. $body_id = $message->findBody(); if ($body_id) { $part = $message->getPart($body_id); $content = Horde_String::convertCharset($part->getContents(), $part->getCharset(), 'UTF-8'); switch ($part->getType()) { case 'text/plain': $comment .= $content; break; case 'text/html': $comment .= Horde_Text_Filter::filter($content, array('Html2text'), array(array('width' => 0))); break; default: $comment .= _("[ Could not render body of message. ]"); break; } } else { $comment .= _("[ Could not render body of message. ]"); } $info['comment'] = $comment . "\n"; // Try to determine the Horde user for creating the ticket. if (empty($auth_user)) { $tmp = new Horde_Mail_Rfc822_Address($from); $auth_user = self::_findAuthUser($tmp->bare_address); } $author = $auth_user; if (empty($auth_user) && !empty($info['default_auth'])) { $auth_user = $info['default_auth']; if (!empty($from)) { $info['user_email'] = $from; } } if (empty($auth_user) && !empty($conf['mail']['username'])) { $auth_user = $conf['mail']['username']; if (!empty($from)) { $info['user_email'] = $from; } } // Authenticate as the correct Horde user. if (!empty($auth_user) && $auth_user != $GLOBALS['registry']->getAuth()) { $GLOBALS['registry']->setAuth($auth_user, array()); } // Attach message. $attachments = array(); if (!empty($GLOBALS['conf']['mail']['attach_message'])) { $tmp_name = Horde::getTempFile('whups'); $fp = @fopen($tmp_name, 'wb'); if (!$fp) { throw new Whups_Exception(sprintf('Cannot open file %s for writing.', $tmp_name)); } fwrite($fp, $text); fclose($fp); $attachments[] = array('name' => _("Original Message") . '.eml', 'tmp_name' => $tmp_name); } // Extract attachments. $dl_list = array_slice(array_keys($message->contentTypeMap()), 1); foreach ($dl_list as $key) { $part = $message->getPart($key); if ($key == $body_id && $part->getType() == 'text/plain' || $part->getType() == 'multipart/alternative' || $part->getType() == 'multipart/mixed') { continue; } $tmp_name = Horde::getTempFile('whups'); $fp = @fopen($tmp_name, 'wb'); if (!$fp) { throw new Whups_Exception(sprintf('Cannot open file %s for writing.', $tmp_name)); } fwrite($fp, $part->getContents()); fclose($fp); $part_name = $part->getName(true); if (!$part_name) { $ptype = $part->getPrimaryType(); switch ($ptype) { case 'multipart': case 'application': $part_name = sprintf(_("%s part"), ucfirst($part->getSubType())); break; default: $part_name = sprintf(_("%s part"), ucfirst($ptype)); break; } if ($ext = Horde_Mime_Magic::mimeToExt($part->getType())) { $part_name .= '.' . $ext; } } $attachments[] = array('name' => $part_name, 'tmp_name' => $tmp_name); } // See if we can match this message to an existing ticket. if ($ticket = self::_findTicket($info)) { $ticket->change('comment', $info['comment']); $ticket->change('comment-email', $from); if ($attachments) { $ticket->change('attachments', $attachments); } $ticket->commit($author); } elseif (!empty($info['ticket'])) { // Didn't match an existing ticket though a ticket number had been // specified. throw new Whups_Exception(sprintf(_("Could not find ticket \"%s\"."), $info['ticket'])); } else { if (!empty($info['guess-queue'])) { // Try to guess the queue name for the new ticket from the // message subject. $queues = $GLOBALS['whups_driver']->getQueues(); foreach ($queues as $queueId => $queueName) { if (preg_match('/\\b' . preg_quote($queueName, '/') . '\\b/i', $info['summary'])) { $info['queue'] = $queueId; break; } } } $info['attachments'] = $attachments; // Create a new ticket. $ticket = Whups_Ticket::newTicket($info, $author); } }
public function testHtml2TextSpacing() { $html = '<span><span>Normal</span> <strong>Strong</strong> Normal</span> <em>Italics</em> Normal <u>Underline</u> Normal <strike>Strike</strike> Normal'; $filter = Horde_Text_Filter::filter($html, 'Html2text'); $this->assertEquals('Normal STRONG Normal /Italics/ Normal _Underline_ Normal Strike Normal', $filter); }
/** * Returns the main text body of the message suitable for sending over * EAS response. * * @param array $options An options array containgin: * - bodyprefs: (array) Bodypref settings * DEFAULT: none (No bodyprefs used). * - 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 we are supporting. * DEFAULT 2.5 * * @return array An array of one or both of 'plain' and 'html' content. * * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound */ public function getMessageBodyData(array $options = array()) { $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; // Look for the parts we need. We try to detect and fetch only the parts // we need, while ensuring we have something to return. So, e.g., if we // don't have BODYPREF_TYPE_HTML, we only request plain text, but if we // can't find plain text but we have a html body, fetch that anyway. $text_id = $this->_message->findBody('plain'); $html_id = $this->_message->findBody('html'); // Deduce which part(s) we need to request. $want_html_text = $version >= Horde_ActiveSync::VERSION_TWELVE && (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME])); $want_plain_text = $version == Horde_ActiveSync::VERSION_TWOFIVE || empty($options['bodyprefs']) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_RTF]) || !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]) || $want_html_text && empty($html_id); $want_html_as_plain = false; if (!empty($text_id) && $want_plain_text) { $text_body_part = $this->_message->getPart($text_id); $charset = $text_body_part->getCharset(); } elseif ($want_plain_text && !empty($html_id) && empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME])) { $want_html_text = true; $want_html_as_plain = true; } if (!empty($html_id) && $want_html_text) { $html_body_part = $this->_message->getPart($html_id); $html_charset = $html_body_part->getCharset(); } // Sanity check the truncation stuff if (empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]) && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]) && $want_plain_text && $want_html_text) { // We only have HTML truncation data, requested HTML body but only // have plaintext. $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN] = $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]; } $query = new Horde_Imap_Client_Fetch_Query(); $query_opts = array('decode' => true, 'peek' => true); // Get body information if ($version >= Horde_ActiveSync::VERSION_TWELVE) { if (!empty($html_id)) { $query->bodyPartSize($html_id); $query->bodyPart($html_id, $query_opts); } if (!empty($text_id)) { $query->bodyPart($text_id, $query_opts); $query->bodyPartSize($text_id); } } else { // EAS 2.5 Plaintext body $query->bodyPart($text_id, $query_opts); $query->bodyPartSize($text_id); } try { $fetch_ret = $this->_imap->fetch($this->_mbox, $query, array('ids' => new Horde_Imap_Client_Ids(array($this->_uid)))); } catch (Horde_Imap_Client_Exception $e) { throw new Horde_ActiveSync_Exception($e); } if (!($data = $fetch_ret->first())) { throw new Horde_Exception_NotFound(sprintf('Could not load message %s from server.', $this->_uid)); } $return = array(); if (!empty($text_id) && $want_plain_text) { $text = $data->getBodyPart($text_id); if (!$data->getBodyPartDecode($text_id)) { $text_body_part->setContents($text); $text = $text_body_part->getContents(); } $text_size = !is_null($data->getBodyPartSize($text_id)) ? $data->getBodyPartSize($text_id) : Horde_String::length($text); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $text = Horde_String::substr($text, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $charset); } $truncated = $text_size > Horde_String::length($text); if ($version >= Horde_ActiveSync::VERSION_TWELVE && $truncated && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['allornone'])) { $text = ''; } $return['plain'] = array('charset' => $charset, 'body' => $text, 'truncated' => $truncated, 'size' => $text_size); } if (!empty($html_id) && $want_html_text) { $html = $data->getBodyPart($html_id); if (!$data->getBodyPartDecode($html_id)) { $html_body_part->setContents($html); $html = $html_body_part->getContents(); } // Size of the original HTML part. $html_size = !is_null($data->getBodyPartSize($html_id)) ? $data->getBodyPartSize($html_id) : Horde_String::length($html); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'])) { $html = Horde_String::substr($html, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize'], $html_charset); } elseif ($want_html_as_plain) { $html = Horde_Text_Filter::filter($html, 'Html2text', array('charset' => $html_charset)); // Get the new size, since it probably changed. $html_size = Horde_String::length($html); if (!empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { // EAS >= 12.0 truncation $html = Horde_String::substr($html, 0, $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'], $html_charset); } } // Was the part truncated? $truncated = $html_size > Horde_String::length($html); if ($want_html_as_plain) { $return['plain'] = array('charset' => $html_charset, 'body' => $html, 'truncated' => $truncated, 'size' => $html_size); } if ($version >= Horde_ActiveSync::VERSION_TWELVE && !($truncated && !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_HTML]['allornone']))) { $return['html'] = array('charset' => $html_charset, 'body' => $html, 'estimated_size' => $html_size, 'truncated' => $truncated); } } return $return; }
public static function html2text($msg) { Horde_Text_Filter::filter($msg, 'Html2text'); }
/** * 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) { global $injector; 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.'); } // Update the vCal so the response will be reflected when imported. $ident = $injector->getInstance('Horde_Core_Factory_Identity')->create($this->_user); $cn = $ident->getValue('fullname'); $email = $ident->getValue('from_addr'); switch ($response['response']) { case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_ACCEPTED: $itip_response = 'ACCEPTED'; break; case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_TENTATIVE: $itip_response = 'TENTATIVE'; break; case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_DECLINED: $itip_response = 'DECLINED'; } $vEvent->updateAttendee($email, $itip_response); // 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); } if (!empty($response['sendresponse'])) { if ($response['sendresponse'] !== true) { $comment = $response['sendresponse']->data; if ($response['sendresponse']->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $comment = Horde_Text_Filter::filter($comment, 'Html2text', array('charset' => 'UTF-8', 'nestingLimit' => 1000)); } } else { $comment = ''; } // 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 = $injector->getInstance('Horde_Core_Factory_Identity')->create($event->creator); if (!$ident->getValue('from_addr')) { throw new Horde_ActiveSync_Exception(_("You do not have an email address configured in your Personal Information Preferences.")); } $resource = new Horde_Itip_Resource_Identity($ident, $vEvent->getAttribute('ATTENDEE'), (string) $ident->getFromAddress()); switch ($response['response']) { case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_ACCEPTED: $type = new Horde_Itip_Response_Type_Accept($resource, $comment); break; case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_DECLINED: $type = new Horde_Itip_Response_Type_Decline($resource, $comment); break; case Horde_ActiveSync_Request_MeetingResponse::RESPONSE_TENTATIVE: $type = new Horde_Itip_Response_Type_Tentative($resource, $comment); break; } try { // Send the reply. Horde_Itip::factory($vEvent, $resource)->sendMultiPartResponse($type, new Horde_Core_Itip_Response_Options_Horde('UTF-8', array()), $injector->getInstance('Horde_Mail')); $this->_logger->info('Reply sent.'); } catch (Horde_Itip_Exception $e) { $this->_logger->err(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } // 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. try { $this->_imap->deleteMessages(array($response['requestid']), $response['folderid']); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } return $uid; }
/** * Executes any code necessary before applying the filter patterns. * * @param mixed $text The text before the filtering. Either a string or * a Horde_Text_Flowed object (since 1.1.0). * * @return string The modified text. */ public function preProcess($text) { if ($text instanceof Horde_Text_Flowed) { $text->setMaxLength(0); $lines = $text->toFixedArray(); $level = 0; $out = $txt = ''; foreach ($lines as $key => $val) { $line = ltrim($val['text'], '>'); if (!isset($lines[$key + 1])) { $out .= $this->preProcess(ltrim($txt) . $line); while (--$level > 0) { $out .= '</blockquote>'; } } elseif ($val['level'] > $level) { $out .= $this->preProcess(ltrim($txt)); do { $out .= $this->_params['flowed']; } while (++$level != $val['level']); $txt = $line; } elseif ($val['level'] < $level) { $out .= $this->preProcess(ltrim($txt)); do { $out .= '</blockquote>'; } while (--$level != $val['level']); $txt = $line; } else { $txt .= "\n" . $line; } } return $out; } if (!strlen($text)) { return ''; } /* Abort out on simple cases. */ if ($this->_params['parselevel'] == self::PASSTHRU) { return $text; } if ($this->_params['parselevel'] == self::NOHTML_NOBREAK) { return @htmlspecialchars($text, ENT_COMPAT, $this->_params['charset']); } if ($this->_params['parselevel'] < self::NOHTML) { $filters = array(); if ($this->_params['linkurls']) { reset($this->_params['linkurls']); $this->_params['linkurls'][key($this->_params['linkurls'])]['encode'] = true; $filters = $this->_params['linkurls']; } else { $filters['linkurls'] = array('encode' => true); } if ($this->_params['parselevel'] < self::MICRO_LINKURL) { if ($this->_params['emails']) { reset($this->_params['emails']); $this->_params['emails'][key($this->_params['emails'])]['encode'] = true; $filters += $this->_params['emails']; } else { $filters['emails'] = array('encode' => true); } } $text = Horde_Text_Filter::filter($text, array_keys($filters), array_values($filters)); } /* For level MICRO or NOHTML, start with htmlspecialchars(). */ $text2 = @htmlspecialchars($text, ENT_COMPAT, $this->_params['charset']); /* Bad charset input in may result in an empty string. Or the charset * may not be supported. Convert to UTF-8 for htmlspecialchars() and * then convert back. If we STILL don't have any output, the input * charset is probably incorrect. Try the popular Western charsets as * a last resort. */ if (!strlen($text2)) { $text2 = Horde_String::convertCharset(@htmlspecialchars(Horde_String::convertCharset($text, $this->_params['charset'], 'UTF-8'), ENT_COMPAT, 'UTF-8'), 'UTF-8', $this->_params['charset']); if (!strlen($text2)) { foreach (array('windows-1252', 'utf-8') as $val) { $text2 = Horde_String::convertCharset(@htmlspecialchars($text, ENT_COMPAT, $val), $val, $this->_params['charset']); if (strlen($text2)) { break; } } } } $text = $text2; /* Do in-lining of http://xxx.xxx to link, xxx@xxx.xxx to email. */ if ($this->_params['parselevel'] < self::NOHTML) { $text = Horde_Text_Filter_Linkurls::decode($text); if ($this->_params['parselevel'] < self::MICRO_LINKURL) { $text = Horde_Text_Filter_Emails::decode($text); } if ($this->_params['space2html']) { $params = reset($this->_params['space2html']); $driver = key($this->_params['space2html']); } else { $driver = 'space2html'; $params = array(); } $text = Horde_Text_Filter::filter($text, $driver, $params); } /* Do the newline ---> <br /> substitution. Everybody gets this; if * you don't want even that, just use htmlspecialchars(). */ return nl2br($text); }
/** * Filter text. * * @param string $text TODO * @param mixed $driver TODO * @param array $params TODO * * @return string The filtered text. */ protected function _textFilter($text, $driver, array $params = array()) { return ($text_filter = $this->getConfigParam('text_filter')) ? call_user_func($text_filter, $text, $driver, $params) : Horde_Text_Filter::filter($text, $driver, $params); }
/** * @dataProvider space2htmlProvider */ public function testSpace2html($spaces, $results, $results_encode_all) { $this->assertEquals($results, Horde_Text_Filter::filter($spaces, 'space2html', array('encode_all' => false))); $this->assertEquals($results_encode_all, Horde_Text_Filter::filter($spaces, 'space2html', array('encode_all' => true))); }
/** * @dataProvider environmentProvider */ public function testEnvironment($input, $expected) { $this->assertEquals($expected, Horde_Text_Filter::filter($input, 'environment')); }
public function testHtml2TextNestingLimit() { $html = '<span>Foo</span><div><div><div><div>Bar</div></div></div></div>'; $filter = Horde_Text_Filter::filter($html, 'Html2text', array('nestingLimit' => 3)); $this->assertEquals('Foo', $filter); $filter = Horde_Text_Filter::filter($html, 'Html2text', array('nestingLimit' => 4)); $this->assertEquals('FooBar', $filter); }
/** * Import a memo represented in the specified contentType. * * @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 * @param string $notepad (optional) The notepad to save the memo on. * * @return string The new UID, or false on failure. * @throws Mnemo_Exception * @throws Horde_Exception_PermissionDenied */ public function import($content, $contentType, $notepad = null) { global $prefs; /* Make sure we have a valid notepad and permissions to edit * it. */ if (empty($notepad)) { $notepad = Mnemo::getDefaultNotepad(Horde_Perms::EDIT); } if (!array_key_exists($notepad, Mnemo::listNotepads(false, Horde_Perms::EDIT))) { throw new Horde_Exception_PermissionDenied(); } /* Create a Mnemo_Driver instance. */ $storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create($notepad); switch ($contentType) { case 'text/plain': $noteId = $storage->add($storage->getMemoDescription($content), $content); 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: $ids = array(); foreach ($components as $content) { if ($content instanceof Horde_Icalendar_Vnote) { $note = $storage->fromiCalendar($content); $noteId = $storage->add($note['desc'], $note['body'], !empty($note['tags']) ? $note['tags'] : ''); $ids[] = $noteId; } } return $ids; } } $note = $storage->fromiCalendar($content); $noteId = $storage->add($note['desc'], $note['body'], !empty($note['tags']) ? $note['tags'] : ''); break; case 'activesync': // We only support plaintext if ($content->body->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $body = Horde_Text_Filter::filter($content->body->data, 'Html2text'); } else { $body = $content->body->data; } $noteId = $storage->add($content->subject, $body, $content->categories); break; default: throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } $note = $storage->get($noteId); return $note['uid']; }
/** * @dataProvider emailsProvider */ public function testEmails($input, $expected) { $this->assertEquals($expected, Horde_Text_Filter::filter($input, 'emails', array('class' => 'pagelink'))); }
/** * Sets the HTML message body text. * * @param string $body The message content. * @param string $charset The character set of the message. * @param boolean $alternative If true, a multipart/alternative message is * created and the text/plain part is * generated automatically. If false, a * text/html message is generated. */ public function setHtmlBody($body, $charset = null, $alternative = true) { if (!$charset) { $charset = $this->_charset; } $this->_htmlBody = new Horde_Mime_Part(); $this->_htmlBody->setType('text/html'); $this->_htmlBody->setCharset($charset); $this->_htmlBody->setContents($body); if ($alternative) { $this->setBody(Horde_Text_Filter::filter($body, 'Html2text', array('charset' => $charset, 'wrap' => false)), $charset); } $this->_base = null; }
/** * Create a nag Task object from an activesync message * * @param Horde_ActiveSync_Message_Task $message The task object */ public function fromASTask(Horde_ActiveSync_Message_Task $message) { /* Owner is always current user. */ $this->owner = $GLOBALS['registry']->getAuth(); /* Must set _tags so we don't lazy load tags from the backend in the * case that this is an edit. For edits, all current tags will be passed * from the client. */ $this->_tags = array(); /* Notes and Title */ if ($message->getProtocolVersion() >= Horde_ActiveSync::VERSION_TWELVE) { if ($message->airsyncbasebody->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $this->desc = Horde_Text_Filter::filter($message->airsyncbasebody->data, 'Html2text'); } else { $this->desc = $message->airsyncbasebody->data; } } else { $this->desc = $message->body; } $this->name = $message->subject; $tz = date_default_timezone_get(); /* Completion */ if ($this->completed = $message->complete) { if ($message->datecompleted) { $message->datecompleted->setTimezone($tz); $this->completed_date = $message->datecompleted->timestamp(); } else { $this->completed_date = null; } } /* Due Date */ if ($due = $message->utcduedate) { $due->setTimezone($tz); $this->due = $due->timestamp(); } elseif ($due = $message->duedate) { // "Local" date, sent as a UTC datetime string, // but must be interpreted as a local time. Since // we have no timezone information we have to assume it's the // same as $tz. $due = new Horde_Date(array('year' => $due->year, 'month' => $due->month, 'mday' => $due->mday, 'hour' => $due->hour, 'min' => $due->min), $tz); $this->due = $due->timestamp(); } /* Start Date */ if ($start = $message->utcstartdate) { $start->setTimezone($tz); $this->start = $start->timestamp(); } elseif ($start = $message->startdate) { // See note above regarding utc vs local times. $start = new Horde_Date(array('year' => $start->year, 'month' => $start->month, 'mday' => $start->mday, 'hour' => $start->hour, 'min' => $start->min), $tz); $this->start = $start->timestamp(); } /* Priority */ switch ($message->getImportance()) { case Horde_ActiveSync_Message_Task::IMPORTANCE_LOW: $this->priority = 5; break; case Horde_ActiveSync_Message_Task::IMPORTANCE_NORMAL: $this->priority = 3; break; case Horde_ActiveSync_Message_Task::IMPORTANCE_HIGH: $this->priority = 1; break; default: $this->priority = 3; } if (($alarm = $message->getReminder()) && $this->due) { $alarm->setTimezone($tz); $this->alarm = ($this->due - $alarm->timestamp()) / 60; } if ($rrule = $message->getRecurrence()) { $this->recurrence = $rrule; } $this->tasklist = $GLOBALS['prefs']->getValue('default_tasklist'); /* Categories */ if (is_array($message->categories) && count($message->categories)) { $this->tags = implode(',', $message->categories); } }
/** * Create a nag Task object from an activesync message * * @param Horde_ActiveSync_Message_Task $message The task object */ public function fromASTask(Horde_ActiveSync_Message_Task $message) { /* Owner is always current user. */ $this->owner = $GLOBALS['registry']->getAuth(); /* Notes and Title */ if ($message->getProtocolVersion() >= Horde_ActiveSync::VERSION_TWELVE) { if ($message->airsyncbasebody->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $this->desc = Horde_Text_Filter::filter($message->airsyncbasebody->data, 'Html2text'); } else { $this->desc = $message->airsyncbasebody->data; } } else { $this->desc = $message->body; } $this->name = $message->subject; $tz = date_default_timezone_get(); /* Completion */ if ($this->completed = $message->complete) { if ($message->datecompleted) { $message->datecompleted->setTimezone($tz); $this->completed_date = $message->datecompleted->timestamp(); } else { $this->completed_date = null; } } /* Due Date */ if (($due = $message->utcduedate) || ($due = $message->duedate)) { $due->setTimezone($tz); $this->due = $due->timestamp(); } /* Start Date */ if (($start = $message->utcstartdate) || ($start = $message->startdate)) { $start->setTimezone($tz); $this->start = $start->timestamp(); } /* Priority */ switch ($message->getImportance()) { case Horde_ActiveSync_Message_Task::IMPORTANCE_LOW: $this->priority = 5; break; case Horde_ActiveSync_Message_Task::IMPORTANCE_NORMAL: $this->priority = 3; break; case Horde_ActiveSync_Message_Task::IMPORTANCE_HIGH: $this->priority = 1; break; default: $this->priority = 3; } if (($alarm = $message->getReminder()) && $this->due) { $alarm->setTimezone($tz); $this->alarm = ($this->due - $alarm->timestamp()) / 60; } if ($rrule = $message->getRecurrence()) { $this->recurrence = $rrule; } $this->tasklist = $GLOBALS['prefs']->getValue('default_tasklist'); /* Categories */ if (is_array($message->categories) && count($message->categories)) { $this->tags = $message->categories; } }
/** * 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': // We only support plaintext if ($content->body->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) { $body = Horde_Text_Filter::filter($content->body->data, 'Html2text'); } else { $body = $content->body->data; } $storage->modify($memo['memo_id'], Horde_String::substr($content->subject, 0, 255), $body, $content->categories); break; default: throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } }
/** * Build the data needed for the BodyPart part. * * @param Horde_Imap_Client_Data_Fetch $data The FETCH results. * @param Horde_Mime_Part $mime The plaintext MIME part. * @param boolean $to_html If true, $id is assumed to be a text/plain * part and is converted to html. * * @return array The BodyPart data. * - charset: (string) The charset of the text. * - body: (string) The body text. * - truncated: (boolean) True if text was truncated. * - size: (integer) The original part size, in bytes. */ protected function _getBodyPart(Horde_Imap_Client_Data_Fetch $data, Horde_Mime_Part $mime, $to_html) { $id = $mime->getMimeId(); $text = $data->getBodyPart($id); if (!$data->getBodyPartDecode($id)) { $mime->setContents($text); $text = $mime->getContents(); } if ($to_html) { $text = Horde_Text_Filter::filter($text, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO, 'charset' => $mime->getCharset())); $size = strlen($text); } else { $size = !is_null($data->getBodyPartSize($id)) ? $data->getBodyPartSize($id) : strlen($text); } if (!empty($this->_options['bodypartprefs']['truncationsize'])) { $text = Horde_String::substr($text, 0, $this->_options['bodypartprefs']['truncationsize'], $mime->getCharset()); } return array('charset' => $mime->getCharset(), 'body' => $text, 'truncated' => $size > strlen($text), 'size' => $size); }
public function testDefaultReplacement() { $line = 'foo baz'; $res = Horde_Text_Filter::filter($line, 'words', array('replacement' => null, 'words' => $this->words)); $this->assertEquals('f** baz', $res); }
/** * Export this event as a MS ActiveSync Message * * @param array $options Options: * - protocolversion: (float) The EAS version to support * DEFAULT: 2.5 * - bodyprefs: (array) A BODYPREFERENCE array. * DEFAULT: none (No body prefs enforced). * - truncation: (integer) Truncate event body to this length * DEFAULT: none (No truncation). * * @return Horde_ActiveSync_Message_Appointment */ public function toASAppointment(array $options = array()) { global $prefs, $registry; $message = new Horde_ActiveSync_Message_Appointment(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); if (!$this->isPrivate()) { // Handle body/truncation if (!empty($options['bodyprefs'])) { if (Horde_String::length($this->description) > 0) { $bp = $options['bodyprefs']; $note = new Horde_ActiveSync_Message_AirSyncBaseBody(); // No HTML supported. Always use plaintext. $note->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']; } elseif (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']; $this->description = Horde_Text_Filter::filter($this->description, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); } else { $truncation = false; } if ($truncation && Horde_String::length($this->description) > $truncation) { $note->data = Horde_String::substr($this->desciption, 0, $truncation); $note->truncated = 1; } else { $note->data = $this->description; } $note->estimateddatasize = Horde_String::length($this->description); $message->airsyncbasebody = $note; } } else { $message->setBody($this->description); } $message->setLocation($this->location); } $message->setSubject($this->getTitle()); $message->setDatetime(array('start' => $this->start, 'end' => $this->end, 'allday' => $this->isAllDay())); $message->setTimezone($this->start); // Organizer if (count($this->attendees)) { if ($this->creator == $registry->getAuth()) { $as_ident = $prefs->getValue('activesync_identity') == 'horde' ? $prefs->getValue('default_identity') : $prefs->getValue('activesync_identity'); $name = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('fullname', $as_ident); $email = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('from_addr', $as_ident); } else { $name = Kronolith::getUserName($this->creator); $email = Kronolith::getUserEmail($this->creator); } $message->setOrganizer(array('name' => $name, 'email' => $email)); } // Privacy $message->setSensitivity($this->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); // Busy Status switch ($this->status) { case Kronolith::STATUS_CANCELLED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; break; case Kronolith::STATUS_CONFIRMED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY; break; case Kronolith::STATUS_TENTATIVE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE; case Kronolith::STATUS_FREE: case Kronolith::STATUS_NONE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; } $message->setBusyStatus($status); // DTStamp $message->setDTStamp($_SERVER['REQUEST_TIME']); // Recurrence if ($this->recurs()) { $message->setRecurrence($this->recurrence, $GLOBALS['prefs']->getValue('week_start_monday')); /* Exceptions are tricky. Exceptions, even those that represent * deleted instances of a recurring event, must be added. To do this * we query the storage for all the events that represent exceptions * (those with the baseid == $this->uid) and then remove the * exceptionoriginaldate from the list of exceptions we know about. * Any dates left in this list when we are done, must represent * deleted instances of this recurring event.*/ if (!empty($this->recurrence) && ($exceptions = $this->recurrence->getExceptions())) { $results = $this->boundExceptions(); foreach ($results as $exception) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); $e->setDateTime(array('start' => $exception->start, 'end' => $exception->end, 'allday' => $exception->isAllDay())); // The start time of the *original* recurring event $e->setExceptionStartTime($exception->exceptionoriginaldate); $originaldate = $exception->exceptionoriginaldate->format('Ymd'); $key = array_search($originaldate, $exceptions); if ($key !== false) { unset($exceptions[$key]); } // Remaining properties that could be different $e->setSubject($exception->getTitle()); if (!$exception->isPrivate()) { $e->setLocation($exception->location); $e->setBody($exception->description); } $e->setSensitivity($exception->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); $e->setReminder($exception->alarm); $e->setDTStamp($_SERVER['REQUEST_TIME']); if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { switch ($exception->status) { case Kronolith::STATUS_TENTATIVE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // Tags/Categories if (!$exception->isPrivate()) { foreach ($exception->tags as $tag) { $e->addCategory($tag); } } $message->addexception($e); } // Any dates left in $exceptions must be deleted exceptions foreach ($exceptions as $deleted) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); // Kronolith stores the date only, but some AS clients need // the datetime. list($year, $month, $mday) = sscanf($deleted, '%04d%02d%02d'); $st = clone $this->start; $st->year = $year; $st->month = $month; $st->mday = $mday; $e->setExceptionStartTime($st); $e->deleted = true; $message->addException($e); } } } // Attendees if (!$this->isPrivate() && count($this->attendees)) { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING); foreach ($this->attendees as $email => $properties) { $attendee = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $adr_obj = new Horde_Mail_Rfc822_Address($email); $attendee->name = $adr_obj->label; $attendee->email = $adr_obj->bare_address; // AS only has required or optional, and only EAS Version > 2.5 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $attendee->type = $properties['attendance'] !== Kronolith::PART_REQUIRED ? Horde_ActiveSync_Message_Attendee::TYPE_OPTIONAL : Horde_ActiveSync_Message_Attendee::TYPE_REQUIRED; switch ($properties['response']) { case Kronolith::RESPONSE_NONE: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_NORESPONSE; break; case Kronolith::RESPONSE_ACCEPTED: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_ACCEPT; break; case Kronolith::RESPONSE_DECLINED: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_DECLINE; break; case Kronolith::RESPONSE_TENTATIVE: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_TENTATIVE; break; default: $attendee->status = Horde_ActiveSync_Message_Attendee::STATUS_UNKNOWN; } } $message->addAttendee($attendee); } } else { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_NOT_MEETING); } // Resources if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $r = $this->getResources(); foreach ($r as $id => $data) { $resource = Kronolith::getDriver('Resource')->getResource($id); // EAS *REQUIRES* an email field for Resources. If it is missing // a number of clients will fail, losing push. if ($resource->get('email')) { $attendee = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $attendee->email = $resource->get('email'); $attendee->type = Horde_ActiveSync_Message_Attendee::TYPE_RESOURCE; $attendee->name = $data['name']; $attendee->status = $data['response']; $message->addAttendee($attendee); } } } // Reminder if ($this->alarm) { $message->setReminder($this->alarm); } // Categories (tags) if (!$this->isPrivate()) { foreach ($this->tags as $tag) { $message->addCategory($tag); } } // EAS 14 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { // We don't track the actual responses we sent to other's invitations. // Set this based on the status flag. switch ($this->status) { case Kronolith::STATUS_TENTATIVE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // 14.1 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_FOURTEENONE) { $message->onlinemeetingexternallink = $this->url; } return $message; }
/** * Export this event as a MS ActiveSync Message * * @param array $options Options: * - protocolversion: (float) The EAS version to support * DEFAULT: 2.5 * - bodyprefs: (array) A BODYPREFERENCE array. * DEFAULT: none (No body prefs enforced). * - truncation: (integer) Truncate event body to this length * DEFAULT: none (No truncation). * * @return Horde_ActiveSync_Message_Appointment */ public function toASAppointment(array $options = array()) { global $prefs, $registry; // @todo This should be a required option. if (empty($options['protocolversion'])) { $options['protocolversion'] = 2.5; } $message = new Horde_ActiveSync_Message_Appointment(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); if (!$this->isPrivate()) { // Handle body/truncation if (!empty($options['bodyprefs'])) { if (Horde_String::length($this->description) > 0) { $bp = $options['bodyprefs']; $note = new Horde_ActiveSync_Message_AirSyncBaseBody(); // No HTML supported. Always use plaintext. $note->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; if (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize'])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_PLAIN]['truncationsize']; } elseif (isset($bp[Horde_ActiveSync::BODYPREF_TYPE_HTML])) { $truncation = $bp[Horde_ActiveSync::BODYPREF_TYPE_HTML]['truncationsize']; $this->description = Horde_Text_Filter::filter($this->description, 'Text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO)); } else { $truncation = false; } if ($truncation && Horde_String::length($this->description) > $truncation) { $note->data = Horde_String::substr($this->description, 0, $truncation); $note->truncated = 1; } else { $note->data = $this->description; } $note->estimateddatasize = Horde_String::length($this->description); $message->airsyncbasebody = $note; } } else { $message->setBody($this->description); } if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_SIXTEEN && !empty($this->location)) { $message->location = new Horde_ActiveSync_Message_AirSyncBaseLocation(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); // @todo - worth it to try to get full city/country etc... // from geotagging service if available?? $message->location->displayname = $this->location; } else { $message->setLocation($this->location); } } $message->setSubject($this->getTitle()); $message->alldayevent = $this->isAllDay(); $st = clone $this->start; $et = clone $this->end; if ($this->isAllDay()) { // EAS requires all day to be from 12:00 to 12:00. if ($this->start->hour != 0 || $this->start->min != 0 || $this->start->sec != 0) { $st->hour = 0; $st->min = 0; $st->sec = 0; } // For end it's a bit trickier. If it's 11:59pm, bump it up to 12:00 // am of the next day. Otherwise, if it's not 12:00am, make it 12:00 // am of the same day. This *shouldn't* happen, but protect against // issues with EAS just in case. if ($this->end->hour != 0 || $this->end->min != 0 || $this->end->sec != 0) { if ($this->end->hour == 23 && $this->end->min == 59) { $et->mday++; } $et->hour = 0; $et->min = 0; $et->sec = 0; } } $message->starttime = $st; $message->endtime = $et; $message->setTimezone($this->start); // Organizer $attendees = $this->attendees; $skipOrganizer = null; if ($this->organizer) { $message->setOrganizer(array('email' => $this->organizer)); } elseif (count($attendees)) { if ($this->creator == $registry->getAuth()) { $as_ident = $prefs->getValue('activesync_identity') == 'horde' ? $prefs->getValue('default_identity') : $prefs->getValue('activesync_identity'); $name = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('fullname', $as_ident); $email = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($this->creator)->getValue('from_addr', $as_ident); } else { $name = Kronolith::getUserName($this->creator); $email = Kronolith::getUserEmail($this->creator); } $message->setOrganizer(array('name' => $name, 'email' => $email)); $skipOrganizer = $email; } // Privacy $message->setSensitivity($this->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); // Busy Status // This is the *busy* status of the time for this meeting. This is NOT // the Kronolith_Event::status or the attendance response for this // meeting. Kronolith does not (yet) support sepcifying the busy status // of the event time separate from the STATUS_FREE value of the // Kronolith_Event::status field, so for now we map these values the // best we can by assuming that STATUS_CONFIRMED meetings should always // show as BUSYSTATUS_BUSY etc... switch ($this->status) { case Kronolith::STATUS_CANCELLED: case Kronolith::STATUS_FREE: case Kronolith::STATUS_NONE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_FREE; break; case Kronolith::STATUS_CONFIRMED: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_BUSY; break; case Kronolith::STATUS_TENTATIVE: $status = Horde_ActiveSync_Message_Appointment::BUSYSTATUS_TENTATIVE; } $message->setBusyStatus($status); // DTStamp $message->setDTStamp($_SERVER['REQUEST_TIME']); // Recurrence if ($this->recurs()) { $message->setRecurrence($this->recurrence, $GLOBALS['prefs']->getValue('week_start_monday')); /* Exceptions are tricky. Exceptions, even those that represent * deleted instances of a recurring event, must be added. To do this * we query the storage for all the events that represent exceptions * (those with the baseid == $this->uid) and then remove the * exceptionoriginaldate from the list of exceptions we know about. * Any dates left in this list when we are done, must represent * deleted instances of this recurring event.*/ if (!empty($this->recurrence) && ($exceptions = $this->recurrence->getExceptions())) { $results = $this->boundExceptions(); foreach ($results as $exception) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); $e->setDateTime(array('start' => $exception->start, 'end' => $exception->end, 'allday' => $exception->isAllDay())); // The start time of the *original* recurring event. // EAS < 16.0 uses 'exceptionstarttime'. Otherwise it's // 'instanceid'. if ($options['protocolversion'] < Horde_ActiveSync::VERSION_SIXTEEN) { $e->setExceptionStartTime($exception->exceptionoriginaldate); } else { $e->instanceid = $exception->exceptionoriginaldate; } $originaldate = $exception->exceptionoriginaldate->format('Ymd'); $key = array_search($originaldate, $exceptions); if ($key !== false) { unset($exceptions[$key]); } // Remaining properties that could be different $e->setSubject($exception->getTitle()); if (!$exception->isPrivate()) { $e->setLocation($exception->location); $e->setBody($exception->description); } $e->setSensitivity($exception->private ? Horde_ActiveSync_Message_Appointment::SENSITIVITY_PRIVATE : Horde_ActiveSync_Message_Appointment::SENSITIVITY_NORMAL); $e->setReminder($exception->alarm); $e->setDTStamp($_SERVER['REQUEST_TIME']); if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) { switch ($exception->status) { case Kronolith::STATUS_TENTATIVE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $e->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // Tags/Categories if (!$exception->isPrivate()) { foreach ($exception->tags as $tag) { $e->addCategory($tag); } } $message->addexception($e); } // Any dates left in $exceptions must be deleted exceptions foreach ($exceptions as $deleted) { $e = new Horde_ActiveSync_Message_Exception(array('protocolversion' => $options['protocolversion'])); // Kronolith stores the date only, but some AS clients need // the datetime. list($year, $month, $mday) = sscanf($deleted, '%04d%02d%02d'); $st = clone $this->start; $st->year = $year; $st->month = $month; $st->mday = $mday; if ($options['protocolversion'] < Horde_ActiveSync::VERSION_SIXTEEN) { $e->setExceptionStartTime($st); } else { $e->instanceid = $st; } $e->deleted = true; $message->addException($e); } } } // Attendees if (!$this->isPrivate() && count($attendees)) { $message->setMeetingStatus($this->status == Kronolith::STATUS_CANCELLED ? Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED : Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING); foreach ($attendees as $attendee) { if ($skipOrganizer && $attendee->email == $skipOrganizer) { continue; } $attendeeAS = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $attendeeAS->name = $attendee->addressObject->label; $attendeeAS->email = $attendee->addressObject->bare_address; // AS only has required or optional, and only EAS Version > 2.5 if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $attendeeAS->type = $attendee->role !== Kronolith::PART_REQUIRED ? Horde_ActiveSync_Message_Attendee::TYPE_OPTIONAL : Horde_ActiveSync_Message_Attendee::TYPE_REQUIRED; switch ($attendee->response) { case Kronolith::RESPONSE_NONE: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_NORESPONSE; break; case Kronolith::RESPONSE_ACCEPTED: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_ACCEPT; break; case Kronolith::RESPONSE_DECLINED: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_DECLINE; break; case Kronolith::RESPONSE_TENTATIVE: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_TENTATIVE; break; default: $attendeeAS->status = Horde_ActiveSync_Message_Attendee::STATUS_UNKNOWN; } } $message->addAttendee($attendeeAS); } } elseif ($this->status == Kronolith::STATUS_CANCELLED) { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_CANCELLED); } else { $message->setMeetingStatus(Horde_ActiveSync_Message_Appointment::MEETING_NOT_MEETING); } // Resources if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) { $r = $this->getResources(); foreach ($r as $id => $data) { $resource = Kronolith::getDriver('Resource')->getResource($id); // EAS *REQUIRES* an email field for Resources. If it is missing // a number of clients will fail, losing push. if ($resource->get('email')) { $attendeeAS = new Horde_ActiveSync_Message_Attendee(array('protocolversion' => $options['protocolversion'])); $attendeeAS->email = $resource->get('email'); $attendeeAS->type = Horde_ActiveSync_Message_Attendee::TYPE_RESOURCE; $attendeeAS->name = $data['name']; $attendeeAS->status = $data['response']; $message->addAttendee($attendeeAS); } } } // Reminder if ($this->alarm) { $message->setReminder($this->alarm); } // Categories (tags) if (!$this->isPrivate()) { foreach ($this->tags as $tag) { $message->addCategory($tag); } } // EAS 14, and only if it is a meeting. if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE && $message->getMeetingStatus() == Horde_ActiveSync_Message_Appointment::MEETING_IS_MEETING) { // Are we the if (empty($this->organizer) && $this->creator == $registry->getAuth()) { $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ORGANIZER; } // We don't track the actual responses we sent to other's invitations. // Set this based on the status flag. switch ($this->status) { case Kronolith::STATUS_TENTATIVE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_TENTATIVE; break; case Kronolith::STATUS_NONE: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NORESPONSE; break; case Kronolith::STATUS_CONFIRMED: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_ACCEPTED; break; default: $message->responsetype = Horde_ActiveSync_Message_Appointment::RESPONSE_NONE; } } // 14.1 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_FOURTEENONE) { $message->onlinemeetingexternallink = $this->url; } // 16.0 if ($options['protocolversion'] >= Horde_ActiveSync::VERSION_SIXTEEN) { $files = $this->listFiles(); if (count($files)) { foreach ($files as $file) { $atc = new Horde_ActiveSync_Message_AirSyncBaseAttachment(array('logger' => $GLOBALS['injector']->getInstance('Horde_Log_Logger'), 'protocolversion' => $options['protocolversion'])); $atc->displayname = $file['name']; $atc->attname = $this->_getEASFileReference($file['name']); $atc->attmethod = Horde_ActiveSync_Message_AirSyncBaseAttachment::ATT_TYPE_NORMAL; $atc->attsize = $file['size']; $message->addAttachment($atc); } } } return $message; }