<?php
/**
 * Whups mail processing library.
 *
 * $Horde: whups/lib/Mail.php,v 1.46.2.2 2009-03-16 11:52:25 jan Exp $
 *
 * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (BSD). If you
 * did not receive this file, see http://www.horde.org/licenses/bsdl.php.
 *
 * @author  Jason M. Felice <jason.m.felice@gmail.com>
 * @author  Jan Schneider <jan@horde.org>
 * @package Whups
 */
class Whups_Mail {

    /**
     * Parse a MIME message and create a new ticket.
     *
     * @param string $text       This is the full text of the MIME message.
     * @param array $info        An array of information for the new ticket.
     *                           This should include:
     *                           - 'queue'    => queue id
     *                           - 'type'     => type id
     *                           - 'state'    => state id
     *                           - 'priority' => priority id
     *                           - 'ticket'   => ticket id (prevents creation
     *                                           of new tickets)
     * @param string $auth_user  This will be the Horde user that creates the
     *                           ticket. If null, we will try to deduce from
     *                           the message's From: header. We do NOT default
     *                           to Auth::getAuth().
     *
     * @return Whups_Ticket | PEAR_Error  Ticket or Error object.
     */
    function processMail($text, $info, $auth_user = null)
    {
        global $conf;

        require_once 'Horde/MIME/Contents.php';
        require_once 'Horde/MIME/Structure.php';
        $message = MIME_Structure::parseTextMIMEMessage($text);

        if (preg_match("/^(.*?)\r?\n\r?\n/s", $text, $matches)) {
            $hdrText = $matches[1];
        } else {
            $hdrText = $text;
        }
        $headers = MIME_Structure::parseMIMEHeaders($hdrText, false);
        $contents = new MIME_Contents($message);

        // If this message was generated by Whups, don't process it.
        if (!empty($headers['X-Whups-Generated'])) {
            return true;
        }

        // Use the message subject as the ticket summary.
        $info['summary'] = Whups_Mail::_findSimpleHeaderValue($headers, 'Subject');
        $info['comment'] = Whups_Mail::_buildComment($contents, $headers);
        $from = Whups_Mail::_findSimpleHeaderValue($headers, 'From');

        // Try to determine the Horde user for creating the ticket.
        if (empty($auth_user)) {
            $auth_user = Whups_Mail::_findAuthUser($headers);
        }
        $author = $auth_user;
        if (empty($auth_user) && !empty($info['default_auth'])) {
            $auth_user = $info['default_auth'];
            if (!empty($from)) {
                $info['user_email'] = $from;
            }
        }
        if (empty($auth_user) && !empty($conf['mail']['username'])) {
            $auth_user = $conf['mail']['username'];
            if (!empty($from)) {
                $info['user_email'] = $from;
            }
        }

        // Authenticate as the correct Horde user.
        if (!empty($auth_user) && $auth_user != Auth::getAuth()) {
            $params = Horde::getDriverConfig('auth', $conf['auth']['driver']);
            $auth = &Auth::singleton($conf['auth']['driver'], $params);
            $auth->setAuth($auth_user, array());
        }

        // See if we can match this message to an existing ticket.
        if ($ticket = Whups_Mail::_findTicket($info)) {
            $ticket->change('comment', $info['comment']);
            $ticket->change('comment-email', $from);
            $result = $ticket->commit($author);
            if (is_a($result, 'PEAR_Error')) {
                $result->addUserInfo(_("current user:") . ' ' . $auth_user);
                return $result;
            }
        } elseif (!empty($info['ticket'])) {
            // Didn't match an existing ticket though a ticket number had been
            // specified.
            return PEAR::raiseError(sprintf(_("Could not find ticket \"%s\"."),
                                            $info['ticket']));
        } else {
            if (!empty($info['guess-queue'])) {
                // Try to guess the queue name for the new ticket from the
                // message subject.
                $queues = $GLOBALS['whups_driver']->getQueues();
                foreach ($queues as $queueId => $queueName) {
                    if (preg_match('/\b' . preg_quote($queueName, '/') . '\b/i',
                                   $info['summary'])) {
                        $info['queue'] = $queueId;
                        break;
                    }
                }
            }
            // Create a new ticket.
            $ticket = Whups_Ticket::newTicket($info, $author);
            if (is_a($ticket, 'PEAR_Error')) {
                $ticket->addUserInfo(_("current user:") . ' ' . $auth_user);
                return $ticket;
            }
        }

        // Extract attachments.
        $attachments = $contents->getDownloadAllList();
        foreach ($attachments as $attachment) {
            $part = &$message->getPart($attachment);
            $part->transferDecodeContents();
            $tmp_name = Horde::getTempFile('whups');
            $fp = @fopen($tmp_name, 'wb');
            if (!$fp) {
                Horde::logMessage(sprintf('Cannot open file %s for writing.',
                                          $tmp_name),
                                  __FILE__, __LINE__, PEAR_LOG_ERR);
                return $ticket;
            }
            fwrite($fp, $part->getContents());
            fclose($fp);
            $part_name = $part->getName(true, true);
            if (empty($part_name)) {
                $part_name = MIME_DEFAULT_DESCRIPTION;
            }
            $ticket->change('attachment', array('name' => $part_name,
                                                'tmp_name' => $tmp_name));
            $result = $ticket->commit();
            if (is_a($result, 'PEAR_Error')) {
                $result->addUserInfo(_("current user:") . ' ' . $auth_user);
                return $result;
            }
        }
    }

