[dev] Kolab Webclient: Kolab:: library

Stuart Bingë s.binge at codefusion.co.za
Wed Jan 28 05:55:57 PST 2004


On Wednesday 28 January 2004 12:26, Jan Schneider wrote:
> Simple use String::convertCharset($text, $from) if you wan't to convert
> *to* the user's charset and String::convertCharset($text,
> NLS::getCharset(), $to) to convert *from* the user's charset.

Thanks for the help, Jan. Here's an updated version of the library that 
implements some of the changes you suggested, namely the iconv to String:: 
and MIME:: conversion, and the conversion from Net_HTTP_Client to 
HTTP_Request and WebDAV_Client.

Cheers,

-- 
Stuart Bingë
Code Fusion cc. <http://www.codefusion.co.za/>
Tel: +27 11 391 1412
Mobile: +27 83 298 9727
Email: s.binge at codefusion.co.za
-------------- next part --------------
<?php

/**
 * The Kolab:: utility library provides various functions for dealing with a
 * Kolab server (i.e. functions that relate to Cyrus IMAP, WebDAV, etc.).
 *
 * Copyright 2003 Code Fusion, cc.
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * Written by Stuart Bingë <s.binge at codefusion.co.za>
 *
 *
 * CHANGELOG:
 *
 * 2004-01-28 <s.binge at codefusion.co.za>
 *   - Replaced the various iconv() functions with Horde counterparts from
 *    the MIME:: and String:: libraries.
 *   - Replaced the Net_HTTP_Client code with its equivalent that uses
 *    HTTP_Request for GETs, and PHP streams with the WebDAV_Client wrapper
 *    for WebDAV PUTs.
 *   - Fixed a small bug in openCyrusConnection() when false was being
 *    returned (instead of raising Horde::fatal) on a certain error condition.
 *
 * 2004-01-28 <s.binge at codefusion.co.za>
 *   - Initial release to the Horde project.
 */

require_once HORDE_LIBS . 'HTTP/WebDAV/Client.php';
require_once HORDE_LIBS . 'HTTP/Request.php';

require_once HORDE_LIBS . 'Horde/iCalendar.php';
require_once HORDE_LIBS . 'Horde/String.php';
require_once HORDE_LIBS . 'Horde/MIME.php';

require_once HORDE_BASE . '/lib/Horde.php';
require_once HORDE_BASE . '/lib/NLS.php';

/**
 * The 'newline' character sequence used by Cyrus IMAP.
 */
define('CYRUS_NL', "\r\n");

/**
 * The Does Not Exist error message returned by the imap_last_error()
 * function if a specified mailbox does not exist on the IMAP server.
 */
define('ERR_MBOX_DNE', 'Mailbox does not exist');

/**
 * The name of an X-Header used in various messages to provide category
 * information for the relevant object (note, task, etc).
 */
define('X_HEAD_CAT', 'X-Horde-Category');

class Kolab {

/* ---------- GENERAL FUNCTIONS ---------- */

    /**
     * Convertes any newlines in the specified text to cyrus format.
     *
     * @access public
     *
     * @param string $text  The text to convert.
     *
     * @return string    $text with all newlines replaced by CRNL.
     */
    function cyrusNewlines($text)
    {
        return preg_replace("/\r\n|\n|\r/s", "\r\n", $text);
    }

    /**
     * Convertes any newlines in the specified text to unix format.
     *
     * @access public
     *
     * @param string $text  The text to convert.
     *
     * @return string    $text with all newlines replaced by NL.
     */
    function unixNewlines($text)
    {
        return preg_replace("/\r\n|\n|\r/s", "\n", $text);
    }

/* ---------- GENERAL KOLAB FUNCTIONS ---------- */

    /**
     * Strips any superfluos domain suffix from an email address. This was
     * done as Kolab uses 'name at maildomain' user names, whereas horde was
     * returning 'name at maildomain@hostname' addresses for the username.
     *
     * @access public
     *
     * @param string $address  The address to strip the domain from, of the
     *                         form 'a at b@c' where any/all of '@b' and '@c'
     *                         may be missing.
     *
     * @return string  A string of the form 'a at b'.
     */
    function stripKolabUsername($address)
    {
        return preg_replace('/^([^@]*(@[^@]*)?).*$/', "\$1", $address);
    }

