<?php

/**
 * Horde Kolab utility library, providing various utility functions for dealing with the Kolab server.
 * Consists mainly of IMAP related functions.
 *
 * Copyright 2003 Code Fusion, cc.
 *
 * @author  Stuart Bingė <s.binge@codefusion.co.za>
 * @version $Revision: 0.1 $
 * @since   Horde 3.0
 * @package horde.Kolab
 */

require_once 'Net/HTTP/Client.php';

define('MIME_NL', "\r\n");

define('ERR_MBOX_DNE', 'Mailbox does not exist');

define('X_HEAD_CAT', 'X-Horde-Category');
define('X_HEAD_ID', 'X-Horde-ID');

class Kolab {

    /**
     * Tests imap_last_error() against $error and returns true if they are equal.
     */
    function testError($error)
    {
        return strcasecmp(imap_last_error(), $error) == 0;
    }

    /**
     * When given a string of the form "a@b@c", where "b", "c" and any "@" may be missing,
     * returns the "a@b" portion, or just "a" if that is all that is specified. Used to strip
     * any trailing "@hostname" from the usernames when used with the Kolab server.
     */
    function stripUsername($userID)
    {
        preg_match('/^([^@]*@?[^@]*).*/', $userID, $matches);
        return $matches[1];
    }

    /**
     * When given a string of the form "a@b@c", where "b", "c" and any "@" may be missing,
     * returns only the "a" portion.
     */
    function stripBaseUsername($userID)
    {
        preg_match('/^([^@]*).*/', $userID, $matches);
        return $matches[1];
    }

    /**
     * Returns an array of the form (username, password) for the currently logged in horde user.
     * The returned username/password pair can be used for authentication with the Kolab IMAP server.
     */
    function getAuthentication()
    {
        return array(Kolab::stripUsername(Auth::getAuth()), Auth::getCredential('password'));
    }

    /**
     * Returns an IMAP stream connected to $server that has been opened to $mailbox, or
     * false on failure. If the folder does not exist and $create == true, the function
     * tries to create the folder.
     */
    function openImapConnection($server, $mailbox, $create = true)
    {
        list ($user, $pass) = Kolab::getAuthentication();

        $host = Kolab::mailboxURI($server);
        $box = Kolab::mailboxURI($server, $mailbox);

        $imapstream = @imap_open($box, $user, $pass); // Firstly try a straight imap_open()

        if ($create && Kolab::testError(ERR_MBOX_DNE))  // Box does not exist, try to create it
        {
            if (!$imapstream) @imap_close($imapstream);

            $imapstream = @imap_open($host, $user, $pass, OP_HALFOPEN);

            if (!$imapstream) return false;

            if (!@imap_createmailbox($imapstream, $box)) return false;

            $imapstream = @imap_reopen($box); // Successfully created the box, now try to open it
        }

        if (!$imapstream) return false;

        return $imapstream;
    }

    /**
     * Closes $imapstream, returns a boolean indicating success.
     */
    function closeImapConnection($imapstream)
    {
        if ($imapstream && !is_null($imapstream))
            return @imap_close($imapstream, CL_EXPUNGE);
        return true;
    }

    /**
     * Returns an associative array of the headers of message $messageid in the currently open message box
     * designated by $imapstream, or false on failure. On success each item of the returned array, of
     * the form $key => $value, corresponds to <HeaderName>: <HeaderValue> respectively in the headers.
     * If $uid == true then $messageid is treated as an IMAP unique message identifier, as opposed to
     * a message sequence number.
     */
    function getMessageHeaders($imapstream, $messageid, $uid = false)
    {
        if (!$imapstream || is_null($imapstream)) return false;
        if ($uid) $options = FT_UID; else $options = 0;
        $headerdata = @imap_fetchheader($imapstream, $messageid, $options);
        if (!$headerdata || is_null($headerdata) || $headerdata == "") return false;
        $headerlines = explode(MIME_NL, $headerdata);
        for ($i = 0; $i < count($headerlines); $i++)
        {
            if (strpos($headerlines[$i], ':') == 0) continue;
            list($hname, $hval) = explode(':', $headerlines[$i]);
            $headers[trim($hname)] = trim($hval);
        }

        return $headers;
    }

    /**
     * Returns the value of the header attribute with name $name in the header list $headers. If $name
     * does not exist in $headers, $default is returned instead.
     */
    function getHeaderValue(&$headers, $name, $default = NULL)
    {
        if (array_key_exists($name, $headers))
            return $headers[$name];
        else
            return $default;
    }

    /**
     * Returns a string of the form "{<localhost>:143/imap/notls}<INBOX>" for use in various IMAP
     * functions, where <localhost> and <INBOX> are example values of $host and $mailbox, respectively.
     */
    function mailboxURI($host, $mailbox = "")
    {
        return '{' . $host . ':143/imap/notls}' . $mailbox;
    }

