<?php

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

/* The virtual path to use for VFS data. */
define('IMP_VFS_ATTACH_PATH', '.horde/imp/compose');

/* The name of the field used for attachment file uploads. */
define('IMP_COMPOSE_UPLOAD', 'file_upload');

/**
 * The IMP_Compose:: class contains functions related to generating
 * outgoing mail messages.
 *
 * $Horde: imp/lib/Compose.php,v 1.40 2003/08/04 23:30:47 chuck 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.40 $
 * @since   IMP 4.0
 * @package imp
 */
class IMP_Compose {

    /**
     * The cached attachment data.
     *
     * @var array $_cache
     */
    var $_cache = array();

    /**
     * The aggregate size of all attachments (in bytes).
     *
     * @var integer $_size
     */
    var $_size = 0;

    /**
     * Constructor
     *
     * @access public
     */
    function IMP_Compose($params = array())
    {
        if (isset($params['cacheID'])) {
            $this->_retrieveMimeCache($params['cacheID']);
        }
    }

    /**
     * Send a message.
     *
     * @access public
     *
     * @param string $email                 The e-mail list to send to.
     * @param object IMP_Headers &$headers  The IMP_Headers object holding
     *                                      this messages headers.
     * @param mixed &$message               Either the message text (string)
     *                                      or a MIME_Structure object that
     *                                      contains the text to send.
     *
     * @return mixed  True on success, PEAR_Error object on error.
     */
    function sendMessage($email, &$headers, &$message)
    {
        global $conf;

        require_once 'Mail.php';

        /* We don't actually want to alter the contents of the $conf['mailer']
           array, so we make a copy of the current settings. We will apply
           our modifications (if any) to the copy, instead. */
        $params = $conf['mailer']['params'];

        /* Force the SMTP host value to the current SMTP server if one has
           been selected for this connection. */
        if (!empty($_SESSION['imp']['smtphost'])) {
            $params['host'] = $_SESSION['imp']['smtphost'];
        }

        /* If SMTP authentication has been requested, populate the
           username and password fields based on the current values
           for the user.  Note that we assume that the username and
           password values from the current IMAP / POP3 connection are
           valid for SMTP authentication as well. */
        if (!empty($params['auth'])) {
            $params['username'] = $_SESSION['imp']['user'];
            $params['password'] = Secret::read(Secret::getKey('imp'), $_SESSION['imp']['pass']);
        }

        /* Add the site headers. */
        $headers->addSiteHeaders();

        /* If $message is a string, we need to get a MIME_Message
           object to encode the headers. */
        if (is_string($message)) {
            $msg = $message;
            $mime_message = &new MIME_Message($_SESSION['imp']['maildomain']);
            $headerArray = $mime_message->encode($headers->toArray());
        } else {
            $msg = $message->toString();
            $headerArray = $message->encode($headers->toArray());
        }

        /* Make sure the message has a trailing newline. */
        if (substr($msg, -1) != "\n") {
            $msg .= "\n";
        }

        $mailer = &Mail::factory($conf['mailer']['type'], $params);
        return $mailer->send(MIME::encodeAddress($email, null, $_SESSION['imp']['maildomain']), $headerArray, $msg);
    }

    /**
     * Finds the main "body" text part (if any) in a message.
     *
     * @param object MIME_Message &$mime_message  A MIME_Message object.
     * @param integer $index                      The index of the message.
     *
     * @return string  The text of the "body" part of the message.
     *                 Returns an empty string if no "body" found.
     */
    function findBody(&$mime_message, $index)
    {
        /* Look for potential body parts. */
        $part = $mime_message->getBasePart();
        $primary_type = $part->getPrimaryType();
        if (($primary_type == MIME::type(TYPEMULTIPART)) ||
            ($primary_type == MIME::type(TYPETEXT))) {
            $body = $this->_findBody($part, $index);
            if (!empty($body)) {
                return $body;
            }
        }

        return '';
    }

