<?php

require_once 'Horde/MIME/Contents.php';
require_once IMP_BASE . '/config/mime_drivers.php';

/**
 * The IMP_Contents:: class extends the MIME_Contents:: class and contains
 * all functions related to handling the content and output of mail messages
 * in IMP.
 *
 * $Horde: imp/lib/MIME/Contents.php,v 1.153.4.18 2005/09/26 22:04:26 jan Exp $
 *
 * Copyright 2002-2005 Michael Slusarz <slusarz@bigworm.colorado.edu>
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * @author  Michael Slusarz <slusarz@bigworm.colorado.edu>
 * @since   IMP 4.0
 * @package IMP
 */
class IMP_Contents extends MIME_Contents {

    /**
     * The text of the body of the message.
     *
     * @var string
     */
    var $_body = '';

    /**
     * The text of various MIME body parts.
     *
     * @var array
     */
    var $_bodypart = array();

    /**
     * The IMAP index of the message.
     *
     * @var integer
     */
    var $_index;

    /**
     * Should attachment stripping links be generated?
     *
     * @var boolean
     */
    var $_strip = false;

    /**
     * Attempts to return a reference to a concrete IMP_Contents instance.
     * If an IMP_Contents object is currently stored in the local cache,
     * recreate that object.  Else, create a new instance.
     * Ensures that only one IMP_Contents instance for any given message is
     * available at any one time.
     *
     * This method must be invoked as:
     *   $imp_contents = &IMP_Contents::singleton($index);
     *
     * @param integer $index  The IMAP message index.
     *
     * @return IMP_Contents  The IMP_Contents object or null.
     */
    function &singleton($index)
    {
        static $instance = array();

        $signature = IMP_Contents::_createCacheID($index);
        if (isset($instance[$signature])) {
            return $instance[$signature];
        }

        $instance[$signature] = &IMP_Contents::getCache($signature);
        if (empty($instance[$signature])) {
            $instance[$signature] = &new IMP_Contents($index);
        }

        return $instance[$signature];
    }

    /**
     * Constructor
     *
     * @param mixed $in  Either the IMAP message index of the message to
     *                   process, or a MIME_Message object.
     */
    function IMP_Contents($in)
    {
        if (is_a($in, 'MIME_Message')) {
            $ob = $in;
        } else {
            $this->_index = $in;

            /* Get the MIME_Message object for the given index. */
            $structure = @imap_fetchstructure($_SESSION['imp']['stream'], $in, FT_UID);
            if (!is_object($structure)) {
                return;
            }
            require_once 'Horde/MIME/Structure.php';
            $ob = &MIME_Structure::parse($structure);
        }

        switch ($GLOBALS['prefs']->getValue('attachment_display')) {
        case 'list':
            $this->_displayType = MIME_CONTENTS_DISPLAY_TYPE_LIST;
            break;

        case 'inline':
            $this->_displayType = MIME_CONTENTS_DISPLAY_TYPE_INLINE;
            break;

        case 'both':
            $this->_displayType = MIME_CONTENTS_DISPLAY_TYPE_BOTH;
            break;
        }

        parent::MIME_Contents($ob, array('download' => 'download_attach', 'view' => 'view_attach'));
    }

    /**
     * Returns the entire body of the message.
     *
     * @return string  The text of the body of the message.
     */
    function getBody()
    {
        if (empty($this->_body)) {
            $this->_body = @imap_body($_SESSION['imp']['stream'], $this->_index, FT_UID);
        }

        return $this->_body;
    }

    /**
     * Gets the raw text for one section of the message.
     *
     * @param integer $id  The ID of the MIME_Part.
     *
     * @return string  The text of the part.
     */
    function getBodyPart($id)
    {
        if (!isset($this->_bodypart[$id])) {
            $this->_bodypart[$id] = @imap_fetchbody($_SESSION['imp']['stream'], $this->_index, $id, FT_UID);
        }

        return isset($this->_bodypart[$id]) ? $this->_bodypart[$id] : '';
    }

    /**
     * Allow attachments to be stripped by providing a link in summary view?
     *
     * @param boolean $strip  Should the strip links be generated?
     */
    function setStripLink($strip = false)
    {
        $this->_strip = $strip;
    }