    /**
     * Strips any superfluos domain suffix from an email address.
     *
     * @access public
     *
     * @param string $address  The address to strip the domain from, of the
     *                         form 'a at b@c' where any/all of '@b' and '@c'
     *                         may be missing.
     *
     * @return string  A string of the form 'a'.
     */
    function stripBaseUsername($address)
    {
        return preg_replace('/^([^@]*).*$/', "\$1", $address);
    }

    /**
     * Returns the username of the currently logged on Horde user, suitable
     * for use in other Kolab authentication procedures (assuming Horde is
     * using LDAP authentication against the Kolab server).
     *
     * @access public
     *
     * @return string  The current users login name.
     */
    function getUser()
    {
        return Kolab::stripKolabUsername(Auth::getAuth());
    }

    /**
     * Returns the password of the currently logged on Horde user, suitable
     * for use in other Kolab authentication procedures (assuming Horde is
     * using LDAP authentication against the Kolab server).
     *
     * @access public
     *
     * @return string  The current users login password.
     */
    function getPassword()
    {
        return Auth::getCredential('password');
    }

    /**
     * Returns the username and password of the currently logged on Horde user,
     * suitable for use in other Kolab authentication procedures (assuming
     * Horde is using LDAP authentication against the Kolab server).
     *
     * @access public
     *
     * @return array  An array of the form (username, password).
     */
    function getAuthentication()
    {
        return array(Kolab::getUser(), Kolab::getPassword());
    }

/* ---------- CYRUS/IMAP FUNCTIONS ---------- */

    /**
     * Returns an IMAP server address formatted for use in the PHP IMAP
     * functions.
     *
     * @access public
     *
     * @param string $host  The address of the IMAP server, of the form
     *                      'host:port'.
     *
     * @return string  An address string suitable for use in functions such as
     *                 imap_open().
     */
    function imapServerURI($host)
    {
        return '{' . $host . '/imap/notls}';
    }

    /**
     * Returns an address of a Cyrus mailbox, formatted for use in the PHP IMAP
     * functions.
     *
     * @access public
     *
     * @param string $host              The address of the Cyrus server.
     * @param optional string $mailbox  The name of the mailbox (defaults to
     *                                  the Inbox).
     *
     * @return string  A mailbox address string suitable for use in functions
     *                 such as imap_open().
     */
    function cyrusMailboxURI($host, $mailbox = '')
    {
        if (!empty($mailbox)) $mailbox = '/' . imap_utf7_encode($mailbox);
        return Kolab::imapServerURI($host) . "INBOX$mailbox";
    }

    /**
     * Tests if $error was the last IMAP error that was generated.
     *
     * @access public
     *
     * @param string $error  The error to test against.
     *
     * @return boolean  True if $error was the last IMAP error.
     */
    function testIMAPError($error)
    {
        return strcasecmp(imap_last_error(), $error) == 0;
    }

    /**
     * Returns an IMAP stream, connected to a specified Cyrus server, with a
     * specified mailbox opened (optionally creating the mailbox if it does
     * not exist). Raises a fatal Horde error if something goes wrong.
     *
     * @access public
     *
     * @param string  $server           The server to open the IMAP connection
     *                                  to.
     * @param string  $mailbox          The mailbox to open on $server.
     * @param optional boolean $create  True if $mailbox is to be created if it
     *                                  does not exist.
     *
     * @return resource  An open IMAP stream on success.
     */
    function openCyrusConnection($server, $mailbox, $create = true)
    {
        list($user, $pass) = Kolab::getAuthentication();
        $box = Kolab::cyrusMailboxURI($server, $mailbox);

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

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

            $imapstream = @imap_open(Kolab::imapServerURI($server), $user, $pass, OP_HALFOPEN);
            if ($imapstream === false)
                Horde::fatal(
                    PEAR::raiseError(sprintf(_('Unable to open mailbox %s: ' . imap_last_error()), $box)),
                    __FILE__,
                    __LINE__
                );

            if (!@imap_createmailbox($imapstream, $box))
                Horde::fatal(
                    PEAR::raiseError(sprintf(_('Unable to create mailbox %s: ' . imap_last_error()), $box)),
                    __FILE__,
                    __LINE__
                );

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

        if ($imapstream === false)
            Horde::fatal(
                PEAR::raiseError(sprintf(_('Unable to open mailbox %s: ' . imap_last_error()), $box)),
                __FILE__,
                __LINE__
            );

        return $imapstream;
    }