    /**
     * Processes a MIME Part and looks for "body" data.
     *
     * @param object MIME_Part &$mime_part  A MIME_Part object.
     * @param integer $index                The index of the message.
     *
     * @return string  The decoded string of the mime part's body.
     */
    function _findBody(&$mime_part, $index)
    {
        if ($mime_part->getPrimaryType() == MIME::type(TYPEMULTIPART)) {
            foreach ($mime_part->getParts() as $part) {
                if (($body = $this->_findBody($part, $index))) {
                    return $body;
                }
            }
        } elseif ($mime_part->getPrimaryType() == MIME::type(TYPETEXT)) {
            if (!$mime_part->getContents()) {
                require_once IMP_BASE . '/lib/Contents.php';
                $contents = &new IMP_Contents($index);
                $mime_part->setContents($contents->getBodyPart($mime_part->getMIMEId()));
            }
            $mime_part->transferDecodeContents();
            $body = $mime_part->getContents();
            if ($mime_part->getSubType() == 'html') {
                require_once HORDE_BASE . '/lib/Text.php';
                $converter = &new Text_HTMLConverter($body);
                return $converter->getText();
            } else {
                return $body;
            }
        }

        return '';
    }

    /**
     * Determine the reply text for a message.
     *
     * @param object MIME_Message &$mime_message  A MIME_Message object.
     * @param integer $index                      The index of the message.
     * @param string $from                        The email address of the
     *                                            original sender.
     * @param object IMP_Headers &$h              The IMP_Headers object for
     *                                            the message.
     *
     * @return string  The text of the body part of the message to use
     *                 for the reply.
     */
    function replyMessage(&$mime_message, $index, $from, &$h)
    {
        global $prefs;

        $msg = '';

        if (!$prefs->getValue('reply_quote')) {
            return $msg;
        }

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

        $quote_str = "\n" . rtrim($prefs->getValue('quote_prefix')) . ' ';
        $msg = $this->findBody($mime_message, $index);
        $wrap_width = $prefs->getValue('wrap_width') - String::length($quote_str);
        if ($wrap_width < 20) {
            $wrap_width = 20;
        }
        $msg = Text::wrap($msg, $wrap_width, $quote_str, NLS::getCharset());
        if (empty($msg)) {
            $msg = '[' . _("No message body text") . ']';
        } elseif ($prefs->getValue('reply_headers') && !empty($h)) {
            $msghead = $quote_str . '----- ';
            if (($from = $h->getFromAddress())) {
                $msghead .= sprintf(_("Message from %s"), $from);
            } else {
                $msghead .= _("Message");
            }

            /* Extra '-'s line up with "End Message" below. */
            $msghead .= ' ---------';

            if (($date_ob = $h->getOb('date', true))) {
                $msghead .= $quote_str . _("    Date: ") . $date_ob;
            }
            if (($from_ob = MIME::addrArray2String($h->getOb('from')))) {
                $msghead .= $quote_str . _("    From: ") . $from_ob;
            }
            if (($rep_ob = MIME::addrArray2String($h->getOb('reply_to')))) {
                $msghead .= $quote_str . _("Reply-To: ") . $rep_ob;
            }
            if (($sub_ob = $h->getOb('subject', true))) {
                $msghead .= $quote_str . _(" Subject: ") . $sub_ob;
            }
            if (($to_ob = MIME::addrArray2String($h->getOb('to')))) {
                $msghead .= $quote_str . _("      To: ") . $to_ob;
            }
            $msg = $msghead . $quote_str . $quote_str . $msg;
            if (!empty($from)) {
                $msg .= $quote_str . $quote_str . '----- ' . sprintf(_("End message from %s"), $from) . " -----\n";
            } else {
                $msg .= $quote_str . $quote_str . '----- ' . _("End message") . " -----\n";
            }
        } else {
            $msg = $this->_expandAttribution($prefs->getValue('attrib_text'), $from, $h) . "\n" . $quote_str . $msg;
        }

        return $msg . "\n";
    }

    /**
     * Determine the reply text for a message.
     *
     * @param object MIME_Message &$mime_message  A MIME_Message object.
     * @param integer $index                      The index of the message.
     * @param object IMP_Headers &$h              The IMP_Headers object for
     *                                            the message.
     *
     * @return string  The text of the body part of the message to use
     *                 for the forward.
     */
    function forwardMessage(&$mime_message, $index, &$h)
    {
        require_once HORDE_BASE . '/lib/Text.php';

        $msg = "\n\n\n----- ";

        if (($from = $h->getFromAddress())) {
            $msg .= sprintf(_("Forwarded message from %s"), $from);
        } else {
            $msg .= _("Forwarded message");
        }

        $msg .= " -----\n";

        if (($date = $h->getOb('date', true))) {
            $msg .= _("    Date: ") . $date . "\n";
        }
        if (($from_ob = MIME::addrArray2String($h->getOb('from')))) {
            $msg .= _("    From: ") . $from_ob . "\n";
        }
        if (($rep_ob = MIME::addrArray2String($h->getOb('reply_to')))) {
            $msg .= _("Reply-To: ") . $rep_ob . "\n";
        }
        if (($subject = $h->getOb('subject', true))) {
            $msg .= _(" Subject: ") . $subject . "\n";
        }
        if (($to_ob = MIME::addrArray2String($h->getOb('to')))) {
            $msg .= _("      To: ") . $to_ob . "\n";
        }

        $msg .= "\n";
        $msg .= $this->findBody($mime_message, $index);
        $msg .= "\n\n----- " . _("End forwarded message") . " -----\n";

        return $msg;
    }