    /**
     * Returns the ticket number matching the provided information.
     *
     * @param array $info  A hash with ticket information.
     *
     * @return integer  The ticket number if has been passed in the subject,
     *                  false otherwise.
     */
    function _findTicket($info)
    {
        if (!empty($info['ticket'])) {
            $ticketnum = $info['ticket'];
        } elseif (preg_match('/\[[\w\s]*#(\d+)\]/', $info['summary'], $matches)) {
            $ticketnum = $matches[1];
        } else {
            return false;
        }

        $ticket = Whups_Ticket::makeTicket($ticketnum);
        if (is_a($ticket, 'PEAR_Error')) {
            return false;
        }

        return $ticket;
    }

    /**
     * Returns a header value.
     *
     * If there are multiple headers of the same name, they are all returned
     * as a comma separated list. Header values will be MIME decoded.
     *
     * @param array $headers  A hash of header values.
     * @param string $name    A header name.
     *
     * @return string  The value of requested header(s), or null.
     */
    function _findSimpleHeaderValue($headers, $name)
    {
        foreach ($headers as $hdrName => $hdrVals) {
            if (strcasecmp($hdrName, $name) == 0) {
                if (is_array($hdrVals)) {
                    return implode(', ', $hdrVals);
                } else {
                    return MIME::decode($hdrVals);
                }
            }
        }

        return null;
    }

    /**
     * Returns the comment text built from the message content.
     *
     * @param MIME_Contents $contents  A MIME_Contents object representing the
     *                                 message.
     * @param array $headers           A hash of header values.
     *
     * @return string  The generated comment text, including basic headers and
     *                 the message body.
     */
    function _buildComment(&$contents, $headers)
    {
        // Format the message into a comment.
        $comment = _("Received message:") . "\n\n";
        if (!empty($GLOBALS['conf']['mail']['include_headers'])) {
            foreach ($headers as $name => $vals) {
                if (!strcasecmp($name, 'Subject') ||
                    !strcasecmp($name, 'From') ||
                    !strcasecmp($name, 'To') ||
                    !strcasecmp($name, 'Cc') ||
                    !strcasecmp($name, 'Date')) {
                    if (is_array($vals)) {
                        foreach ($vals as $val) {
                            $comment .= $name . ': ' . $val . "\n";
                        }
                    } else {
                        $comment .= $name . ': ' . MIME::decode($vals) . "\n";
                    }
                }
            }

            $comment .= "\n";
        }

        // Look for the body part.
        $body_id = $contents->findBody();
        if ($body_id) {
            $part = &$contents->getMIMEPart($body_id);
            $comment .= String::convertCharset($part->transferDecode(), $part->getCharset());
        } else {
            $comment .= _("[ Could not render body of message. ]");
        }

        return $comment . "\n";
    }

    /**
     * Searches the From: header for an email address contained in one
     * of our users' identities.
     *
     * @param array $headers  A hash of header values.
     *
     * @return string  The Horde user name that matches the headers' From:
     *                 address or null if the users can't be listed or no
     *                 match has been found.
     */
    function _findAuthUser($headers)
    {
        global $conf;

        require_once 'Horde/Identity.php';
        require_once 'Horde/MIME.php';

        $from = Whups_Mail::_findSimpleHeaderValue($headers, 'From');
        $from = MIME::bareAddress($from);

        $params = Horde::getDriverConfig('auth', $conf['auth']['driver']);
        $auth = &Auth::singleton($conf['auth']['driver'], $params);
        if ($auth->hasCapability('list')) {
            foreach ($auth->listUsers() as $user) {
                $identity = &Identity::singleton('none', $user);
                $addrs = $identity->getAll('from_addr');
                foreach ($addrs as $addr) {
                    if (strcasecmp($from, $addr) == 0) {
                        return $user;
                    }
                }
            }
        }

        return false;
    }

}
