<?php

require_once HORDE_BASE . '/lib/MIME/Headers.php';
require_once IMP_BASE . '/lib/version.php';

/** @constant IMP_AGENT_HEADER The description of the IMP program to use in the 'User-Agent:' header. */
define('IMP_AGENT_HEADER', 'Internet Messaging Program (IMP) ' . IMP_VERSION);

/**
 * The IMP_Headers:: class contains all functions related to handling
 * the headers of mail messages in IMP.
 *
 * $Horde: imp/lib/Headers.php,v 1.69 2003/07/06 00:03:58 slusarz Exp $
 *
 * Copyright 2002-2003 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>
 * @version $Revision: 1.69 $
 * @since   IMP 4.0
 * @package imp
 */
class IMP_Headers extends MIME_Headers {

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

    /**
     * Cached output of the MIME_Structure::parseMIMEHeaders() command.
     *
     * @var array $_allHeaders
     */
    var $_allHeaders;

    /**
     * Cached output of the imap_fetchheader() command.
     *
     * @var string $_headerText
     */
    var $_headerText;

    /**
     * The header object returned from imap_headerinfo().
     *
     * @var stdClass $_headerObject
     */
    var $_headerObject;

    /**
     * The internal flags array.
     *
     * @var array $_flags
     */
    var $_flags = array();

    /**
     * Constructor
     *
     * @access public
     *
     * @param optional integer $index  The IMAP message index.
     */
    function IMP_Headers($index = null)
    {
        $this->_index = $index;
        $this->_agent = IMP_AGENT_HEADER;
    }

    /**
     * Return the full list of headers from the imap_fetchheader() function.
     *
     * @access public
     *
     * @return string  See imap_fetchheader().
     */
    function getHeaderText()
    {
        global $imp;

        if (empty($this->_headerText)) {
            $this->_headerText = @imap_fetchheader($imp['stream'], $this->_index, FT_UID);
        }

        return $this->_headerText;
    }

    /**
     * Return the full list of headers.
     *
     * @access public
     *
     * @param optional boolean $decode  Decode the headers?
     *
     * @return array  See MIME_Structure::parseMIMEHeaders().
     */
    function getAllHeaders($decode = true)
    {
        require_once HORDE_BASE . '/lib/MIME/Structure.php';

        if (empty($this->_allHeaders)) {
            $this->_allHeaders = MIME_Structure::parseMIMEHeaders($this->getHeaderText(), $decode);
        }

        return $this->_allHeaders;
    }

    /**
     * Return the header object from imap_headerinfo().
     *
     * @access public
     *
     * @return object stdClass  See imap_headerinfo().
     */
    function getHeaderObject()
    {
        global $imp;

        if (empty($this->_headerObject)) {
            $this->_headerObject = @imap_headerinfo($imp['stream'], @imap_msgno($imp['stream'], $this->_index));
        }

        return $this->_headerObject;
    }

    /**
     * Build the header array. The headers are MIME decoded.
     *
     * @access public
     */
    function buildHeaders()
    {
        if (!empty($this->_headers)) {
            return;
        }

        /* Parse through the list of all headers. */
        foreach ($this->getAllHeaders() as $key => $val) {
            $this->addHeader($key, $val);
        }
    }

    /**
     * Build the flags array.
     *
     * @access public
     */
    function buildFlags()
    {
        if (!empty($this->_flags)) {
            return;
        }

        /* Get the IMAP header object. */
        $ob = $this->getHeaderObject();

        /* Unseen flag */
        if (($ob->Unseen == 'U') || ($ob->Recent == 'N')) {
            $this->_flags['unseen'] = true;
        }

        /* Answered flag */
        if ($ob->Answered == 'A') {
            $this->_flags['answered'] = true;
        }

        /* Draft flag */
        if (isset($ob->Draft) && ($ob->Draft == 'X')) {
            $this->_flags['draft'] = true;
        }

        /* Important flag */
        if ($ob->Flagged == 'F') {
            $this->_flags['important'] = true;
        }

        /* Deleted flag */
        if ($ob->Deleted == 'D') {
            $this->_flags['deleted'] = true;
        }
    }

    /**
     * Parse all of the available mailing list headers.
     *
     * @access public
     */
    function parseAllListHeaders()
    {
        foreach ($this->listHeaders() as $val => $str) {
            $this->parseListHeaders($val);
        }
    }

    /**
     * Parse the information in the mailing list headers.
     *
     * @param string $header         The header to process.
     * @param optional boolean $raw  Should the raw URL be returned instead
     *                               of setting the header value?
     *
     * @return string  The header value (if $raw == true).
     */
    function parseListHeaders($header, $raw = false)
    {
        if (!($data = $this->getValue($header))) {
            return;
        }

        $output = '';

        require_once HORDE_BASE . '/lib/Text.php';

        /* Split the incoming data by the ',' character. */
        foreach (preg_split("/,/", $data) as $entry) {
            /* Get the data inside of the brackets. If there is no brackets,
               then return the raw text. */
            if (!preg_match("/\<([^\>]+)\>/", $entry, $matches)) {
                return trim($entry);
            }

            /* Remove all whitespace from between brackets (RFC 2369 [2]). */
            $match = preg_replace("/\s+/", '', $matches[1]);

            /* Determine if there is any comments. */
            preg_match("/(\(.+\))/", $entry, $comments);

            /* RFC 2369 [2] states that we should only show the *FIRST* URL
               that appears in a header that we can adequately handle. */
            if (stristr($match, 'mailto:')) {
                $match = substr($match, strpos($match, ':') + 1);
                if ($raw) {
                    return $match;
                }
                $output = Horde::link(IMP::composeLink($match)) . $match . '</a>';
                if (!empty($comments[1])) {
                    $output .= '&nbsp;' . $comments[1];
                }
                break;
            } else {
                if (($url = Text::linkUrls($match))) {
                    if ($raw) {
                        return $match;
                    }
                    $output = $url;
                    if (!empty($comments[1])) {
                        $output .= '&nbsp;' . $comments[1];
                    }
                    break;
                } else {
                    /* Use this entry unless we can find a better one. */
                    $output = $match;
                }
            }
        }

        $this->setValue($header, $output);
    }