    /**
     * Adds an attachment to a MIME_Part from an uploaded file.
     * The actual attachment data is stored in a separate file - the
     * MIME_Part information entries 'temp_filename' and 'temp_filetype'
     * are set with this information.
     *
     * @access public
     *
     * @return mixed  Returns the filename on success.
     *                Returns PEAR_Error on error.
     */
    function addUploadAttachment($disposition)
    {
        global $conf;

        require_once HORDE_BASE . '/lib/Server.php';
        $res = Server::wasFileUploaded(IMP_COMPOSE_UPLOAD, _("attachment"));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $filename = basename($_FILES[IMP_COMPOSE_UPLOAD]['name']);
        $tempfile = $_FILES[IMP_COMPOSE_UPLOAD]['tmp_name'];

        /* Check for filesize limitations. */
        if ($conf['compose']['attach_size_limit'] &&
            (($conf['compose']['attach_size_limit'] - $this->sizeOfAttachments() - $_FILES[IMP_COMPOSE_UPLOAD]['size']) < 0)) {
            return PEAR::raiseError(sprintf(_("Attached file \"%s\" exceeds the attachment size limits. File NOT attached."), $filename), 'horde.error');
        }

        /* Store the data in a MIME_Part. Some browsers do not send the MIME
           type so try an educated guess. */
        if (isset($_FILES[IMP_COMPOSE_UPLOAD]['type']) &&
            !empty($_FILES[IMP_COMPOSE_UPLOAD]['type']) &&
            ($_FILES[IMP_COMPOSE_UPLOAD]['type'] != 'application/octet-stream')) {
            $part = &new MIME_Part($_FILES[IMP_COMPOSE_UPLOAD]['type']);
        } else {
            require_once HORDE_BASE . '/lib/MIME/Magic.php';
            /* Try to determine the MIME type from 1) analysis of the file
               (if available) and, if that fails, 2) from the extension. */
            if (!($type = MIME_Magic::analyzeFile($tempfile))) {
                $type = MIME_Magic::filenameToMIME($filename);
            }
            $part = &new MIME_Part($type);
        }
        $part->setName($filename);
        $part->setBytes($_FILES[IMP_COMPOSE_UPLOAD]['size']);
        $part->setInformation('size', $part->getSize());
        $part->setTransferEncoding('base64');
        if ($disposition) {
            $part->setDisposition($disposition);
        }

        if ($conf['compose']['use_vfs']) {
            $attachment = $tempfile;
        } else {
            $attachment = Horde::getTempFile('impatt', false);
            move_uploaded_file($tempfile, $attachment);
        }

        /* Store the data. */
        $this->_storeAttachment($part, $attachment);

        return $part->getName();
    }