    /**
     * Returns an array summarizing a part of a MIME message.
     *
     * @param MIME_Part &$mime_part  See MIME_Contents::partSummary().
     * @param boolean $guess         See MIME_Contents::partSummary().
     *
     * @return array  See MIME_Contents::partSummary().
     *                Adds the following key to that return:
     *                [6] = Compressed Download Link
     *                [7] = Strip Attachment Link (if allowed)
     */
    function partSummary(&$mime_part, $guess = false)
    {
        global $conf, $registry;

        $summary = parent::partSummary($mime_part, $guess);

        /* Don't add extra links if not requested or if this is a guessed
           part. */
        if ($guess || !$this->_links) {
            return $summary;
        }

        /* Display the compressed download link only if size is greater
           than 200 KB. */
        if (($mime_part->getBytes() > 204800) &&
            Util::extensionExists('zlib') &&
            ($mime_part->getType() != 'application/zip') &&
            ($mime_part->getType() != 'application/x-zip-compressed')) {
            $summary[] = $this->linkView($mime_part, 'download_attach', Horde::img('compressed.png', _("Download in .zip Format"), null, $registry->getImageDir('horde') . '/mime'), array('jstext' => sprintf(_("Download %s in .zip Format"), $mime_part->getDescription(true, true)), 'viewparams' => array('zip' => 1)), true);
        } else {
            $summary[] = null;
        }

        /* Strip the Attachment? */
        if ($this->_strip &&
            !$mime_part->getInformation('rfc822_part')) {
            $url = Horde::selfUrl(true);
            $url = Util::removeParameter($url, array('actionID', 'imapid', 'index'));
            $url = Util::addParameter($url, array('actionID' => 'strip_attachment', 'imapid' => $this->_getMIMEKey($mime_part, false), 'index' => $this->getMessageIndex()));
            $summary[] = Horde::link($url, _("Strip Attachment"), null, null, "return window.confirm('" . addslashes(_("Are you sure you wish to PERMANENTLY delete this attachment?")) . "');") . Horde::img('delete.png', _("Strip Attachment"), null, $registry->getImageDir('horde')) . '</a>';
        } else {
            $summary[] = null;
        }

        return $summary;
    }

    /**
     * Return the URL to the view.php page.
     *
     * @param MIME_Part &$mime_part  See MIME_Contents::urlView().
     * @param integer $actionID      See MIME_Contents::urlView().
     * @param array $params          See MIME_Contents::urlView().
     * @param boolean $dload         See MIME_Contents::urlView().
     * The following parameter names will be overwritten by this function:
     *   id, index, mailbox
     *
     * @return string  The URL to view.php.
     */
    function urlView(&$mime_part, $actionID, $params = array(), $dload = false)
    {
        require_once IMP_BASE . '/lib/Search.php';

        /* Add the necessary local parameters. */
        $params = array_merge($params, IMP_Search::getSearchParameters(IMP::getThisMailbox()));
        $params['index'] = $this->_index;
        if (!isset($params['mailbox'])) {
            $params['mailbox'] = $_SESSION['imp']['mailbox'];
        }

        /* Should this be a download link? */
        $dload = (($actionID == 'download_attach') ||
                  ($actionID == 'download_render') ||
                  ($actionID == 'save_message'));

        return parent::urlView($mime_part, $actionID, $params, $dload);
    }

    /**
     * Generate a link to the view.php page.
     *
     * @param MIME_Part &$mime_part  See MIME_Contents::linkView().
     * @param integer $actionID      See MIME_Contents::linkView().
     * @param string $text           See MIME_Contents::linkView().
     * @param array $params          See MIME_Contents::linkView().
     *
     * @return string  See MIME_Contents::linkView().
     */
    function linkView(&$mime_part, $actionID, $text, $params = array())
    {
        if ($mime_part->getInformation('actionID')) {
            $actionID = $mime_part->getInformation('actionID');
        }

        /* If this is a 'download_attach or 'download_render' link, we do not
           want to show in a new window. */
        $dload = (($actionID == 'download_attach') ||
                  ($actionID == 'download_render'));

        /* If download attachment, add the 'thismailbox' param. */
        if ($actionID == 'download_attach') {
            $params['viewparams']['thismailbox'] = IMP::getThisMailbox();
        }

        if ($mime_part->getInformation('viewparams')) {
            foreach ($mime_part->getInformation('viewparams') as $key => $val) {
                $params['viewparams'][$key] = $val;
            }
        }

        return parent::linkView($mime_part, $actionID, $text, $params, $dload);
    }