    /**
     * Appends a message to the message box $mailbox on the imap stream $imapstream. Returns a boolean
     * indicating success. The message is constructed by setting the Content-Type to $conttype,
     * setting From and To to $user, setting User-Agent to $ua, appending any additional headers
     * specified in $headers, and finally setting $body as the message body.
     *
     * NOTE: $mailbox must include the host address; this can be achieved by using the result of
     *       Kolab::MailboxURI("localhost", "INBOX/Mailbox"), for example, as the value for $mailbox.
     * NOTE: Ensure $body does not contain bare newlines ('\n') or else the function will fail.
     */
    function addMessage($imapstream, $mailbox, $user, $conttype, $body, $ua = "", $headers = array())
    {
        $msg =
              'Content-Type: ' . $conttype . '; charset="utf-8"' . MIME_NL
            . 'From: ' . $user . MIME_NL
            . 'Reply-To:' . MIME_NL
            . 'To: ' . $user . MIME_NL
            . 'User-Agent: Horde/' . $ua . '/Kolab' . MIME_NL
            . 'Date: ' . date('r') . MIME_NL;

        foreach ($headers as $key => $value)
            $msg .= $key . ': ' . $value . MIME_NL;

        $msg .= MIME_NL. $body;

        if (!@imap_append($imapstream, $mailbox, $msg)) return false;

        return true;
    }

    /**
     * Returns an array of message ids corresponding to the current messages in the mailbox specified
     * by $imapstream, sorted by date.
     */
    function getMessageList($imapstream)
    {
        return imap_sort($imapstream, SORTDATE, 0);
    }

    /**
     * Returns the contents of $user's VFB file stored on $server in the free/busy folder $folder.
     * If no user is specified, the free/busy information for the current user is returned.
     * If an error occurs, PEAR_Error is returned instead.
     */
    function getFreeBusy($server, $folder, $user = "")
    {
        $http = new Net_HTTP_Client();
        if (!$http->Connect($server, 80)) return new PEAR_Error("Unable to retrieve free/busy information for user " . $user);

        list($uname, $pass) = Kolab::getAuthentication();
        $http->setCredentials($uname, $pass);
        if (empty($user)) $user = $uname;

        $vfbfile = $folder . '/' . $user . '.vfb';
        $status = $http->Get($vfbfile);
        if ($status != 200) return new PEAR_Error("Unable to retrieve free/busy information for user " . $user . " - " . $http->getStatusMessage());
        $vfb = $http->getBody();

        $http->Disconnect();

        return $vfb;
    }

    /**
     * Stores $vfb as the current user's free/busy file on $server in the free/busy folder $folder.
     * On success, true is returned, otherwise a PEAR_Error is returned instead.
     */
    function storeFreeBusy($server, $folder, $vfb)
    {
        $http = new Net_HTTP_Client();
        if (!$http->Connect($server, 80)) return new PEAR_Error('Unable to store free/busy information for the current user.');

        list($user, $pass) = Kolab::getAuthentication();
        $http->setCredentials($uname, $pass);

        $vfbfile = $folder . '/' . $user . '.vfb';
        $status = $http->Put($vfbfile, $vfb);
        if ($status != 200) return new PEAR_Error('Unable to store free/busy information for the current user - ' . $http->getStatusMessage());

        $http->Disconnect();

        return true;
    }

    /**
     * Converts the date object $obj to a timestamp value (Yoinked from Kronolith).
     */
    function objectToTimestamp($obj)
    {
        return @mktime($obj->hour, $obj->min, $obj->sec, $obj->month, $obj->mday, $obj->year);
    }

    /**
     * Converts the associative array $arr to a timestamp value (Yoinked with modification from Kronolith).
     */
    function arrayToTimestamp($arr)
    {
        if (!is_array($arr)) return 0;
        $h = 0; $mi = 0; $s = 0; $mo = 0; $d = 0; $y = 0;
        foreach ($arr as $key => $value)
            switch ($key)
            {
                case 'hour': $h = $value; break;
                case 'min': $mi = $value; break;
                case 'sec': $s = $value; break;
                case 'month': $mo = $value; break;
                case 'mday': $d = $value; break;
                case 'year': $y = $value; break;
            }

        return @mktime($h, $mi, $s, $mo, $d, $y);
    }

    /**
     * Converts the timestamp value $timestamp to a date object (Yoinked from Kronolith).
     */
    function timestampToObject($timestamp)
    {
        $res = new stdClass();
        list($res->hour, $res->min, $res->sec, $res->mday, $res->month, $res->year) = explode('/', date('H/i/s/j/n/Y', $timestamp));
        return $res;
    }

    /**
     * Converts the timestamp value $timestamp to an associative array (Yoinked from Kronolith).
     */
    function timestampToArray($timestamp)
    {
        $obj = Kolab::TimestampToObject($timestamp);

        return array(
            'hour' => $obj->hour,
            'min' => $obj->min,
            'sec' => $obj->sec,
            'month' => $obj->month,
            'mday' => $obj->mday,
            'year' => $obj->year
        );
    }

}