    /**
     * Adds an attachment to a MIME_Part from data existing in the part.
     *
     * @access public
     *
     * @param object MIME_Part &$part  The MIME_Part object that contains the
     *                                 attachment data.
     *
     * @return object PEAR_Error  Returns a PEAR_Error object on error.
     */
    function addMIMEPartAttachment(&$part)
    {
        global $conf;

        $vfs = $conf['compose']['use_vfs'];

        /* Extract the data from the currently existing MIME_Part and then
           delete it. If this is an unknown MIME part, we must save to a
           temporary file to run the file analysis on it. */
        if (!$vfs || ($part->getType() == 'application/octet-stream')) {
            $attachment = Horde::getTempFile('impatt', $vfs);
            $fp = fopen($attachment, 'w');
            fwrite($fp, $part->transferDecode());
            fclose($fp);

            if ($part->getType() == 'application/octet-stream') {
                require_once HORDE_BASE . '/lib/MIME/Magic.php';

                /* Try to determine the MIME type from 1) analysis of the file
                   (if available) and, if that fails, 2) from the extension. */
                if (!($type = MIME_Magic::analyzeFile($attachment))) {
                    $type = MIME_Magic::filenameToMIME($attachment);
                }
                $part->setType($type);
            }
        }

        if (is_null($part->getBytes())) {
            $data = $part->getContents();
            $part->setBytes(String::length($data, $part->getCharset(true)));
            $part->setInformation('size', $part->getSize());
        }

        /* Check for filesize limitations. */
        if ($conf['compose']['attach_size_limit'] &&
            (($conf['compose']['attach_size_limit'] - $this->sizeOfAttachments() - $part->getBytes()) < 0)) {
            return PEAR::raiseError(sprintf(_("Attached file \"%s\" exceeds the attachment size limits. File NOT attached."), $part->getName()), 'horde.error');
        }

        if ($vfs) {
            $vfs_data = $part->transferDecode();
        }
        $part->setContents('');

        /* Store the data. */
        if ($vfs) {
            $this->_storeAttachment($part, $vfs_data, false);
        } else {
            $this->_storeAttachment($part, $attachment);
        }
    }

    /**
     * Stores the attachment data in its correct location.
     *
     * @access private
     *
     * @param object MIME_Part &$part     The MIME_Part of the attachment.
     * @param string $data                Either the filename of the
     *                                    attachment or, if $vfs_file is
     *                                    false, the attachment data.
     * @param optional boolean $vfs_file  If using VFS, is $data a filename?
     */
    function _storeAttachment(&$part, $data, $vfs_file = true)
    {
        global $conf;

        /* Store in VFS. */
        if ($conf['compose']['use_vfs']) {
            require_once HORDE_BASE . '/lib/VFS.php';
            require_once HORDE_BASE . '/lib/VFS/GC.php';
            $vfs = &VFS::singleton($conf['vfs']['type'], Horde::getDriverConfig('vfs', $conf['vfs']['type']));
            VFS_GC::gc($vfs, IMP_VFS_ATTACH_PATH, 86400);
            $cacheID = md5(mt_rand());
            if ($vfs_file) {
                $vfs->write(IMP_VFS_ATTACH_PATH, $cacheID, $data);
            } else {
                $vfs->writeData(IMP_VFS_ATTACH_PATH, $cacheID, $data);
            }
            $part->setInformation('temp_filename', $cacheID);
            $part->setInformation('temp_filetype', 'vfs');
        } else {
            chmod($data, 0600);
            $part->setInformation('temp_filename', $data);
            $part->setInformation('temp_filetype', 'file');
        }

        /* Add the size information to the counter. */
        $this->_size += $part->getBytes();

        $this->_cache[] = $part;
    }

    /**
     * Delete attached files.
     *
     * @access public
     *
     * @param mixed $number  Either a single integer or an array of integers
     *                       corresponding to the attachment position.
     *
     * @return array  The list of deleted filenames.
     */
    function deleteAttachment($number)
    {
        global $conf;

        $names = array();

        if (!is_array($number)) {
            $number = array($number);
        }

        foreach ($number as $val) {
            $val--;
            $part = &$this->_cache[$val];
            $filename = $part->getInformation('temp_filename');
            if ($part->getInformation('temp_filetype') == 'vfs') {
                /* Delete from VFS. */
                require_once HORDE_BASE . '/lib/VFS.php';
                $vfs = &VFS::singleton($conf['vfs']['type'], Horde::getDriverConfig('vfs', $conf['vfs']['type']));
                $vfs->deleteFile(IMP_VFS_ATTACH_PATH, $filename);
            } else {
                /* Delete from filesystem. */
                @unlink($filename);
            }

            $part->setInformation('temp_filename', '');
            $part->setInformation('temp_filetype', '');

            $names[] = $part->getName(false, true);

            /* Remove the size information from the counter. */
            $this->_size -= $part->getBytes();

            unset($this->_cache[$val]);
        }

        /* Reorder the attachments. */
        $this->_cache = array_values($this->_cache);

        return $names;
    }

    /**
     * Deletes all attachments.
     *
     * @access public
     */
    function deleteAllAttachments()
    {
        $numbers = array();

        for ($i = 1; $i <= $this->numberOfAttachments(); $i++) {
            $numbers[] = $i;
        }

        $this->deleteAttachment($numbers);
    }