    /**
     * Returns the full message text for a given message/mailbox.
     *
     * @param integer $index  The index/mailbox of the message. If empty, will
     *                        use the current message index.
     *
     * @return string  The full message text.
     */
    function fullMessageText($index = null)
    {
        $imp_headers = &$this->getHeaderOb($index);
        return $imp_headers->getHeaderText() . $this->getBody();
    }

    /**
     * Returns the header object for the given message.
     *
     * @param integer $index  The index/mailbox of the message. If empty, will
     *                        use the current message index.
     *
     * @return IMP_Headers  The IMP_Headers object.
     */
    function &getHeaderOb($index = null)
    {
        if (is_null($index)) {
            $index = $this->_index;
        } elseif ($index != $this->_index) {
            if (strstr($index, IMP_IDX_SEP)) {
                require_once IMP_BASE . '/lib/base.php';
                $imp_imap = &IMP_IMAP::singleton();
                list($index, $mailbox) = explode(IMP_IDX_SEP, $index);
                $imp_imap->changeMbox($mailbox);
            }
            if ($index != $this->_index) {
                $this->_body = '';
                $this->_index = $index;
            }
        }

        require_once IMP_BASE . '/lib/MIME/Headers.php';
        $imp_headers = &new IMP_Headers($index);

        return $imp_headers;
    }

    /**
     * Returns the IMAP index for the current message.
     *
     * @return integer  The message index.
     */
    function getMessageIndex()
    {
        return $this->_index;
    }

    /**
     * Rebuild the MIME_Part structure of a message from IMAP data.
     *
     * @return MIME_Message  A MIME_Message object with all of the body text
     *                       stored in the individual MIME_Parts.
     */
    function rebuildMessage()
    {
        $part = $this->_message->getBasePart();
        $this->_rebuildMessage($part);
        return $this->_message;
    }

    /**
     * Recursive function used to rebuild the MIME_Part structure of a
     * message.
     *
     * @access private
     *
     * @param MIME_Part &$part  A MIME_Part object.
     */
    function _rebuildMessage(&$part)
    {
        $id = $part->getMIMEId();

        if ($part->getPrimaryType() == 'multipart') {
            /* Recursively process any subparts. */
            foreach ($part->getParts() as $mime) {
                $this->_rebuildMessage($mime);
            }
        } elseif ($part->getType() == 'message/rfc822') {
            $part->setContents($this->getBodyPart(substr($id, 0, strrpos($id, '.'))));
            $this->_message->alterPart($id, $part);
        } else {
            $mime_part = &$this->getMIMEPart($id);
            $mime_part->setContents($this->getBodyPart($id));
            if ($this->_message->getPart($id) === $this->_message) {
                $this->_message = &$mime_part;
            } else {
                $this->_message->alterPart($id, $mime_part);
            }
        }
    }

    /**
     * Render a MIME Part.
     *
     * @param MIME_Part &$mime_part  A MIME_Part object.
     *
     * @return string  The rendered data.
     */
    function renderMIMEPart(&$mime_part)
    {
        if (!$mime_part->getContents()) {
            $mime_part->setContents($this->getBodyPart($mime_part->getMIMEId()));
        }

        $text = parent::renderMIMEPart($mime_part);

        /* Convert textual emoticons into graphical ones - but only for
         * text parts. */
        if (($mime_part->getPrimaryType() == 'text') &&
            $GLOBALS['prefs']->getValue('emoticons')) {
            require_once 'Horde/Text/Filter.php';
            $text = Text_Filter::filter($text, 'emoticons', array(true));
        }

        return $text;
    }

    /**
     * Saves a copy of the MIME_Contents object at the end of a request.
     *
     * @access private
     */
    function _addCacheShutdown()
    {
        /* Don't save the cached $_body or $_bodypart arrays since we most
           likely will not use them in the cached object (since the cache
           will generally be used to view parts that _weren't* displayed
           originally. */
        $this->_body = null;
        $this->_bodypart = array();

        parent::_addCacheShutdown();
    }

    /**
     * Get the from address of the message.
     *
     * @return string  The from address of the message.
     */
    function getFromAddress()
    {
        require_once IMP_BASE . '/lib/MIME/Headers.php';
        $headers = &new IMP_Headers($this->getMessageIndex());
        return $headers->getFromAddress();
    }