    /**
     * Closes a specified IMAP stream, expunging all the messages flagged for
     * deletion.
     *
     * @access public
     *
     * @param resource $imapstream       A reference to an open IMAP stream,
     *                                   connected to the mailbox of interest.
     *                                   This is set to NULL if the connection
     *                                   was terminated successfully.
     * @param optional boolean $expunge  True to expunge the mailbox before
     *                                   closing.
     *
     * @return boolean  True if the stream was closed successfully.
     */
    function closeImapConnection(&$imapstream, $expunge = true)
    {
        $result = true;

        if (isset($imapstream)) {
            $result = @imap_close($imapstream, ($expunge ? CL_EXPUNGE : 0));
            if ($result) $imapstream = NULL;
        }

        return $result;
    }

    /**
     * Ensures that the specified IMAP connection is alive - reconnects
     * if the stream has been disconnected.
     *
     * @access public
     *
     * @param resource $imapstream      A reference to an open IMAP stream,
     *                                  connected to the mailbox of interest.
     * @param string  $server           The server to open the IMAP connection
     *                                  to.
     * @param string  $mailbox          The mailbox to open on $server.
     * @param optional boolean $create  True if $mailbox is to be created if it
     *                                  does not exist.
     *
     */
    function persistentCyrusConnection(&$imapstream, $server, $mailbox, $create = true)
    {
        if (!isset($imapstream) || !imap_ping($imapstream)) {
            if (isset($imapstream)) Kolab::closeImapConnection($imapstream);
            $imapstream = Kolab::openCyrusConnection($server, $mailbox, $create);
        }
    }

    /**
     * Returns a hash of the message headers of a specified message.
     *
     * @access public
     *
     * @param resource $imapstream  An open IMAP stream, connected to the
     *                              mailbox of interest.
     * @param integer $messageid    The message from which to read the headers.
     *
     * @return array  A hash of the headers, where each 'key => value' pair in
     *                the hash corresponds to a 'Name: Value' header line. This
     *                array is empty if an error occurs.
     */
    function getMessageHeaders($imapstream, $messageid)
    {
        $headers = array();

        $headerlines = explode(CYRUS_NL, @imap_fetchheader($imapstream, $messageid, FT_UID));
        foreach ($headerlines as $headerline) {
            if (empty($headerline)) continue;

            list($hname, $hval) = explode(':', MIME::decode($headerline), 2);
            $headers[trim($hname)] = trim($hval);
        }

        return $headers;
    }

    /**
     * Returns the value of a header attribute in a hash returned by
     * Kolab::getHeaderHash(), or a specified default value if the header
     * attribute does not exist.
     *
     * @access public
     *
     * @param array $headers           The message header hash.
     * @param string $name             The attribute to search for.
     * @param optional mixed $default  The value to return if $name does not
     *                                 exist in $headers.
     *
     * @return mixed    The value of $default.
     */
    function getHeaderValue(&$headers, $name, $default = '')
    {
        return array_key_exists($name, $headers) ? $headers[$name] : $default;
    }

    /**
     * Returns the body of a specified mail message.
     *
     * @access public
     *
     * @param resource $imapstream       An open IMAP stream, connected to the
     *                                   mailbox of interest.
     * @param integer $messageid         The message to read.
     * @param optional boolean $convert  True to convert the body from UTF-8 to
     *                                   the current users character set.
     *
     * @return mixed  (string)  The body of mail message $messageid.
     *                (boolean) False on error.
     */
    function getMessageBody($imapstream, $messageid, $convert = false)
    {
        $body = @imap_body($imapstream, $messageid, FT_UID);
        return ($convert ? String::convertCharset($body, 'UTF-8') : $body);
    }

    /**
     * Returns a list of messages in the specified mailbox, sorted by date.
     *
     * @access public
     *
     * @param resource $imapstream  An open IMAP stream, connected to the
     *                              mailbox of interest.
     *
     * @return array    A list of the messages in the mailbox opened on
     *                  $imapstream, sorted by date.
     */
    function getMessages($imapstream)
    {
        return @imap_sort($imapstream, SORTDATE, 0, SE_UID);
    }

    /**
     * Finds a set of messages in the specified mailbox that match the specified
     * criteria.
     *
     * @access public
     *
     * @param resource $imapstream  An open IMAP stream, connected to the
     *                              mailbox of interest.
     * @param string $criteria      The search criteria (see imap_search() for
     *                              the format of this string).
     *
     * @return mixed   (array) A list of the messages in the mailbox opened on
     *                         $imapstream that match $criteria.
     *                 (boolean) False on failure.
     */
    function findMessages($imapstream, $criteria)
    {
        return @imap_search($imapstream, $criteria, SE_UID);
    }