    /**
     * Updates information in a specific attachment.
     *
     * @access public
     *
     * @param integer $number  The attachment to update.
     * @param array $params    An array of update information.
     * <pre>
     * 'disposition'  --  The Content-Disposition value.
     * 'description'  --  The Content-Description value.
     * </pre>
     */
    function updateAttachment($number, $params)
    {
        $number--;
        $this->_cache[$number]->setDisposition($params['disposition']);
        $this->_cache[$number]->setDescription($params['description']);
    }

    /**
     * Returns the list of current attachments.
     *
     * @access public
     *
     * @return array  The list of attachments.
     */
    function getAttachments()
    {
        return $this->_cache;
    }

    /**
     * Returns the number of attachments currently in this message.
     *
     * @access public
     *
     * @return integer  The number of attachments in this message.
     */
    function numberOfAttachments()
    {
        return count($this->_cache);
    }

    /**
     * Returns the size of the attachments in bytes.
     *
     * @access public
     *
     * @return integer  The size of the attachments (in bytes).
     */
    function sizeOfAttachments()
    {
        return $this->_size;
    }

    /**
     * Build the MIME_Part attachments from the temporary file data.
     *
     * @access public
     *
     * @param object MIME_Part &$base  The base MIME_Part object to add the
     *                                 attachments to.
     */
    function buildAttachments(&$base)
    {
        global $conf;

        foreach ($this->_cache as $part) {
            $filename = $part->getInformation('temp_filename');
            if ($part->getInformation('temp_filetype') == 'vfs') {
                require_once HORDE_BASE . '/lib/VFS.php';
                $vfs = &VFS::singleton($conf['vfs']['type'], Horde::getDriverConfig('vfs', $conf['vfs']['type']));
                $data = $vfs->read(IMP_VFS_ATTACH_PATH, $filename);
            } else {
                $fd = fopen($filename, 'rb');
                $data = fread($fd, filesize($filename));
                fclose($fd);
            }

            /* Set the part's contents to the raw attachment data. */
            $part->setContents($data);

            /* We've just set raw data; make sure the part doesn't think
               that it's already encoded. */
            $part->setTransferEncoding($part->getTransferEncoding(), false);

            /* Add to the base part. */
            $base->addPart($part);
        }
    }

    /**
     * Expand macros in attribution text when replying to messages.
     *
     * @access private
     *
     * @param string $line            The line of attribution text.
     * @param string $from            The email address of the original
     *                                sender.
     * @param object IMP_Headers &$h  The IMP_Headers object for the message.
     *
     * @return string  The attribution text.
     */
    function _expandAttribution($line, $from, &$h) 
    {        
        $addressList = '';
        $nameList = '';

        /* First we'll get a comma seperated list of email addresses
           and a comma seperated list of personal names out of $from
           (there just might be more than one of each). */
        foreach (@imap_rfc822_parse_adrlist($from, '') as $entry) {
            if (isset($entry->mailbox) && isset($entry->host)) {
                if (strlen($addressList) > 0) {
                    $addressList .= ', ';
                }
                $addressList .= $entry->mailbox . '@' . $entry->host;
            } elseif (isset($entry->mailbox)) {
                if (strlen($addressList) > 0) {
                    $addressList .= ', ';
                }                
                $addressList .= $entry->mailbox;
            }
            if (isset($entry->personal)){
                if (strlen($nameList) > 0) {
                    $nameList .= ', ';
                } 
                $nameList .= $entry->personal;
            } elseif (isset($entry->mailbox)) {
                if (strlen($nameList) > 0) {
                    $nameList .= ', ';
                }                
                $nameList .= $entry->mailbox;
            } 
        }        

        /* Define the macros. */
        if (!($subject = $h->getOb('subject', true))) {
            $subject = _("[No Subject]");
        }
        $udate = strtotime($h->getOb('date', true));

        $match = array(
            /* New line. */
            '/%n/' => "\n",

            /* The '%' character. */
            '/%%/' => '%',

            /* Name and email address of original sender. */
            '/%f/' => $from,

            /* Senders email address(es). */
            '/%a/' => $addressList,

            /* Senders name(s). */
            '/%p/' => $nameList,

            /* RFC 822 date and time. */
            '/%r/' => $h->getOb('date', true),

            /* Date as ddd, dd mmm yyyy. */
            '/%d/' => String::convertCharset(@strftime("%a, %d %b %Y", $udate), NLS::getCharset(true)),   

            /* Date in locale's default. */
            '/%x/' => String::convertCharset(@strftime("%x", $udate), NLS::getCharset(true)),

            /* Date and time in locale's default. */
            '/%c/' => String::convertCharset(@strftime("%c", $udate), NLS::getCharset(true)),

            /* Message-ID. */
            '/%m/' => $h->getOb('message_id'),

            /* Message subject. */
            '/%s/' => $subject
        );

        return (preg_replace(array_keys($match), array_values($match), $line));
    }