    /**
     * Generate the list of MIME IDs to use for download all.
     *
     * @param boolean $forward  Generate a list of items that can be forwarded,
     *                          rather than downloaded.
     *
     * @return array  The list of MIME IDs that should be downloaded when
     *                downloading all attachments.
     */
    function getDownloadAllList($forward = false)
    {
        $downloads = array();
        $rfc822 = null;

        /* Here is what we don't consider 'downloadable':
           Any 'multipart/*' part
           All 'message/*' parts except for 'message/rfc822'
           The first viewable part
           PGP or S/MIME signature information */
        foreach ($this->_message->contentTypeMap() as $key => $val) {
            if (($key == 0) && (strpos($val, 'multipart/') === false)) {
                break;
            }

            if ($forward && !is_null($rfc822)) {
                if (strpos($key, $rfc822) === 0) {
                    continue;
                } else {
                    $rfc822 = null;
                }
            }

            if (strpos($val, 'message/') === 0) {
                /* If we are forwarding, only forward the message/rfc822 - not
                 * its constituent parts. */
                if (strpos($val, 'message/rfc822') === 0) {
                    if ($forward) {
                        $rfc822 = substr($key, 0, strrpos($key, '.'));
                    }
                    $downloads[] = $key;
                }
            } elseif (($forward ||
                       (intval($key) != 1) && (strpos($val, '1.') === false)) &&
                      ((strpos($val, 'multipart/') === false) ||
                       ($forward && (strpos($val, 'multipart/related') === 0))) &&
                      (strpos($val, 'application/x-pkcs7-signature') === false) &&
                      (strpos($val, 'application/pkcs7-signature') === false)) {
                $downloads[] = $key;
            }
        }

        return $downloads;
    }

    /**
     * Generate a download all link, if possible.
     *
     * @return string  The download link.
     */
    function getDownloadAllLink()
    {
        $url = null;

        $downloads_list = $this->getDownloadAllList();
        if (!empty($downloads_list)) {
            /* Create a dummy variable to pass to urlView() since we don't
               have a MIME_Part and we can't pass null by reference. */
            $dummy = 0;
            $url = $this->urlView($dummy, 'download_all', array('id' => 1));
            $url = Util::removeParameter($url, array('id'));
        }

        return $url;
    }

    /**
     * Prints out the status message for a given MIME Part.
     *
     * @param mixed $msg      See MIME_Contents::formatStatusMsg().
     * @param string $img     See MIME_Contents::formatStatusMsg().
     * @param boolean $print  Output this message when in a print view?
     *
     * @return string  The formatted status message.
     */
    function formatStatusMsg($msg, $img = null, $printable = true)
    {
        if (!$printable && IMP::printMode()) {
            return '';
        } else {
            return parent::formatStatusMsg($msg, $img);
        }
    }

    /**
     * Finds the main "body" text part (if any) in a message.
     * "Body" data is the first text part in the base MIME part.
     *
     * @return string  The MIME ID of the main "body" part.
     */
    function findBody()
    {
        /* Look for potential body parts. */
        $part = $this->_message->getBasePart();
        $primary_type = $part->getPrimaryType();
        if (($primary_type == MIME::type(TYPEMULTIPART)) ||
            ($primary_type == MIME::type(TYPETEXT))) {
            return $this->_findBody($part);
        }

        return null;
    }

    /**
     * Processes a MIME Part and looks for "body" data.
     *
     * @access private
     *
     * @return string  The MIME ID of the main "body" part.
     */
    function _findBody(&$mime_part)
    {
        if (intval($mime_part->getMIMEId()) < 2 ||
            $mime_part->getInformation('alternative') === 0) {
            if ($mime_part->getPrimaryType() == MIME::type(TYPEMULTIPART)) {
                foreach ($mime_part->getParts() as $part) {
                    if (($partid = $this->_findBody($part))) {
                        return $partid;
                    }
                }
            } elseif ($mime_part->getPrimaryType() == MIME::type(TYPETEXT)) {
                if ($mime_part->getBytes() ||
                    $this->getBodyPart($mime_part->getMIMEId())) {
                    return $mime_part->getMIMEId();
                }
            }
        }

        return null;
    }

    /**
     * Creates a unique cache ID for this object.
     *
     * @access private
     *
     * @param integer $index  The IMAP index of the current message.
     *
     * @return string  A unique cache ID.
     */
    function _createCacheID($index = null)
    {
        if (is_null($index)) {
            if (isset($this)) {
                $index = $this->_index;
            } else {
                return parent::_createCacheID();
            }
        }
        return md5(implode('|', array($index, $_SESSION['imp']['thismailbox'])));
    }

}