    /**
     * Returns the flag status.
     *
     * @access public
     *
     * @param string $flag  Is this flag set?
     *                      Flags: unseen, answered, draft, important, deleted
     *
     * @return boolean  True if the flag has been set, false if not.
     */
    function getFlag($flag)
    {
        if (!empty($this->_flags[String::lower($flag)])) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Add any site-specific headers defined in config/header.txt to
     * the internal header array.
     *
     * @access public
     */
    function addSiteHeaders()
    {
        global $conf;

        /* Add the 'User-Agent' header. */
        $this->addAgentHeader();

        /* Tack on any site-specific headers. */
        if ($conf['msg']['prepend_header'] &&
            @is_readable(IMP_BASE . '/config/header.txt')) {
            include_once HORDE_BASE . '/lib/Text.php';

            $lines = @file(IMP_BASE . '/config/header.txt');
            foreach ($lines as $line) {
                $line = Text::expandEnvironment($line);
                if (!empty($line)) {
                    list($key, $val) = explode(':', $line, 2);
                    $val = strtr($val, ':', '_');
                    $this->addHeader($key, rtrim($val));
                }
            }
        }
    }

    /**
     * Get the primary from address (first address in the From: header).
     *
     * @access public
     *
     * @return string  The from address (user@host).
     */
    function getFromAddress()
    {
        if (!($ob = $this->getOb('from'))) {
            return null;
        }

        require_once HORDE_BASE . '/lib/MIME.php';

        return trim(MIME::trimEmailAddress(MIME::rfc822WriteAddress($ob[0]->mailbox, $ob[0]->host, '')));
    }

    /**
     * Get a header from the header object.
     *
     * @access public
     *
     * @param string $field    The object field to retrieve (see
     *                         imap_headerinfo() for the list of fields).
     * @param boolean $decode  Should the return value be MIME decoded?
     *                         It will only be decoded if it is not an object
     *                         itself.
     *
     * @return mixed  The field requested.
     */
    function getOb($field, $decode = false)
    {
        $data = null;

        if (!($ob = $this->getHeaderObject())) {
            return $data;
        }

        if (isset($ob->$field)) {
            $data = $ob->$field;
            if (!empty($decode) && !is_object($data) && !is_array($data)) {
                include_once HORDE_BASE . '/lib/MIME.php';
                $data = MIME::decode($data);
            }
        }

        return (is_string($data)) ? strtr($data, "\t", " ") : $data;
    }

    /**
     * Builds a string containing a list of addresses.
     *
     * @access public
     *
     * @param string $field           The address field to parse.
     * @param integer $addURL         The self URL.
     * @param optional boolean $set   Set the associated header with the
     *                                return string?
     * @param optional boolean $link  Link each address to the compose screen?
     *
     * @return string  String containing the formatted address list.
     */
    function buildAddressLinks($field, $addURL, $set = false, $link = true)
    {
        global $prefs, $registry;

        $add_link = null;
        $ret = '';

        /* Make sure this is a valid object address field. */
        $array = $this->getOb($field);
        if (empty($array) || !is_array($array)) {
            return null;
        }

        /* Set up the add address icon link if contact manager is
           available. */
        if ($link &&
            $registry->hasMethod('contacts/add') &&
            $prefs->getValue('add_source')) {
            $add_link = Horde::addParameter($addURL, 'actionID', 'add_address');
        }

        foreach ($this->getAddressesFromObject($array) as $ob) {
            /* Separate multiple addresses with a comma. */
            if (!empty($ret)) {
                $ret .= ', ';
            }

            if (!empty($ob->address) && !empty($ob->inner)) {
                /* If this is an incomplete e-mail address, don't link to
                   anything. */
                if (stristr($ob->host, 'UNKNOWN')) {
                    $ret .= $ob->address;
                } else {
                    if ($link) {
                        $ret .= Horde::link(IMP::composeLink(array('to' => $ob->address)), sprintf(_("Compose Message (%s)"), $ob->inner));
                        $ret .= htmlspecialchars($ob->address) . '</a>';
                    } else {
                        $ret .= htmlspecialchars($ob->address);
                    }

                    /* Append the add address icon to every address if
                       contact manager is available. */
                    if ($add_link) {
                        $curr_link = Horde::addParameter($add_link, 'name', $ob->personal);
                        $curr_link = Horde::addParameter($curr_link, 'address', $ob->inner);
                        $ret .= Horde::link($curr_link, sprintf(_("Add to Address book (%s)"), $ob->inner));
                        $ret .= Horde::img('addressbook-blue.gif', '', 'hspace="2"') . '</a>';
                    }
                }
            }
        }

        /* If left with an empty address list ($ret), inform the user that
           the recipient list is purposely "undisclosed". */
        if (empty($ret)) {
            $ret = _("Undisclosed Recipients");
        }

        /* Set the header value, if requested. */
        if (!empty($set)) {
            $this->setValue($field, $ret);
        }

        return $ret;
    }

}