    /**
     * Obtains the cached array of MIME_Parts to be attached to this message.
     *
     * @access private
     *
     * @param string $cacheID  The cacheID of the session object.
     */
    function _retrieveMimeCache($cacheID)
    {
        if ($cacheID) {
            require_once HORDE_BASE . '/lib/SessionObjects.php';
            $cacheSess = &Horde_SessionObjects::singleton();
            $result = $cacheSess->query($cacheID);
            $cacheSess->setPruneFlag($cacheID, true);
            $this->_cache = &$result['cache'];
            $this->_size = &$result['size'];
        }
    }
            
    /** 
     * Obtains the cache ID for the session object that contains the
     * MIME_Part objects to be attached to this message.
     * This function needs to be run at least once per pageload to save the
     * session object.
     *
     * @access public
     *
     * @return string  The message cache ID if the object needs to be saved.
     *                 Else, false is returned.
     */
    function getMessageCacheId()
    {
        if (!empty($this->_cache)) {
            require_once HORDE_BASE . '/lib/SessionObjects.php';
            $cacheSess = &Horde_SessionObjects::singleton();
            $store = array(
                'cache' => $this->_cache,
                'size' => $this->_size
            );
            return $cacheSess->storeOid($store);
        } else {
            return false;
        }
    }

    /**
     * Has the maximum number of attachments been reached?
     *
     * @access public
     *
     * @return boolean  True if maximum number of attachments has been
     *                  reached.
     */
    function haveMaxAttachments()
    {
        global $conf;

        if (!$conf['compose']['attach_count_limit'] ||
            ($conf['compose']['attach_count_limit'] > $this->numberOfAttachments())) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * What is the maximum attachment size allowed?
     *
     * @access public
     *
     * @return integer  The maximum attachment size allowed (in bytes).
     */
    function maxAttachmentSize()
    {
        global $conf, $imp;

        $size = $imp['file_upload'];

        if ($conf['compose']['attach_size_limit']) {
            $size = min($size, max($conf['compose']['attach_size_limit'] - $this->sizeOfAttachments(), 0));
        }

        return $size;
    }

    /**
     * Adds the attachments to the message (in the case of a forward with
     * attachments).
     *
     * @param object MIME_Message &$message  The MIME_Message object.
     *
     * @return array  An array of PEAR_Error object on error.
     *                An empty array if successful.
     */
    function attachFilesFromMessage(&$message)
    {
        $errors = array();

        foreach ($message->getParts() as $ref => $mime) {
            if (($ref != 1) ||
                ($mime->getPrimaryType() != MIME::type(TYPETEXT))) {
                $res = $this->addMIMEPartAttachment($mime);
                if (is_a($res, 'PEAR_Error')) {
                    $errors[] = $res;
                }
            }
        }

        return $errors;
    }

    /**
     * Send a spam message to the sysadmin.
     *
     * @access public
     *
     * @param string $data  The message data.
     */
    function sendSpamReportMessage($data)
    {
        global $conf, $imp;

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

        /* Initialize the user's identities */
        $user_identity = &Identity::singleton(array('imp', 'imp'));

        /* Build the MIME structure. */
        $mime = &new MIME_Message();
        $mime->setType('multipart/digest');
        $mime->addPart(new MIME_Part('message/rfc822', $data));

        $spam_headers = &new IMP_Headers();
        $spam_headers->addMessageIdHeader();
        $spam_headers->addHeader('Date', date('r'));
        $spam_headers->addHeader('To', $conf['spam']['email']);
        $spam_headers->addHeader('From', $user_identity->getFromLine());
        $spam_headers->addHeader('Subject', _("Spam Report from") . ' ' . $imp['user']);
        $spam_headers->addMIMEHeaders($mime);

        /* Send the message. */
        $this->sendMessage($conf['spam']['email'], $spam_headers, $mime);
    }

}