    /**
     * Adds a message to the specified mailbox.
     *
     * @access public
     *
     * @param resource $imapstream  An open IMAP stream, connected to the
     *                              mailbox of interest.
     * @param string $mailbox       The cyrusMailboxURI() formatted name of the
     *                              mailbox to add the message to.
     * @param string $conttype      The mime content type of the message.
     * @param string $subject       The message subject.
     * @param string $body          The message body. Newlines are automatically
     *                              converted to the corrent type.
     * @param optional string $ua   The user agent which is adding the message.
     * @param optional array $headers A hash containing any extra headers to
     *                              append. Each 'key => value' pair is written
     *                              as 'key: value' in the message header.
     * @param optional boolean $convert True to convert the message body to
     *                              UTF-8 before adding the message.
     *
     * @return mixed    (boolean) True on success.
     *                  (object)  PEAR_Error on failure.
     */
    function addMessage($imapstream, $mailbox, $conttype, $subject, $body,
                        $ua = '', $headers = array(), $convert = false)
    {
        $user = MIME::encodeAddress(Kolab::getUser());

        $msg  = 'Content-Type: ' . MIME::encode($conttype . ($convert ? '; Charset="UTF-8"' : '')) . CYRUS_NL;
        $msg .= 'From: ' .  $user . CYRUS_NL;
        $msg .= 'Reply-To: ' .  '' . CYRUS_NL;
        $msg .= 'To: ' .  $user . CYRUS_NL;
        $msg .= 'User-Agent: ' .  MIME::encode('Horde' . (empty($ua) ? '' : '/' . $ua) . '/Kolab') . CYRUS_NL;
        $msg .= 'Date: ' .  date('r') . CYRUS_NL;
        $msg .= 'Subject: ' .  MIME::encode($subject) . CYRUS_NL;

        foreach ($headers as $key => $value)
            $msg .= $key . ': ' . MIME::encode($value) . CYRUS_NL;

        if ($convert) {
            $body = String::convertCharset($body, NLS::getCharset(), 'UTF-8');
        }

        $msg .= CYRUS_NL . Kolab::cyrusNewlines($body);

        if (!@imap_append($imapstream, $mailbox, $msg))
            return PEAR::raiseError(
                sprintf(
                    _('Unable to add message from %s to mailbox %s: ' . imap_last_error()),
                    $user,
                    $mailbox
                )
            );

        return true;
    }

    /**
     * Deletes a message in the specified mailbox.
     *
     * @access public
     *
     * @param resource $imapstream       An open IMAP stream, connected to the
     *                                   mailbox of interest.
     * @param integer $messageid         The message to delete.
     * @param optional boolean $expunge  True to expunge the mailbox after
     *                                   deletion.
     */
    function deleteMessage($imapstream, $messageid, $expunge = false)
    {
        @imap_delete($imapstream, $messageid, FT_UID);
        if ($expunge) @imap_expunge($imapstream);
    }

/* ---------- WEBDAV FUNCTIONS ---------- */

    /**
     * Retrieves the contents of a specified users VFB file, stored on a
     * specified WebDAV server.
     *
     * @access public
     *
     * @param string $server         The address of the WebDAV server, of the
     *                               form host:port.
     * @param string $folder         The folder on $server where the VFB file is
     *                               stored.
     * @param optional string $user  The name of the user whose VFB file is to
     *                               be retrieved. Defaults to the current user.
     *
     * @return mixed  (string) The contents of the users VFB file, suitable for
     *                    parsing by a Horde_iCalendar object.
     *                (object) PEAR_Error on failure.
     */
    function retrieveFreeBusy($server, $folder, $user = '')
    {
        list($uname, $pass) = Kolab::getAuthentication();
        if (empty($user)) $user = $uname;

        $http = &new HTTP_Request(
            "http://$server/$folder/$user.vfb",
            array(
                'user'  => $uname,
                'pass'  => $pass
            )
        );

        $result = $http->sendRequest();
        if ($result !== true) return $result;

        $status = $http->getResponseCode();
        if ($status != 200) {
            // Try `user' instead of `user at domain', for backward compatibility
            $http->reset(
                "http://$server/$folder/" . Kolab::stripBaseUsername($user) . ".vfb",
                array(
                    'user'  => $uname,
                    'pass'  => $pass
                )
            );
            $status = $http->getResponseCode();
            if ($status != 200)
                return PEAR::raiseError(sprintf(
                    _('Unable to retrieve free/busy information for user %s on server %s'),
                    $user,
                    $server
                ));
        }

        return $http->getResponseBody();
    }

