1645 lines
54 KiB
PHP
1645 lines
54 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (GPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/gpl.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2002-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/gpl GPL
|
|
* @package IMP
|
|
*/
|
|
|
|
/**
|
|
* The IMP_Contents:: class contains all functions related to handling the
|
|
* content and output of mail messages in IMP.
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @copyright 2002-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/gpl GPL
|
|
* @package IMP
|
|
*/
|
|
class IMP_Contents
|
|
{
|
|
/* Mask entries for getSummary(). */
|
|
const SUMMARY_BYTES = 1;
|
|
const SUMMARY_SIZE = 2;
|
|
const SUMMARY_ICON = 4;
|
|
const SUMMARY_ICON_RAW = 16384;
|
|
const SUMMARY_DESCRIP = 8;
|
|
const SUMMARY_DESCRIP_LINK = 16;
|
|
const SUMMARY_DOWNLOAD = 32;
|
|
const SUMMARY_DOWNLOAD_ZIP = 64;
|
|
const SUMMARY_IMAGE_SAVE = 128;
|
|
const SUMMARY_PRINT = 256;
|
|
const SUMMARY_PRINT_STUB = 512;
|
|
const SUMMARY_STRIP = 1024;
|
|
|
|
/* Rendering mask entries. */
|
|
const RENDER_FULL = 1;
|
|
const RENDER_INLINE = 2;
|
|
const RENDER_INLINE_DISP_NO = 4;
|
|
const RENDER_INFO = 8;
|
|
const RENDER_INLINE_AUTO = 16;
|
|
const RENDER_RAW = 32;
|
|
const RENDER_RAW_FALLBACK = 64;
|
|
|
|
/* Header return type for getHeader(). */
|
|
const HEADER_OB = 1;
|
|
const HEADER_TEXT = 2;
|
|
const HEADER_STREAM = 3;
|
|
|
|
/**
|
|
* Have we scanned for embedded parts?
|
|
*
|
|
* @var boolean
|
|
*/
|
|
protected $_build = false;
|
|
|
|
/**
|
|
* The list of MIME IDs that consist of embedded data.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_embedded = array();
|
|
|
|
/**
|
|
* Message header.
|
|
*
|
|
* @var mixed
|
|
*/
|
|
protected $_header;
|
|
|
|
/**
|
|
* The index of the current message.
|
|
*
|
|
* @var IMP_Indices_Mailbox
|
|
*/
|
|
protected $_indices;
|
|
|
|
/**
|
|
* The Horde_Mime_Part object for the message.
|
|
*
|
|
* @var Horde_Mime_Part
|
|
*/
|
|
protected $_message;
|
|
|
|
/**
|
|
* Cached data for the MIME Viewer objects.
|
|
*
|
|
* @var object
|
|
*/
|
|
protected $_viewcache;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param mixed $in An IMP_Indices_Mailbox or Horde_Mime_Part object.
|
|
*
|
|
* @throws IMP_Exception
|
|
*/
|
|
public function __construct($in)
|
|
{
|
|
if ($in instanceof Horde_Mime_Part) {
|
|
$this->_message = $in;
|
|
} else {
|
|
$this->_indices = $in;
|
|
|
|
/* Get the Horde_Mime_Part object for the given UID. */
|
|
$query = new Horde_Imap_Client_Fetch_Query();
|
|
$query->structure();
|
|
|
|
if (!($ret = $this->_fetchData($query))) {
|
|
$e = new IMP_Exception(_("Error displaying message: message does not exist on server."));
|
|
$e->setLogLevel('NOTICE');
|
|
throw $e;
|
|
}
|
|
|
|
$this->_message = $ret->getStructure();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* String representation of object.
|
|
*
|
|
* @return string The indices string.
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return strval($this->getIndicesOb());
|
|
}
|
|
|
|
/**
|
|
* Returns the IMAP UID for the current message.
|
|
*
|
|
* @return integer The message UID.
|
|
*/
|
|
public function getUid()
|
|
{
|
|
list(,$uid) = $this->_indices->getSingle();
|
|
return $uid;
|
|
}
|
|
|
|
/**
|
|
* Returns the IMAP mailbox for the current message.
|
|
*
|
|
* @return IMP_Mailbox The message mailbox.
|
|
*/
|
|
public function getMailbox()
|
|
{
|
|
list($mbox,) = $this->_indices->getSingle();
|
|
return $mbox;
|
|
}
|
|
|
|
/**
|
|
* Return an IMP_Indices object for the current message.
|
|
*
|
|
* @return IMP_Indices An indices object.
|
|
*/
|
|
public function getIndicesOb()
|
|
{
|
|
return $this->_indices;
|
|
}
|
|
|
|
/**
|
|
* Returns the entire body of the message.
|
|
*
|
|
* @param array $options Additional options:
|
|
* - stream: (boolean) If true, return a stream.
|
|
* DEFAULT: No
|
|
*
|
|
* @return mixed The text of the part, or a stream resource if 'stream'
|
|
* is true.
|
|
*/
|
|
public function getBody($options = array())
|
|
{
|
|
if (!$this->_indices) {
|
|
return $this->_message->toString(array(
|
|
'headers' => true,
|
|
'stream' => !empty($options['stream'])
|
|
));
|
|
}
|
|
|
|
$query = new Horde_Imap_Client_Fetch_Query();
|
|
$query->bodytext(array(
|
|
'peek' => true
|
|
));
|
|
|
|
return ($res = $this->_fetchData($query))
|
|
? $res->getBodyText(0, !empty($options['stream']))
|
|
: '';
|
|
}
|
|
|
|
/**
|
|
* Gets the raw text for one section of the message.
|
|
*
|
|
* @param integer $id The ID of the MIME part.
|
|
* @param array $options Additional options:
|
|
* - decode: (boolean) Attempt to decode the bodypart on the remote
|
|
* server.
|
|
* DEFAULT: No
|
|
* - length: (integer) If set, only download this many bytes of the
|
|
* bodypart from the server.
|
|
* DEFAULT: All data is retrieved.
|
|
* - mimeheaders: (boolean) Include the MIME headers also?
|
|
* DEFAULT: No
|
|
* - stream: (boolean) If true, return a stream.
|
|
* DEFAULT: No
|
|
*
|
|
* @return object Object with the following properties:
|
|
* <pre>
|
|
* - data: (mixed) The text of the part or a stream resource if 'stream'
|
|
* option is true.
|
|
* - decode: (string) If 'decode' option is true, and bodypart decoded
|
|
* on server, the content-type of the decoded data.
|
|
* </pre>
|
|
*/
|
|
public function getBodyPart($id, $options = array())
|
|
{
|
|
$ret = new stdClass;
|
|
$ret->data = '';
|
|
$ret->decode = null;
|
|
|
|
if (empty($id)) {
|
|
return $ret;
|
|
}
|
|
|
|
if (!$this->_indices || $this->isEmbedded($id)) {
|
|
if (empty($options['mimeheaders']) ||
|
|
in_array($id, $this->_embedded)) {
|
|
$ob = $this->getMIMEPart($id, array('nocontents' => true));
|
|
|
|
if (empty($options['stream'])) {
|
|
if (!is_null($ob)) {
|
|
$ret->data = $ob->getContents();
|
|
}
|
|
} else {
|
|
$ret->data = is_null($ob)
|
|
? fopen('php://temp', 'r+')
|
|
: $ob->getContents(array('stream' => true));
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
$base_id = $id;
|
|
while (!in_array($base_id, $this->_embedded, true)) {
|
|
$base_id = Horde_Mime::mimeIdArithmetic($base_id, 'up');
|
|
if (is_null($base_id)) {
|
|
return $ret;
|
|
}
|
|
}
|
|
|
|
$part = $this->getMIMEPart($base_id, array('nocontents' => true));
|
|
$txt = $part->addMimeHeaders()->toString() .
|
|
"\n" .
|
|
$part->getContents();
|
|
|
|
try {
|
|
$body = Horde_Mime_Part::getRawPartText($txt, 'header', '1') .
|
|
"\n\n" .
|
|
Horde_Mime_Part::getRawPartText($txt, 'body', '1');
|
|
} catch (Horde_Mime_Exception $e) {
|
|
$body = '';
|
|
}
|
|
|
|
if (empty($options['stream'])) {
|
|
$ret->data = $body;
|
|
return $ret;
|
|
}
|
|
|
|
$ret->data = fopen('php://temp', 'r+');
|
|
if (strlen($body)) {
|
|
fwrite($ret->data, $body);
|
|
fseek($ret->data, 0);
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
$query = new Horde_Imap_Client_Fetch_Query();
|
|
if (substr($id, -2) === '.0') {
|
|
$rfc822 = true;
|
|
$id = substr($id, 0, -2);
|
|
} else {
|
|
$rfc822 = false;
|
|
}
|
|
|
|
if (!isset($options['length']) || !empty($options['length'])) {
|
|
$bodypart_params = array(
|
|
'decode' => !empty($options['decode']),
|
|
'peek' => true
|
|
);
|
|
|
|
if (isset($options['length'])) {
|
|
$bodypart_params['start'] = 0;
|
|
$bodypart_params['length'] = $options['length'];
|
|
}
|
|
|
|
if ($rfc822) {
|
|
$bodypart_params['id'] = $id;
|
|
$query->bodyText($bodypart_params);
|
|
} else {
|
|
$query->bodyPart($id, $bodypart_params);
|
|
}
|
|
}
|
|
|
|
if (!empty($options['mimeheaders'])) {
|
|
if ($rfc822) {
|
|
$query->headerText(array(
|
|
'id' => $id,
|
|
'peek' => true
|
|
));
|
|
} else {
|
|
$query->mimeHeader($id, array(
|
|
'peek' => true
|
|
));
|
|
}
|
|
}
|
|
|
|
if ($res = $this->_fetchData($query)) {
|
|
try {
|
|
if (empty($options['mimeheaders'])) {
|
|
$ret->decode = $res->getBodyPartDecode($id);
|
|
$ret->data = $rfc822
|
|
? $res->getBodyText($id, !empty($options['stream']))
|
|
: $res->getBodyPart($id, !empty($options['stream']));
|
|
return $ret;
|
|
} elseif (empty($options['stream'])) {
|
|
$ret->data = $rfc822
|
|
? ($res->getHeaderText($id) . $res->getBodyText($id))
|
|
: ($res->getMimeHeader($id) . $res->getBodyPart($id));
|
|
return $ret;
|
|
}
|
|
|
|
if ($rfc822) {
|
|
$data = array(
|
|
$res->getHeaderText($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM),
|
|
$res->getBodyText($id, true)
|
|
);
|
|
} else {
|
|
$data = array(
|
|
$res->getMimeHeader($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM),
|
|
$res->getBodyPart($id, true)
|
|
);
|
|
}
|
|
|
|
$ret->data = Horde_Stream_Wrapper_Combine::getStream($data);
|
|
return $ret;
|
|
} catch (Horde_Exception $e) {}
|
|
}
|
|
|
|
if (!empty($options['stream'])) {
|
|
$ret->data = fopen('php://temp', 'r+');
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Returns the full message text.
|
|
*
|
|
* @param array $options Additional options:
|
|
* - stream: (boolean) If true, return a stream for bodytext.
|
|
* DEFAULT: No
|
|
*
|
|
* @return mixed The full message text or a stream resource if 'stream'
|
|
* is true.
|
|
*/
|
|
public function fullMessageText($options = array())
|
|
{
|
|
if (!$this->_indices) {
|
|
return $this->_message->toString();
|
|
}
|
|
|
|
$query = new Horde_Imap_Client_Fetch_Query();
|
|
$query->bodyText(array(
|
|
'peek' => true
|
|
));
|
|
|
|
if ($res = $this->_fetchData($query)) {
|
|
try {
|
|
if (empty($options['stream'])) {
|
|
return $this->getHeader(self::HEADER_TEXT) . $res->getBodyText(0);
|
|
}
|
|
|
|
return Horde_Stream_Wrapper_Combine::getStream(array(
|
|
$this->getHeader(self::HEADER_STREAM),
|
|
$res->getBodyText(0, true)
|
|
));
|
|
} catch (Horde_Exception $e) {}
|
|
}
|
|
|
|
return empty($options['stream'])
|
|
? ''
|
|
: fopen('php://temp', 'r+');
|
|
}
|
|
|
|
/**
|
|
* Returns base header information.
|
|
*
|
|
* @param integer $type Return type (HEADER_* constant).
|
|
*
|
|
* @return mixed Either a Horde_Mime_Headers object (HEADER_OB), header
|
|
* text (HEADER_TEXT), or a stream resource (HEADER_STREAM).
|
|
*/
|
|
public function getHeader($type = self::HEADER_OB)
|
|
{
|
|
return $this->_getHeader($type, false);
|
|
}
|
|
|
|
/**
|
|
* Returns base header information and marks the message as seen.
|
|
*
|
|
* @param integer $type See getHeader().
|
|
*
|
|
* @return mixed See getHeader().
|
|
*/
|
|
public function getHeaderAndMarkAsSeen($type = self::HEADER_OB)
|
|
{
|
|
$mbox = $this->getMailbox();
|
|
|
|
if ($mbox->readonly) {
|
|
$seen = false;
|
|
} else {
|
|
$seen = true;
|
|
|
|
if (isset($this->_header)) {
|
|
try {
|
|
$imp_imap = $mbox->imp_imap;
|
|
$imp_imap->store($mbox, array(
|
|
'add' => array(
|
|
Horde_Imap_Client::FLAG_SEEN
|
|
),
|
|
'ids' => $imp_imap->getIdsOb($this->getUid())
|
|
));
|
|
} catch (Exception $e) {}
|
|
}
|
|
}
|
|
|
|
return $this->_getHeader($type, $seen);
|
|
}
|
|
|
|
/**
|
|
* Returns base header information.
|
|
*
|
|
* @param integer $type See getHeader().
|
|
* @param boolean $seen Mark message as seen?
|
|
*
|
|
* @return mixed See getHeader().
|
|
*/
|
|
protected function _getHeader($type, $seen)
|
|
{
|
|
if (!isset($this->_header)) {
|
|
if (!$this->_indices) {
|
|
$this->_header = $this->_message->addMimeHeaders();
|
|
} else {
|
|
$query = new Horde_Imap_Client_Fetch_Query();
|
|
$query->headerText(array(
|
|
'peek' => !$seen
|
|
));
|
|
|
|
$this->_header = ($res = $this->_fetchData($query))
|
|
? $res
|
|
: new Horde_Imap_Client_Data_Fetch();
|
|
}
|
|
}
|
|
|
|
switch ($type) {
|
|
case self::HEADER_OB:
|
|
return $this->_indices
|
|
? $this->_header->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_PARSE)
|
|
: $this->_header;
|
|
|
|
case self::HEADER_TEXT:
|
|
return $this->_indices
|
|
? $this->_header->getHeaderText()
|
|
: $this->_header->toString();
|
|
|
|
case self::HEADER_STREAM:
|
|
if ($this->_indices) {
|
|
return $this->_header->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_STREAM);
|
|
}
|
|
|
|
$stream = new Horde_Support_StringStream($this->_header->toString());
|
|
$stream->fopen();
|
|
return $stream;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Horde_Mime_Part object.
|
|
*
|
|
* @return Horde_Mime_Part A Horde_Mime_Part object.
|
|
*/
|
|
public function getMIMEMessage()
|
|
{
|
|
return $this->_message;
|
|
}
|
|
|
|
/**
|
|
* Fetch a part of a MIME message.
|
|
*
|
|
* @param integer $id The MIME index of the part requested.
|
|
* @param array $options Additional options:
|
|
* - length: (integer) If set, only download this many bytes of the
|
|
* bodypart from the server.
|
|
* DEFAULT: All data is retrieved.
|
|
* - nocontents: (boolean) If true, don't add the contents to the part
|
|
* DEFAULT: Contents are added to the part
|
|
*
|
|
* @return Horde_Mime_Part The raw MIME part asked for (reference).
|
|
*/
|
|
public function getMIMEPart($id, $options = array())
|
|
{
|
|
$this->_buildMessage();
|
|
|
|
$part = $this->_message->getPart($id);
|
|
|
|
/* Ticket #9201: Treat 'ISO-8859-1' as 'windows-1252'. 1252 has some
|
|
* characters (e.g. euro sign, back quote) not in 8859-1. There
|
|
* shouldn't be any issue doing this since the additional code points
|
|
* in 1252 don't map to anything in 8859-1. */
|
|
if ($part &&
|
|
(strcasecmp($part->getCharset(), 'ISO-8859-1') === 0)) {
|
|
$part->setCharset('windows-1252');
|
|
}
|
|
|
|
/* Don't download contents of entire body if ID == 0 (indicating the
|
|
* body of the main multipart message). I'm pretty sure we never
|
|
* want to download the body of that part here. */
|
|
if (!empty($id) &&
|
|
!is_null($part) &&
|
|
(substr($id, -2) != '.0') &&
|
|
empty($options['nocontents']) &&
|
|
$this->_indices &&
|
|
!$part->getContents(array('stream' => true))) {
|
|
$body = $this->getBodyPart($id, array(
|
|
'decode' => true,
|
|
'length' => empty($options['length']) ? null : $options['length'],
|
|
'stream' => true
|
|
));
|
|
$part->setContents($body->data, array(
|
|
'encoding' => $body->decode,
|
|
'usestream' => true
|
|
));
|
|
}
|
|
|
|
return $part;
|
|
}
|
|
|
|
/**
|
|
* Render a MIME Part.
|
|
*
|
|
* @param string $mime_id The MIME ID to render.
|
|
* @param integer $mode One of the RENDER_ constants.
|
|
* @param array $options Additional options:
|
|
* - autodetect: (boolean) Attempt to auto-detect MIME type?
|
|
* - mime_part: (Horde_Mime_Part) The MIME part to render.
|
|
* - type: (string) Use this MIME type instead of the MIME type
|
|
* identified in the MIME part.
|
|
*
|
|
* @return array See Horde_Mime_Viewer_Base::render(). The following
|
|
* fields may also be present in addition to the fields
|
|
* defined in Horde_Mime_Viewer_Base:
|
|
* - attach: (boolean) Force display of this part as an attachment.
|
|
* - js: (array) A list of javascript commands to run after the content
|
|
* is displayed on screen.
|
|
* - name: (string) Contains the MIME name information.
|
|
* - wrap: (string) If present, indicates that this part, and all child
|
|
* parts, will be wrapped in a DIV with the given class name.
|
|
*/
|
|
public function renderMIMEPart($mime_id, $mode, array $options = array())
|
|
{
|
|
$this->_buildMessage();
|
|
|
|
$mime_part = empty($options['mime_part'])
|
|
? $this->getMIMEPart($mime_id)
|
|
: $options['mime_part'];
|
|
if (!$mime_part) {
|
|
return array($mime_id => null);
|
|
}
|
|
|
|
if (!empty($options['autodetect']) &&
|
|
($tempfile = Horde::getTempFile()) &&
|
|
($fp = fopen($tempfile, 'w')) &&
|
|
!is_null($contents = $mime_part->getContents(array('stream' => true)))) {
|
|
rewind($contents);
|
|
while (!feof($contents)) {
|
|
fwrite($fp, fread($contents, 8192));
|
|
}
|
|
fclose($fp);
|
|
|
|
$options['type'] = Horde_Mime_Magic::analyzeFile($tempfile, empty($GLOBALS['conf']['mime']['magic_db']) ? null : $GLOBALS['conf']['mime']['magic_db']);
|
|
}
|
|
|
|
$type = empty($options['type'])
|
|
? null
|
|
: $options['type'];
|
|
|
|
$viewer = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->create($mime_part, array('contents' => $this, 'type' => $type));
|
|
|
|
switch ($mode) {
|
|
case self::RENDER_INLINE:
|
|
case self::RENDER_INLINE_AUTO:
|
|
case self::RENDER_INLINE_DISP_NO:
|
|
$textmode = 'inline';
|
|
$limit = $viewer->getConfigParam('limit_inline_size');
|
|
|
|
if ($limit && ($mime_part->getBytes() > $limit)) {
|
|
$data = '';
|
|
$status = new IMP_Mime_Status(array(
|
|
_("This message part cannot be viewed because it is too large."),
|
|
sprintf(_("Click %s to download the data."), $this->linkView($mime_part, 'download_attach', _("HERE")))
|
|
));
|
|
$status->icon('alerts/warning.png', _("Warning"));
|
|
|
|
if (method_exists($viewer, 'overLimitText')) {
|
|
$data = $viewer->overLimitText();
|
|
$status->addText(_("The initial portion of this text part is displayed below."));
|
|
}
|
|
|
|
return array(
|
|
$mime_id => array(
|
|
'data' => $data,
|
|
'name' => '',
|
|
'status' => $status,
|
|
'type' => 'text/html; charset=' . 'UTF-8'
|
|
)
|
|
);
|
|
}
|
|
break;
|
|
|
|
case self::RENDER_INFO:
|
|
$textmode = 'info';
|
|
break;
|
|
|
|
case self::RENDER_RAW:
|
|
$textmode = 'raw';
|
|
break;
|
|
|
|
case self::RENDER_RAW_FALLBACK:
|
|
$textmode = $viewer->canRender('raw')
|
|
? 'raw'
|
|
: 'full';
|
|
break;
|
|
|
|
case self::RENDER_FULL:
|
|
default:
|
|
$textmode = 'full';
|
|
break;
|
|
}
|
|
|
|
$ret = $viewer->render($textmode);
|
|
|
|
if (empty($ret)) {
|
|
return ($mode == self::RENDER_INLINE_AUTO)
|
|
? $this->renderMIMEPart($mime_id, self::RENDER_INFO, $options)
|
|
: array();
|
|
}
|
|
|
|
if (!empty($ret[$mime_id]) && !isset($ret[$mime_id]['name'])) {
|
|
$ret[$mime_id]['name'] = $mime_part->getName(true);
|
|
}
|
|
|
|
/* Don't show empty parts. */
|
|
if (($textmode == 'inline') &&
|
|
!is_null($ret[$mime_id]['data']) &&
|
|
!strlen($ret[$mime_id]['data']) &&
|
|
!isset($ret[$mime_id]['status'])) {
|
|
$ret[$mime_id] = null;
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Finds the main "body" text part (if any) in a message.
|
|
* "Body" data is the first text part in the base MIME part.
|
|
*
|
|
* @param string $subtype Specifically search for this subtype.
|
|
*
|
|
* @return string The MIME ID of the main body part.
|
|
*/
|
|
public function findBody($subtype = null)
|
|
{
|
|
$this->_buildMessage();
|
|
return $this->_message->findBody($subtype);
|
|
}
|
|
|
|
/**
|
|
* Generate the preview text.
|
|
*
|
|
* @return array Array with the following keys:
|
|
* - cut: (boolean) Was the preview text cut?
|
|
* - text: (string) The preview text.
|
|
*/
|
|
public function generatePreview()
|
|
{
|
|
// For preview generation, don't go through overhead of scanning for
|
|
// embedded parts. Necessary evil, or else very large parts (e.g
|
|
// 5 MB+ text parts) will take ages to scan.
|
|
$oldbuild = $this->_build;
|
|
$this->_build = true;
|
|
$mimeid = $this->findBody();
|
|
|
|
if (is_null($mimeid)) {
|
|
$this->_build = $oldbuild;
|
|
return array('cut' => false, 'text' => '');
|
|
}
|
|
|
|
$maxlen = empty($GLOBALS['conf']['msgcache']['preview_size'])
|
|
? $GLOBALS['prefs']->getValue('preview_maxlen')
|
|
: $GLOBALS['conf']['msgcache']['preview_size'];
|
|
|
|
// Retrieve 3x the size of $maxlen of bodytext data. This should
|
|
// account for any content-encoding & HTML tags.
|
|
$pmime = $this->getMIMEPart($mimeid, array('length' => $maxlen * 3));
|
|
|
|
$ptext = Horde_String::convertCharset($pmime->getContents(), $pmime->getCharset(), 'UTF-8');
|
|
|
|
if ($pmime->getType() == 'text/html') {
|
|
$ptext = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($ptext, 'Html2text');
|
|
}
|
|
|
|
$this->_build = $oldbuild;
|
|
|
|
if (Horde_String::length($ptext) > $maxlen) {
|
|
return array(
|
|
'cut' => true,
|
|
'text' => Horde_String::truncate($ptext, $maxlen)
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'cut' => false,
|
|
'text' => $ptext
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get summary info for a MIME ID.
|
|
*
|
|
* @param string $id The MIME ID.
|
|
* @param integer $mask A bitmask indicating what information to return:
|
|
* <pre>
|
|
* Always output:
|
|
* 'type' = MIME type
|
|
*
|
|
* IMP_Contents::SUMMARY_BYTES
|
|
* Output: parts = 'bytes'
|
|
*
|
|
* IMP_Contents::SUMMARY_SIZE
|
|
* Output: parts = 'size'
|
|
*
|
|
* IMP_Contents::SUMMARY_ICON
|
|
* IMP_Contents::SUMMARY_ICON_RAW
|
|
* Output: parts = 'icon'
|
|
*
|
|
* IMP_Contents::SUMMARY_DESCRIP
|
|
* Output: parts = 'description_raw'
|
|
*
|
|
* IMP_Contents::SUMMARY_DESCRIP_LINK
|
|
* Output: parts = 'description'
|
|
*
|
|
* IMP_Contents::SUMMARY_DOWNLOAD
|
|
* Output: parts = 'download', 'download_url'
|
|
*
|
|
* IMP_Contents::SUMMARY_DOWNLOAD_ZIP
|
|
* Output: parts = 'download_zip'
|
|
*
|
|
* IMP_Contents::SUMMARY_IMAGE_SAVE
|
|
* Output: parts = 'img_save'
|
|
*
|
|
* IMP_Contents::SUMMARY_PRINT
|
|
* IMP_Contents::SUMMARY_PRINT_STUB
|
|
* Output: parts = 'print'
|
|
*
|
|
* IMP_Contents::SUMMARY_STRIP
|
|
* Output: parts = 'strip'
|
|
* </pre>
|
|
*
|
|
* @return array An array with the requested information.
|
|
*/
|
|
public function getSummary($id, $mask = 0)
|
|
{
|
|
$autodetect_link = false;
|
|
$download_zip = (($mask & self::SUMMARY_DOWNLOAD_ZIP) && Horde_Util::extensionExists('zlib'));
|
|
$param_array = array();
|
|
|
|
$this->_buildMessage();
|
|
|
|
$part = array(
|
|
'bytes' => null,
|
|
'download' => null,
|
|
'download_url' => null,
|
|
'download_zip' => null,
|
|
'id' => $id,
|
|
'img_save' => null,
|
|
'size' => null,
|
|
'strip' => null
|
|
);
|
|
|
|
$mime_part = $this->getMIMEPart($id, array('nocontents' => true));
|
|
if (empty($mime_part)) {
|
|
throw new IMP_Exception('MIME Part not found.');
|
|
}
|
|
$mime_type = $mime_part->getType();
|
|
|
|
/* If this is an attachment that has no specific MIME type info, see
|
|
* if we can guess a rendering type. */
|
|
if (in_array($mime_type, array('application/octet-stream', 'application/base64'))) {
|
|
$mime_type = Horde_Mime_Magic::filenameToMIME($mime_part->getName());
|
|
if ($mime_type == $mime_part->getType()) {
|
|
$autodetect_link = true;
|
|
} else {
|
|
$mime_part = clone $mime_part;
|
|
$mime_part->setType($mime_type);
|
|
$param_array['ctype'] = $mime_type;
|
|
}
|
|
}
|
|
$part['type'] = $mime_type;
|
|
|
|
/* Is this part an attachment? */
|
|
$is_atc = $this->isAttachment($mime_type);
|
|
|
|
/* Get bytes/size information. */
|
|
if (($mask & self::SUMMARY_BYTES) ||
|
|
$download_zip ||
|
|
($mask & self::SUMMARY_SIZE)) {
|
|
$part['bytes'] = $size = $mime_part->getBytes();
|
|
$part['size'] = ($size > 1048576)
|
|
? sprintf(_("%s MB"), IMP::numberFormat($size / 1048576, 1))
|
|
: sprintf(_("%s KB"), max(round($size / 1024), 1));
|
|
}
|
|
|
|
/* Get part's icon. */
|
|
if (($mask & self::SUMMARY_ICON) ||
|
|
($mask & self::SUMMARY_ICON_RAW)) {
|
|
$part['icon'] = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->getIcon($mime_type);
|
|
if ($mask & self::SUMMARY_ICON) {
|
|
$part['icon'] = Horde_Themes_Image::tag($part['icon'], array(
|
|
'attr' => array(
|
|
'title' => $mime_type
|
|
)
|
|
));
|
|
}
|
|
} else {
|
|
$part['icon'] = null;
|
|
}
|
|
|
|
/* Get part's description. */
|
|
$description = $this->getPartName($mime_part, true);
|
|
|
|
if ($mask & self::SUMMARY_DESCRIP_LINK) {
|
|
if (($can_d = $this->canDisplay($mime_part, self::RENDER_FULL)) ||
|
|
$autodetect_link) {
|
|
$part['description'] = $this->linkViewJS($mime_part, 'view_attach', htmlspecialchars($description), array('jstext' => sprintf(_("View %s"), $description), 'params' => array_filter(array_merge($param_array, array(
|
|
'autodetect' => !$can_d
|
|
)))));
|
|
} else {
|
|
$part['description'] = htmlspecialchars($description);
|
|
}
|
|
}
|
|
if ($mask & self::SUMMARY_DESCRIP) {
|
|
$part['description_raw'] = $description;
|
|
}
|
|
|
|
/* Download column. */
|
|
if (($mask & self::SUMMARY_DOWNLOAD) &&
|
|
$is_atc &&
|
|
(is_null($part['bytes']) || $part['bytes'])) {
|
|
$part['download'] = $this->linkView($mime_part, 'download_attach', '', array('class' => 'iconImg downloadAtc', 'jstext' => _("Download")));
|
|
$part['download_url'] = $this->urlView($mime_part, 'download_attach');
|
|
}
|
|
|
|
/* Display the compressed download link only if size is greater
|
|
* than 200 KB. */
|
|
if ($is_atc &&
|
|
$download_zip &&
|
|
($part['bytes'] > 204800)) {
|
|
$viewer = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->create($mime_part, array('contents' => $this, 'type' => $mime_type));
|
|
if (!$viewer->getMetadata('compressed')) {
|
|
$part['download_zip'] = $this->linkView($mime_part, 'download_attach', null, array('class' => 'iconImg downloadZipAtc', 'jstext' => sprintf(_("Download %s in .zip Format"), $description), 'params' => array('zip' => 1)));
|
|
}
|
|
}
|
|
|
|
/* Display the image save link if the required registry calls are
|
|
* present. */
|
|
if (($mask & self::SUMMARY_IMAGE_SAVE) &&
|
|
$GLOBALS['registry']->hasMethod('images/selectGalleries') &&
|
|
($mime_part->getPrimaryType() == 'image')) {
|
|
$part['img_save'] = Horde::link('#', _("Save Image in Gallery"), 'iconImg saveImgAtc', null, Horde::popupJs(IMP_Basic_Saveimage::url(), array('params' => array('muid' => strval($this->getIndicesOb()), 'id' => $id), 'height' => 200, 'width' => 450, 'urlencode' => true)) . 'return false;') . '</a>';
|
|
}
|
|
|
|
/* Add print link? */
|
|
if ((($mask & self::SUMMARY_PRINT) ||
|
|
($mask & self::SUMMARY_PRINT_STUB)) &&
|
|
$this->canDisplay($id, self::RENDER_FULL)) {
|
|
$part['print'] = ($mask & self::SUMMARY_PRINT)
|
|
? $this->linkViewJS($mime_part, 'print_attach', '', array('css' => 'iconImg printAtc', 'jstext' => _("Print"), 'onload' => 'IMP_JS.printWindow', 'params' => $param_array))
|
|
: Horde::link('#', _("Print"), 'iconImg printAtc', null, null, null, null, array('mimeid' => $id)) . '</a>';
|
|
}
|
|
|
|
/* Strip Attachment? Allow stripping of base parts other than the
|
|
* base multipart and the base text (body) part. */
|
|
if (($mask & self::SUMMARY_STRIP) &&
|
|
($id != 0) &&
|
|
(intval($id) != 1) &&
|
|
(strpos($id, '.') === false)) {
|
|
$part['strip'] = Horde::link(
|
|
Horde::selfUrlParams()->add(array(
|
|
'actionID' => 'strip_attachment',
|
|
'imapid' => $id,
|
|
'muid' => strval($this->getIndicesOb()),
|
|
'token' => $GLOBALS['session']->getToken()
|
|
)),
|
|
_("Strip Attachment"),
|
|
'iconImg deleteImg stripAtc',
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
array('mimeid' => $id)
|
|
) . '</a>';
|
|
}
|
|
|
|
return $part;
|
|
}
|
|
|
|
/**
|
|
* Return the URL to the download/view page.
|
|
*
|
|
* @param Horde_Mime_Part $mime_part The MIME part to view.
|
|
* @param integer $actionID The actionID to perform.
|
|
* @param array $options Additional options:
|
|
* - params: (array) A list of any additional parameters that need to be
|
|
* passed to the download/view page (key => name).
|
|
*
|
|
* @return Horde_Url The URL to the download/view page.
|
|
*/
|
|
public function urlView($mime_part = null, $actionID = 'view_attach',
|
|
array $options = array())
|
|
{
|
|
$params = $this->_urlViewParams($mime_part, $actionID, isset($options['params']) ? $options['params'] : array());
|
|
|
|
return (strpos($actionID, 'download_') === 0)
|
|
? IMP_Contents_View::downloadUrl($mime_part->getName(true), $params)
|
|
: Horde::url('view.php', true)->add($params);
|
|
}
|
|
|
|
/**
|
|
* Generates the necessary URL parameters for the download/view page.
|
|
*
|
|
* @param Horde_Mime_Part $mime_part The MIME part to view.
|
|
* @param integer $actionID The actionID to perform.
|
|
* @param array $params Additional parameters to pass.
|
|
*
|
|
* @return array The array of parameters.
|
|
*/
|
|
protected function _urlViewParams($mime_part, $actionID, $params)
|
|
{
|
|
/* Add the necessary local parameters. */
|
|
$params = array_merge($params, array(
|
|
'actionID' => $actionID,
|
|
'id' => isset($params['id']) ? $params['id'] : $mime_part->getMIMEId()
|
|
));
|
|
|
|
if ($this->_indices) {
|
|
$params['muid'] = strval($this->getIndicesOb());
|
|
}
|
|
|
|
return IMP_Contents_View::addToken($params);
|
|
}
|
|
|
|
/**
|
|
* Generate a link to the download/view page.
|
|
*
|
|
* @param Horde_Mime_Part $mime_part The MIME part to view.
|
|
* @param integer $actionID The actionID value.
|
|
* @param string $text The ESCAPED (!) link text.
|
|
* @param array $options Additional parameters:
|
|
* - class: (string) The CSS class to use.
|
|
* - jstext: (string) The JS text to use.
|
|
* - params: (array) A list of any additional parameters that need to be
|
|
* passed to the download/view page.
|
|
*
|
|
* @return string A HTML href link to the download/view page.
|
|
*/
|
|
public function linkView($mime_part, $actionID, $text, $options = array())
|
|
{
|
|
$options = array_merge(array(
|
|
'class' => null,
|
|
'jstext' => $text,
|
|
'params' => array()
|
|
), $options);
|
|
|
|
return Horde::link(
|
|
$this->urlView($mime_part, $actionID, $options),
|
|
$options['jstext'],
|
|
$options['class'],
|
|
($actionID == 'download_attach') ? null : strval(new Horde_Support_Randomid())
|
|
) . $text . '</a>';
|
|
}
|
|
|
|
/**
|
|
* Generate a javascript link to the download/view page.
|
|
*
|
|
* @param Horde_Mime_Part $mime_part The MIME part to view.
|
|
* @param string $actionID The actionID to perform.
|
|
* @param string $text The ESCAPED (!) link text.
|
|
* @param array $options Additional options:
|
|
* - css: (string) The CSS class to use.
|
|
* - jstext: (string) The javascript link text.
|
|
* - onload: (string) A JS function to run when popup window is
|
|
* fully loaded.
|
|
* - params: (array) A list of any additional parameters that need to be
|
|
* passed to download/view page. (key = name)
|
|
* - widget: (boolean) If true use Horde::widget() to generate,
|
|
* Horde::link() otherwise.
|
|
*
|
|
* @return string A HTML href link to the download/view page.
|
|
*/
|
|
public function linkViewJS($mime_part, $actionID, $text,
|
|
$options = array())
|
|
{
|
|
if (empty($options['params'])) {
|
|
$options['params'] = array();
|
|
}
|
|
|
|
if (empty($options['jstext'])) {
|
|
$options['jstext'] = sprintf(_("View %s"), $mime_part->getDescription(true));
|
|
}
|
|
|
|
$url = Horde::popupJs(Horde::url('view.php'), array(
|
|
'menu' => true,
|
|
'onload' => empty($options['onload']) ? 'IMP_JS.resizePopup' : $options['onload'],
|
|
'params' => $this->_urlViewParams($mime_part, $actionID, isset($options['params']) ? $options['params'] : array()),
|
|
'urlencode' => true
|
|
));
|
|
|
|
return empty($options['widget'])
|
|
? Horde::link('#', $options['jstext'], empty($options['css']) ? null : $options['css'], null, $url) . $text . '</a>'
|
|
: Horde::widget(array('url' => '#', 'class' => empty($options['css']) ? null : $options['css'], 'onclick' => $url, 'title' => $text));
|
|
}
|
|
|
|
/**
|
|
* Determines if a MIME type is an attachment.
|
|
* For IMP's purposes, an attachment is any MIME part that can be
|
|
* downloaded by itself (i.e. all the data needed to view the part is
|
|
* contained within the download data).
|
|
*
|
|
* @param string $mime_type The MIME type.
|
|
*
|
|
* @return boolean True if an attachment.
|
|
*/
|
|
public function isAttachment($mime_type)
|
|
{
|
|
switch ($mime_type) {
|
|
case 'application/ms-tnef':
|
|
return false;
|
|
}
|
|
|
|
list($ptype,) = explode('/', $mime_type, 2);
|
|
|
|
switch ($ptype) {
|
|
case 'message':
|
|
return in_array($mime_type, array('message/rfc822', 'message/disposition-notification'));
|
|
|
|
case 'multipart':
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds the "virtual" Horde_Mime_Part object by checking for embedded
|
|
* parts.
|
|
*
|
|
* @param array $parts The parts list to process.
|
|
*/
|
|
protected function _buildMessage($parts = null)
|
|
{
|
|
global $injector;
|
|
|
|
if (is_null($parts)) {
|
|
if ($this->_build) {
|
|
return;
|
|
}
|
|
$this->_build = true;
|
|
$parts = array_keys($this->_message->contentTypeMap());
|
|
$first_id = reset($parts);
|
|
} else {
|
|
$first_id = null;
|
|
}
|
|
|
|
$last_id = null;
|
|
$to_process = array();
|
|
|
|
$mv_factory = $injector->getInstance('IMP_Factory_MimeViewer');
|
|
|
|
foreach ($parts as $id) {
|
|
if (!is_null($last_id) &&
|
|
(strpos($id, $last_id) === 0)) {
|
|
continue;
|
|
}
|
|
|
|
$last_id = null;
|
|
|
|
$mime_part = $this->getMIMEPart($id, array('nocontents' => true));
|
|
$viewer = $mv_factory->create($mime_part, array('contents' => $this));
|
|
if ($viewer->embeddedMimeParts()) {
|
|
$mime_part = $this->getMIMEPart($id);
|
|
$viewer->setMIMEPart($mime_part);
|
|
$new_part = $viewer->getEmbeddedMimeParts();
|
|
if (!is_null($new_part)) {
|
|
$mime_part->addPart($new_part);
|
|
$mime_part->buildMimeIds($id);
|
|
$this->_embedded[] = $new_part->getMimeId();
|
|
$to_process = array_merge($to_process, array_keys($new_part->contentTypeMap()));
|
|
$last_id = $id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($to_process)) {
|
|
$this->_buildMessage($to_process);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Can this MIME part be displayed in the given mode?
|
|
*
|
|
* @param mixed $part The MIME part or a MIME ID string.
|
|
* @param integer $mask One of the RENDER_ constants.
|
|
* @param string $type The type to use (overrides the MIME ID if $id is
|
|
* a MIME part).
|
|
*
|
|
* @return integer The RENDER_ constant of the allowable display.
|
|
*/
|
|
public function canDisplay($part, $mask, $type = null)
|
|
{
|
|
if (!is_object($part)) {
|
|
$part = $this->getMIMEPart($part, array('nocontents' => true));
|
|
}
|
|
if (!$part) {
|
|
return 0;
|
|
}
|
|
$viewer = $GLOBALS['injector']->getInstance('IMP_Factory_MimeViewer')->create($part, array('contents' => $this, 'type' => $type));
|
|
|
|
if ($mask & self::RENDER_INLINE_AUTO) {
|
|
$mask |= self::RENDER_INLINE | self::RENDER_INFO;
|
|
}
|
|
|
|
if (($mask & self::RENDER_RAW) && $viewer->canRender('raw')) {
|
|
return self::RENDER_RAW;
|
|
}
|
|
|
|
if (($mask & self::RENDER_FULL) && $viewer->canRender('full')) {
|
|
return self::RENDER_FULL;
|
|
}
|
|
|
|
if ($mask & self::RENDER_INLINE) {
|
|
if ($viewer->canRender('inline')) {
|
|
return self::RENDER_INLINE;
|
|
}
|
|
} elseif (($mask & self::RENDER_INLINE_DISP_NO) &&
|
|
$viewer->canRender('inline')) {
|
|
return self::RENDER_INLINE_DISP_NO;
|
|
}
|
|
|
|
if (($mask & self::RENDER_INFO) && $viewer->canRender('info')) {
|
|
return self::RENDER_INFO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the Content-Type map for the entire message, regenerating
|
|
* embedded parts if needed.
|
|
*
|
|
* @return array See Horde_Mime_Part::contentTypeMap().
|
|
*/
|
|
public function getContentTypeMap()
|
|
{
|
|
$this->_buildMessage();
|
|
return $this->_message->contentTypeMap();
|
|
}
|
|
|
|
/**
|
|
* Returns the MIME part tree of the message.
|
|
*
|
|
* @param string $renderer Either the tree renderer driver or a full
|
|
* class name to use.
|
|
*
|
|
* @return Horde_Tree_Renderer_Base A tree instance representing the MIME parts.
|
|
* @throws Horde_Tree_Exception
|
|
*/
|
|
public function getTree($renderer = 'Horde_Core_Tree_Renderer_Html')
|
|
{
|
|
$tree = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Tree')->create('mime-' . $this->getUid(), $renderer, array(
|
|
'nosession' => true
|
|
));
|
|
$this->_addTreeNodes($tree, $this->_message);
|
|
return $tree;
|
|
}
|
|
|
|
/**
|
|
* Adds MIME parts to the tree instance.
|
|
*
|
|
* @param Horde_Tree_Renderer_Base tree A tree instance.
|
|
* @param Horde_Mime_Part $part The MIME part to add to the
|
|
* tree, including its sub-parts.
|
|
* @param string $parent The parent part's MIME id.
|
|
*/
|
|
protected function _addTreeNodes($tree, $part, $parent = null)
|
|
{
|
|
$mimeid = $part->getMimeId();
|
|
|
|
$summary_mask = self::SUMMARY_ICON_RAW | self::SUMMARY_DESCRIP_LINK | self::SUMMARY_SIZE | self::SUMMARY_DOWNLOAD;
|
|
if ($GLOBALS['prefs']->getValue('strip_attachments')) {
|
|
$summary_mask += self::SUMMARY_STRIP;
|
|
}
|
|
|
|
$summary = $this->getSummary($mimeid, $summary_mask);
|
|
|
|
$tree->addNode(array(
|
|
'id' => $mimeid,
|
|
'parent' => $parent,
|
|
'label' => sprintf(
|
|
'%s (%s) %s %s',
|
|
$summary['description'],
|
|
$summary['size'],
|
|
$summary['download'],
|
|
$summary['strip']
|
|
),
|
|
'params' => array(
|
|
'class' => 'partsTreeDiv',
|
|
'icon' => $summary['icon']
|
|
)
|
|
));
|
|
|
|
foreach ($part->getParts() as $part) {
|
|
$this->_addTreeNodes($tree, $part, $mimeid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get download all list.
|
|
*
|
|
* @return array An array of downloadable parts.
|
|
*/
|
|
public function downloadAllList()
|
|
{
|
|
$ret = array();
|
|
|
|
foreach ($this->getContentTypeMap() as $key => $val) {
|
|
if ($this->isAttachment($val)) {
|
|
$ret[] = $key;
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Injects body contents into the base Horde_Mime_part object.
|
|
*
|
|
* @param array $ignore A list of MIME IDs to ignore.
|
|
*
|
|
* @return Horde_Mime_Part The part with body contents set.
|
|
*/
|
|
public function buildMessageContents($ignore = array())
|
|
{
|
|
$message = $this->_message;
|
|
$curr_ignore = null;
|
|
|
|
foreach ($message->contentTypeMap() as $key => $val) {
|
|
if (is_null($curr_ignore) && in_array($key, $ignore)) {
|
|
$curr_ignore = $key . '.';
|
|
} elseif (is_null($curr_ignore) ||
|
|
(strpos($key, $curr_ignore) === false)) {
|
|
$curr_ignore = null;
|
|
if (($key != 0) &&
|
|
($val != 'message/rfc822') &&
|
|
(strpos($val, 'multipart/') === false)) {
|
|
$part = $this->getMIMEPart($key);
|
|
$message->alterPart($key, $part);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
/**
|
|
* Determines if a given MIME part ID is a part of embedded data.
|
|
*
|
|
* @param string $mime_id The MIME ID.
|
|
*
|
|
* @return boolean True if the MIME ID is part of embedded data.
|
|
*/
|
|
public function isEmbedded($mime_id)
|
|
{
|
|
foreach ($this->_embedded as $val) {
|
|
if (($mime_id == $val) || Horde_Mime::isChild($val, $mime_id)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Find a MIME type in parent parts.
|
|
*
|
|
* @param string $id The MIME ID to begin the search at.
|
|
* @param string $type The MIME type to search for.
|
|
*
|
|
* @return mixed Either the requested MIME part, or null if not found.
|
|
*/
|
|
public function findMimeType($id, $type)
|
|
{
|
|
$id = Horde_Mime::mimeIdArithmetic($id, 'up');
|
|
|
|
while (!is_null($id)) {
|
|
if (($part = $this->getMIMEPart($id, array('nocontents' => true))) &&
|
|
($part->getType() == $type)) {
|
|
return $part;
|
|
}
|
|
$id = Horde_Mime::mimeIdArithmetic($id, 'up');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return the descriptive part label, making sure it is not empty.
|
|
*
|
|
* @param Horde_Mime_Part $part The MIME Part object.
|
|
* @param boolean $use_descrip Use description? If false, uses name.
|
|
*
|
|
* @return string The part label (non-empty).
|
|
*/
|
|
public function getPartName(Horde_Mime_Part $part, $use_descrip = false)
|
|
{
|
|
$name = $use_descrip
|
|
? $part->getDescription(true)
|
|
: $part->getName(true);
|
|
|
|
if ($name) {
|
|
return $name;
|
|
}
|
|
|
|
switch ($ptype = $part->getPrimaryType()) {
|
|
case 'multipart':
|
|
if (($part->getSubType() == 'related') &&
|
|
($view_id = $part->getMetaData('viewable_part')) &&
|
|
($viewable = $this->getMIMEPart($view_id, array('nocontents' => true)))) {
|
|
return $this->getPartName($viewable, $use_descrip);
|
|
}
|
|
/* Fall-through. */
|
|
|
|
case 'application':
|
|
case 'model':
|
|
$ptype = $part->getSubType();
|
|
break;
|
|
}
|
|
|
|
switch ($ptype) {
|
|
case 'audio':
|
|
return _("Audio");
|
|
|
|
case 'image':
|
|
return _("Image");
|
|
|
|
case 'message':
|
|
case '':
|
|
case Horde_Mime_Part::UNKNOWN:
|
|
return _("Message");
|
|
|
|
case 'multipart':
|
|
return _("Multipart");
|
|
|
|
case 'text':
|
|
return _("Text");
|
|
|
|
case 'video':
|
|
return _("Video");
|
|
|
|
default:
|
|
// Attempt to translate this type, if possible. Odds are that
|
|
// it won't appear in the dictionary though.
|
|
return _(Horde_String::ucfirst($ptype));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Prints out a MIME summary (in HTML).
|
|
*
|
|
* @param string $id The MIME ID.
|
|
* @param integer $mask A bitmask indicating what summary information to
|
|
* return.
|
|
* @param array $display The fields to display (in this order).
|
|
* @param boolean $atc Is this an attachment?
|
|
*
|
|
* @return string The formatted summary string.
|
|
*/
|
|
protected function _formatSummary($id, $mask, $display, $atc = false)
|
|
{
|
|
$summary = $this->getSummary($id, $mask);
|
|
$tmp_summary = array();
|
|
|
|
foreach ($display as $val) {
|
|
if (isset($summary[$val])) {
|
|
switch ($val) {
|
|
case 'description':
|
|
$summary[$val] = '<span class="mimePartInfoDescrip">' . $summary[$val] . '</span>';
|
|
break;
|
|
|
|
case 'size':
|
|
$summary[$val] = '<span class="mimePartInfoSize">(' . $summary[$val] . ')</span>';
|
|
break;
|
|
}
|
|
$tmp_summary[] = $summary[$val];
|
|
}
|
|
}
|
|
|
|
return '<div class="mimePartInfo' .
|
|
($atc ? ' mimePartInfoAtc' : '') .
|
|
'"><div>' .
|
|
implode(' ', $tmp_summary) .
|
|
'</div></div>';
|
|
}
|
|
|
|
/**
|
|
* Get FETCH data from IMAP server for this message.
|
|
*
|
|
* @param Horde_Imap_Client_Fetch_Query $query Search query.
|
|
*
|
|
* @return Horde_Imap_Client_Data_Fetch Fetch data for the message.
|
|
*/
|
|
protected function _fetchData(Horde_Imap_Client_Fetch_Query $query)
|
|
{
|
|
try {
|
|
$mbox = $this->getMailbox();
|
|
$imp_imap = $mbox->imp_imap;
|
|
return $imp_imap->fetch($mbox, $query, array(
|
|
'ids' => $imp_imap->getIdsOb($this->getUid())
|
|
))->first();
|
|
} catch (Horde_Imap_Client_Exception $e) {
|
|
return new Horde_Imap_Client_Data_Fetch();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the view cache object for this message.
|
|
*
|
|
* @return object View object.
|
|
*/
|
|
public function getViewCache()
|
|
{
|
|
if (!isset($this->_viewcache)) {
|
|
$this->_viewcache = new stdClass;
|
|
}
|
|
|
|
return $this->_viewcache;
|
|
}
|
|
|
|
}
|