public function testIsChild() { $this->assertTrue(Horde_Mime::isChild('1', '1.0')); $this->assertTrue(Horde_Mime::isChild('1', '1.1')); $this->assertTrue(Horde_Mime::isChild('1', '1.1.0')); $this->assertFalse(Horde_Mime::isChild('1', '1')); $this->assertFalse(Horde_Mime::isChild('1', '2.1')); $this->assertFalse(Horde_Mime::isChild('1', '10.0')); }
/** * Generate inline message display. * * @param array $options Options: * - display_mask: (integer) The mask of display view type to render * inline (DEFAULT: RENDER_INLINE_AUTO). * - mask: (integer) The mask needed for a getSummary() call. * - no_inline_all: (boolean) If true, only display first inline part. * Subsequent inline parts will be treated as * attachments. * - part_info_display: (array) The list of summary fields to display. * - show_parts: (string) The value of the 'parts_display' pref. * * @return array An array with the following keys: * - atc_parts: (array) The list of attachment MIME IDs. * - display_ids: (array) The list of display MIME IDs. * - js_onload: (array) A list of javascript code to run onload. * - msgtext: (string) The rendered HTML code. * - one_part: (boolean) If true, the message only consists of one part. */ public function getInlineOutput(array $options = array()) { global $prefs, $registry; $atc_parts = $display_ids = $msgtext = $js_onload = $wrap_ids = array(); $parts_list = $this->getContentTypeMap(); $text_out = ''; $view = $registry->getView(); $contents_mask = isset($options['mask']) ? $options['mask'] : 0; $display_mask = isset($options['display_mask']) ? $options['display_mask'] : self::RENDER_INLINE_AUTO; $no_inline_all = !empty($options['no_inline_all']); $part_info_display = isset($options['part_info_display']) ? $options['part_info_display'] : array(); $show_parts = isset($options['show_parts']) ? $options['show_parts'] : $prefs->getValue('parts_display'); foreach ($parts_list as $mime_id => $mime_type) { if (isset($display_ids[$mime_id]) || isset($atc_parts[$mime_id])) { continue; } if (!($render_mode = $this->canDisplay($mime_id, $display_mask))) { if ($this->isAttachment($mime_type)) { if ($show_parts == 'atc') { $atc_parts[$mime_id] = 1; } if ($contents_mask) { $msgtext[$mime_id] = array('text' => $this->_formatSummary($mime_id, $contents_mask, $part_info_display, true)); } } continue; } $render_part = $this->renderMIMEPart($mime_id, $render_mode); if ($show_parts == 'atc' && $this->isAttachment($mime_type) && (empty($render_part) || !($render_mode & self::RENDER_INLINE))) { $atc_parts[$mime_id] = 1; } if (empty($render_part)) { if ($contents_mask && $this->isAttachment($mime_type)) { $msgtext[$mime_id] = array('text' => $this->_formatSummary($mime_id, $contents_mask, $part_info_display, true)); } continue; } reset($render_part); while (list($id, $info) = each($render_part)) { $display_ids[$id] = 1; if (empty($info)) { continue; } if ($no_inline_all === 1) { $atc_parts[$id] = 1; continue; } $part_text = $contents_mask && empty($info['nosummary']) ? $this->_formatSummary($id, $contents_mask, $part_info_display, !empty($info['attach'])) : ''; if (empty($info['attach'])) { if (isset($info['status'])) { if (!is_array($info['status'])) { $info['status'] = array($info['status']); } foreach ($info['status'] as $val) { if (in_array($view, $val->views)) { $part_text .= strval($val); } } } $part_text .= '<div class="mimePartData">' . $info['data'] . '</div>'; } elseif ($show_parts == 'atc') { $atc_parts[$id] = 1; } $msgtext[$id] = array('text' => $part_text, 'wrap' => empty($info['wrap']) ? null : $info['wrap']); if (isset($info['js'])) { $js_onload = array_merge($js_onload, $info['js']); } if ($no_inline_all) { $no_inline_all = 1; } } } if (!empty($msgtext)) { uksort($msgtext, 'strnatcmp'); } reset($msgtext); while (list($id, $part) = each($msgtext)) { while (!empty($wrap_ids) && !Horde_Mime::isChild(end($wrap_ids), $id)) { array_pop($wrap_ids); $text_out .= '</div>'; } if (!empty($part['wrap'])) { $text_out .= '<div class="' . $part['wrap'] . '">'; $wrap_ids[] = $id; } $text_out .= '<div class="mimePartBase">' . $part['text'] . '</div>'; } $text_out .= str_repeat('</div>', count($wrap_ids)); if (!strlen($text_out)) { $text_out = strval(new IMP_Mime_Status(_("There are no parts that can be shown inline."))); } $atc_parts = $show_parts == 'all' ? array_keys($parts_list) : array_keys($atc_parts); return array('atc_parts' => $atc_parts, 'display_ids' => array_keys($display_ids), 'js_onload' => $js_onload, 'msgtext' => $text_out, 'one_part' => count($parts_list) == 1); }
/** * Regenerates body text for use in the compose screen from IMAP data. * * @param IMP_Contents $contents An IMP_Contents object. * @param array $options Additional options: * <ul> * <li>html: (boolean) Return text/html part, if available.</li> * <li>imp_msg: (integer) If non-empty, the message data was created by * IMP. Either: * <ul> * <li>self::COMPOSE</li> * <li>self::FORWARD</li> * <li>self::REPLY</li> * </ul> * </li> * <li>replylimit: (boolean) Enforce length limits?</li> * <li>toflowed: (boolean) Do flowed conversion?</li> * </ul> * * @return mixed Null if bodypart not found, or array with the following * keys: * - charset: (string) The guessed charset to use. * - flowed: (Horde_Text_Flowed) A flowed object, if the text is flowed. * Otherwise, null. * - id: (string) The MIME ID of the bodypart. * - mode: (string) Either 'text' or 'html'. * - text: (string) The body text. */ protected function _getMessageText($contents, array $options = array()) { global $conf, $injector, $notification, $prefs, $session; $body_id = null; $mode = 'text'; $options = array_merge(array('imp_msg' => self::COMPOSE), $options); if (!empty($options['html']) && $session->get('imp', 'rteavail') && ($body_id = $contents->findBody('html')) !== null) { $mime_message = $contents->getMIMEMessage(); switch ($mime_message->getPrimaryType()) { case 'multipart': if ($body_id != '1' && $mime_message->getSubType() == 'mixed' && !Horde_Mime::isChild('1', $body_id)) { $body_id = null; } else { $mode = 'html'; } break; default: if (strval($body_id) != '1') { $body_id = null; } else { $mode = 'html'; } break; } } if (is_null($body_id)) { $body_id = $contents->findBody(); if (is_null($body_id)) { return null; } } $part = $contents->getMIMEPart($body_id); $type = $part->getType(); $part_charset = $part->getCharset(); $msg = Horde_String::convertCharset($part->getContents(), $part_charset, 'UTF-8'); /* Enforce reply limits. */ if (!empty($options['replylimit']) && !empty($conf['compose']['reply_limit'])) { $limit = $conf['compose']['reply_limit']; if (Horde_String::length($msg) > $limit) { $msg = Horde_String::substr($msg, 0, $limit) . "\n" . _("[Truncated Text]"); } } if ($mode == 'html') { $dom = $injector->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, 'Xss', array('charset' => $this->charset, 'return_dom' => true, 'strip_style_attributes' => false)); /* If we are replying to a related part, and this part refers * to local message parts, we need to move those parts into this * message (since the original message may disappear during the * compose process). */ if ($related_part = $contents->findMimeType($body_id, 'multipart/related')) { $this->_setMetadata('related_contents', $contents); $related_ob = new Horde_Mime_Related($related_part); $related_ob->cidReplace($dom, array($this, '_getMessageTextCallback'), $part_charset); $this->_setMetadata('related_contents', null); } /* Convert any Data URLs to attachments. */ $xpath = new DOMXPath($dom->dom); foreach ($xpath->query('//*[@src]') as $val) { $data_url = new Horde_Url_Data($val->getAttribute('src')); if (strlen($data_url->data)) { $data_part = new Horde_Mime_Part(); $data_part->setContents($data_url->data); $data_part->setType($data_url->type); try { $atc = $this->addAttachmentFromPart($data_part); $val->setAttribute('src', $atc->viewUrl()); $this->addRelatedAttachment($atc, $val, 'src'); } catch (IMP_Compose_Exception $e) { $notification->push($e, 'horde.warning'); } } } $msg = $dom->returnBody(); } elseif ($type == 'text/html') { $msg = $injector->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, 'Html2text'); $type = 'text/plain'; } /* Always remove leading/trailing whitespace. The data in the * message body is not intended to be the exact representation of the * original message (use forward as message/rfc822 part for that). */ $msg = trim($msg); if ($type == 'text/plain') { if ($prefs->getValue('reply_strip_sig') && ($pos = strrpos($msg, "\n-- ")) !== false) { $msg = rtrim(substr($msg, 0, $pos)); } /* Remove PGP armored text. */ $pgp = $injector->getInstance('Horde_Crypt_Pgp_Parse')->parseToPart($msg); if (!is_null($pgp)) { $msg = ''; $pgp->buildMimeIds(); foreach ($pgp->contentTypeMap() as $key => $val) { if (strpos($val, 'text/') === 0) { $msg .= $pgp[$key]->getContents(); } } } if ($part->getContentTypeParameter('format') == 'flowed') { $flowed = new Horde_Text_Flowed($msg, 'UTF-8'); if (Horde_String::lower($part->getContentTypeParameter('delsp')) == 'yes') { $flowed->setDelSp(true); } $flowed->setMaxLength(0); $msg = $flowed->toFixed(false); } else { /* If the input is *not* in flowed format, make sure there is * no padding at the end of lines. */ $msg = preg_replace("/\\s*\n/U", "\n", $msg); } if (isset($options['toflowed'])) { $flowed = new Horde_Text_Flowed($msg, 'UTF-8'); $msg = $options['toflowed'] ? $flowed->toFlowed(true) : $flowed->toFlowed(false, array('nowrap' => true)); } } if (strcasecmp($part->getCharset(), 'windows-1252') === 0) { $part_charset = 'ISO-8859-1'; } return array('charset' => $part_charset, 'flowed' => isset($flowed) ? $flowed : null, 'id' => $body_id, 'mode' => $mode, 'text' => $msg); }
/** * Render out the currently set contents. * * @param boolean $inline Are we viewing inline? * * @return array See self::render(). */ protected function _IMPrender($inline) { $related_id = $this->_mimepart->getMimeId(); $used = array($related_id); if (!($id = $this->_init($inline))) { return array(); } $render = $this->getConfigParam('imp_contents')->renderMIMEPart($id, $inline ? IMP_Contents::RENDER_INLINE : IMP_Contents::RENDER_FULL); if (!$inline) { foreach (array_keys($render) as $key) { if (!is_null($render[$key])) { return array($related_id => $render[$key]); } } return null; } $data_id = null; $ret = array_fill_keys(array_keys($this->_mimepart->contentTypeMap()), null); foreach (array_keys($render) as $val) { $ret[$val] = $render[$val]; if ($ret[$val]) { $data_id = $val; } } if (!is_null($data_id)) { $this->_mimepart->setMetadata('viewable_part', $data_id); /* We want the inline display to show multipart/related vs. the * viewable MIME part. This is because a multipart/related part * is not downloadable and clicking on the MIME part may not * produce the desired result in the full display (i.e. HTML parts * with related images). */ if ($data_id !== $related_id) { $ret[$related_id] = $ret[$data_id]; $ret[$data_id] = null; } } /* Fix for broken messages that don't refer to a related CID part * within the base part. */ if ($cids_used = $this->_mimepart->getMetadata('related_cids_used')) { $used = array_merge($used, $cids_used); } foreach (array_diff(array_keys($ret), $used) as $val) { if ($val !== $id && !Horde_Mime::isChild($id, $val)) { $summary = $this->getConfigParam('imp_contents')->getSummary($val, IMP_Contents::SUMMARY_SIZE | IMP_Contents::SUMMARY_ICON | IMP_Contents::SUMMARY_DESCRIP_LINK | IMP_Contents::SUMMARY_DOWNLOAD); $status = new IMP_Mime_Status(array(_("This part contains an attachment that can not be displayed within this part:"), implode(' ', array($summary['icon'], $summary['description'], $summary['size'], $summary['download'])))); $status->action(IMP_Mime_Status::WARNING); if (isset($ret[$related_id]['status'])) { if (!is_array($ret[$related_id]['status'])) { $ret[$related_id]['status'] = array($ret[$related_id]['status']); } } else { $ret[$related_id]['status'] = array(); } $ret[$related_id]['status'][] = $status; } } return $ret; }