    /**
     * Stores the specified VFB data in the current users VFB file on the
     * specified WebDAV server.
     *
     * @access public
     *
     * @param string $server  The address of the WebDAV server, of the form
     *                        host:port.
     * @param string $folder  The folder on $server where the VFB file is stored.
     * @param string $vfb     The new VFB data to store.
     *
     * @return mixed  (boolean) True on success.
     *                (object)  PEAR_Error on failure.
     */
    function storeFreeBusy($server, $folder, $vfb)
    {
        list($user, $pass) = Kolab::getAuthentication();

        $path = 'webdav://' . urlencode($user) . ':' . urlencode($pass) . "@$server/$folder/$user.vfb";

        if (!$fh = fopen($path, 'w'))
            return PEAR::raiseError(sprintf(
                _('Unable to store free/busy information for user %s on server %s'),
                $user,
                $server
            ));

        if (!fwrite($fh, $vfb)) {
            fclose($fh);
            return PEAR::raiseError(sprintf(
                _('Unable to store free/busy information for user %s on server %s'),
                $user,
                $server
            ));
        }

        fclose($fh);

        return true;
    }

    /**
     * Retrieves the value of a specified attribute in a Horde_iCalendar object,
     * or a default value if the attribute does not exist.
     *
     * @access private
     *
     * @param Horde_iCalendar* $component  The component of interest.
     * @param string $attribute            The attribute of interest.
     * @param mixed $default               The value to return if $attribute
     *                                     does not exist.
     *
     * @return mixed  (string) The value of $attribute.
     *                (mixed) $default if $attribute does not exist.
     */
    function _getAttr(&$component, $attribute, $default = '')
    {
        $tmp = $component->getAttribute($attribute);
        if (is_a($tmp, 'PEAR_Error')) return $default;
        return $tmp;
    }

    /**
     * Compiles and uploads a free/busy VFB file for the current user. The busy
     * times are obtained from reading iCalendar events in the users calendar
     * folder. By default the VFB file spans 8 weeks, starting at the time when
     * the function is called.
     *
     * @access private
     *
     * @param optional string $calserver  The server address where the calendar
     *                                    folder is located.
     * @param optional string $calfolder  The name of the calendar folder.
     * @param optional string $vfbserver  The server address where the free/busy
     *                                    folder is located.
     * @param optional string $vfbfolder  The name of the free/busy folder.
     * @param optional integer $period    The period (in seconds) from which
     *                                    busy information should be drawn from.
     *                                    This period begins when the function
     *                                    is called.
     *
     * @return mixed  (boolean) True on success.
     *                (object)  PEAR_Error on failure.
     */
    function compileFreeBusy($calserver = 'localhost', $calfolder = 'Calendar',
                             $vfbserver = 'localhost', $vfbfolder = '/freebusy/',
                             $period = 4838400)
    {
        $imap = Kolab::openCyrusConnection($calserver, $calfolder);

        $vfbcont = new Horde_iCalendar;
        $vfb =& $vfbcont->newComponent('VFREEBUSY', $vfbcont);

        $cal = new Horde_iCalendar;
        $cal->setAttribute('ORGANIZER', 'MAILTO:' . Kolab::getUser());

        $vfbstart = time();
        $vfbend = $vfbstart + $period;

        $vfb->setAttribute('DTSTART', $vfbstart);
        $vfb->setAttribute('DTEND', $vfbend);

        $msgs = Kolab::getMessages($imap);
        foreach ($msgs as $msg) {
            $cal->parsevCalendar(Kolab::getMessageBody($imap, $msg));

            $components = $cal->getComponents();
            $ispresent = false;

            foreach ($components as $c)
            {
                if ($ispresent) break;
                if (!is_a($c, 'Horde_iCalendar_vevent')) continue;
                $ispresent = true;

                $start = Kolab::_getAttr($c, 'DTSTART', 0);
                $end = Kolab::_getAttr($c, 'DTEND', 0);

                if ($start > $vfbend || $end < $vfbstart) continue;

                $start = max($start, $vfbstart);
                $end = min($end, $vfbend);

                $vfb->addBusyPeriod('BUSY', $start, $end);
            }
        }

        Kolab::closeImapConnection($imap);

        $vfb->simplify();
        $vfbcont->addComponent($vfb);
        $vfbfile = $vfbcont->exportvCalendar();

        return Kolab::storeFreeBusy($vfbserver, $vfbfolder, $vfbfile);
    }

}


More information about the dev mailing list