stephan: server/horde CHANGELOG, NONE, 1.1 Kolab.php, NONE, 1.1 String.php, NONE, 1.1 iCalendar.php, NONE, 1.1 kronolith_kolab.php, NONE, 1.1 mnemo_kolab.php, NONE, 1.1 nag_kolab.php, NONE, 1.1 turba_kolab.php, NONE, 1.1 valarm.php, NONE, 1.1 vevent.php, NONE, 1.1 vfreebusy.php, NONE, 1.1 vjournal.php, NONE, 1.1 vtimezone.php, NONE, 1.1 vtodo.php, NONE, 1.1

cvs at intevation.de cvs at intevation.de
Mon Sep 1 09:54:35 CEST 2003


Author: stephan

Update of /kolabrepository/server/horde
In directory doto:/tmp/cvs-serv18112

Added Files:
	CHANGELOG Kolab.php String.php iCalendar.php 
	kronolith_kolab.php mnemo_kolab.php nag_kolab.php 
	turba_kolab.php valarm.php vevent.php vfreebusy.php 
	vjournal.php vtimezone.php vtodo.php 
Log Message:
Files needed for the webclient to function


--- NEW FILE: CHANGELOG ---
(This appears to be a binary file; contents omitted.)

--- NEW FILE: Kolab.php ---
<?php

/**
 * Horde Kolab utility library, providing various utility functions for dealing with the Kolab server.
 * Consists mainly of IMAP related functions.
 *
 * Copyright (C) 2003 Code Fusion, cc.
 * Written by Stuart Bingë <s.binge at codefusion.co.za>
 *
 * Created 2003-08-28
 * Last Modified 2003-08-29 by Stuart Bingë
 */

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;
  }

  /**
   * 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()
    {
      $userID = Auth::getAuth();
      //return array(substr($userID, 0, strrpos($userID, "@")), Auth::getCredential('password'));
      return array($userID, 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.
   */
  function GetMessageList($imapstream)
  {
    return imap_sort($imapstream, SORTDATE, 0);
  }

  /**
   * 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);
  }
}

--- NEW FILE: String.php ---
<?php
/**
 * The String:: class provides methods for charset and locale safe string
 * manipulation.
 *
 * $Horde: horde/lib/String.php,v 1.22 2003/06/26 21:24:24 jan Exp $
 *
 * Copyright 2003 Jan Schneider <jan at horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Jan Schneider <jan at horde.org>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde
 */
class String {

    /**
     * Converts a string from one charset to another.
     *
     * Works only if either the iconv or the mbstring extension
     * are present and best if both are available.
     * The original string is returned if conversion failed or none
     * of the extensions were available.
     *
     * @access public
     *
     * @param mixed $input         The data to be converted. If $input is an
     *                             an array, the array's values get converted
     *                             recursively.
     * @param string $from         The string's current charset.
     * @param optional string $to  The charset to convert the string to.
     *
     * @return string  The converted string.
     */
    function convertCharset($input, $from, $to = null)
    {
        if (is_array($input)) {
            $tmp = array();
            foreach ($input as $key => $val) {
                $tmp[String::convertCharset($key, $from, $to)] = String::convertCharset($val, $from, $to);
            }
            return $tmp;
        }
        if (is_object($input)) {
            $vars = get_object_vars($input);
            foreach ($vars as $key => $val) {
                $input->$key = String::convertCharset($val, $from, $to);
            }
            return $input;
        }

        if (!is_string($input)) {
            return $input;
        }

        global $nls;

        $output = false;

        /* Get the user's default character set if none passed in. */
        if (is_null($to)) {
            $to = NLS::getCharset();
        }

        /* If the from and to chaacter sets are identical, return now. */
        $str_from = String::lower($from);
        $str_to = String::lower($to);
        if ($str_from == $str_to) {
            return $input;
        }

        /* Use utf8_[en|de]code() if possible. */
        $str_from_check = (($str_from == 'iso-8859-1') || ($str_from == 'us-ascii'));
        if ($str_from_check && ($str_to == 'utf-8')) {
            return utf8_encode($input);
        }

        $str_to_check = (($str_to == 'iso-8859-1') || ($str_to == 'us-ascii'));
        if (($str_from == 'utf-8') && $str_to_check) {
            return utf8_decode($input);
        }

        /* First try iconv with transliteration. */
        if (Horde::extensionExists('iconv')) {
            ini_set('track_errors', 1);
            /* We need to tack an extra character temporarily
               because of a bug in iconv() if the last character
               is not a 7 bit ASCII character. */
            $output = @iconv($from, $to . '//TRANSLIT', $input . 'x');
            if (isset($php_errormsg)) {
                $output = false;
            } else {
                $output = substr($output, 0, -1);
            }
            ini_restore('track_errors');
        }

        /* Next try mbstring. */
        if (!$output && Horde::extensionExists('mbstring')) {
            $output = @mb_convert_encoding($input, $to, $from);
        }

        /* At last try imap_utf7_[en|de]code if appropriate. */
        if (!$output && Horde::extensionExists('imap')) {
            if ($str_from_check && ($str_to == 'utf7-imap')) {
                return @imap_utf7_encode($input);
            }
            if (($str_from == 'utf7-imap') && $str_to_check) {
                return @imap_utf7_decode($input);
            }
        }

        return (!$output) ? $input : $output;
    }

    /**
     * Makes a string lowercase.
     *
     * @param string $string  The string to be converted.
     * @param bool $locale    If true the string will be converted based on a
     *                        given charset, locale independent else.
     * @param string $charset If $locale is true, the charset to use when
     *                        converting. If not provided the current charset.
     *
     * @return string         The string with lowercase characters
     */
    function lower($string, $locale = false, $charset = null)
    {
        static $lowers;

        if ($locale) {
            /* The existence of mb_strtolower() depends on the platform,
               as of PHP 4.3.0. */
            if (Horde::extensionExists('mbstring') &&
                function_exists('mb_strtolower')) {
                if (is_null($charset)) {
                    $charset = NLS::getCharset();
                }
                $ret = @mb_strtolower($string, $charset);
                if (!empty($ret)) {
                    return $ret;
                }
            }
            return strtolower($string);
        }

        if (!isset($lowers)) {
            $lowers = array();
        }
        if (!isset($lowers[$string])) {
            $language = setlocale(LC_CTYPE, 0);
            setlocale(LC_CTYPE, 'en');
            $lowers[$string] = strtolower($string);
            setlocale(LC_CTYPE, $language);
        }

        return $lowers[$string];
    }

    /**
     * Makes a string uppercase.
     *
     * @param string $string  The string to be converted.
     * @param bool $locale    If true the string will be converted based on a
     *                        given charset, locale independent else.
     * @param string $charset If $locale is true, the charset to use when
     *                        converting. If not provided the current charset.
     *
     * @return string         The string with uppercase characters
     */
    function upper($string, $locale = false, $charset = null)
    {
        static $uppers;

        if ($locale) {
            /* The existence of mb_strtoupper() depends on the platform,
               as of PHP 4.3.0. */
            if (Horde::extensionExists('mbstring') &&
                function_exists('mb_strtoupper')) {
                if (is_null($charset)) {
                    $charset = NLS::getCharset();
                }
                $ret = @mb_strtoupper($string, $charset);
                if (!empty($ret)) {
                    return $ret;
                }
            }
            return strtoupper($string);
        }

        if (!isset($uppers)) {
            $uppers = array();
        }
        if (!isset($uppers[$string])) {
            $language = setlocale(LC_CTYPE, 0);
            setlocale(LC_CTYPE, 'en');
            $uppers[$string] = strtoupper($string);
            setlocale(LC_CTYPE, $language);
        }

        return $uppers[$string];
    }

    /**
     * Returns part of a string.
     *
     * @param string $string  The string to be converted.
     * @param int $start      The part's start position, zero based.
     * @param int $length     The part's length.
     * @param string $charset The charset to use when calculating the part's
     *                        position and length, defaults to current charset.
     *
     * @return string         The string's part.
     */
    function substr($string, $start, $length = null, $charset = null)
    {
        if (Horde::extensionExists('mbstring')) {
            if (is_null($charset)) {
                $charset = NLS::getCharset();
            }
            if (is_null($length)) {
                $length = String::length($string, $charset);
            }
            $ret = @mb_substr($string, $start, $length, $charset);
            if (!empty($ret)) {
                return $ret;
            }
        }
        if (is_null($length)) {
            $length = String::length($string);
        }
        return substr($string, $start, $length);
    }

    /**
     * Returns the character (not byte) length of a string.
     *
     * @param string $string  The string to return the length of.
     * @param string $charset The charset to use when calculating the string's
     *                        length.
     *
     * @return string         The string's part.
     */
    function length($string, $charset = null)
    {
        if (Horde::extensionExists('mbstring')) {
            if (is_null($charset)) {
                $charset = NLS::getCharset();
            }
            $ret = @mb_strlen($string, $charset);
            if (!empty($ret)) {
                return $ret;
            }
        }
        return strlen($string);
    }

    /**
     * Returns the numeric position of the first occurrence of $needle in
     * the $haystack string.
     *
     * @param string $haystack  The string to search through.
     * @param string $needle    The string to search for.
     * @param int $offset       Allows to specify which character in haystack
     *                          to start searching.
     * @param string $charset   The charset to use when searching for the
     *                          $needle string.
     *
     * @return int              The position of first occurrence.
     */
    function pos($haystack, $needle, $offset = 0, $charset = null)
    {
        if (Horde::extensionExists('mbstring')) {
            if (is_null($charset)) {
                $charset = NLS::getCharset();
            }
            ini_set('track_errors', 1);
            $ret = @mb_strpos($haystack, $needle, $offset, $charset);
            ini_restore('track_errors');
            if (!isset($php_errormsg)) {
                return $ret;
            }
        }
        return strpos($haystack, $needle, $offset);
    }

    function isAlpha($string, $charset = null)
    {
        /*
        if (Horde::extensionExists('mbstring')) {
            if (is_null($charset)) {
                $charset = NLS::getCharset();
            }
            mb_regex_encoding($charset);
            return mb_ereg_match('/\\w+/', $string) &&
                   !strstr($string, '_') &&
                   !mb_ereg_match('/\\d+/', $string);
        }
        */
        return ctype_alpha($string);
    }

    /**
     * Returns true if every character in the parameter is a lowercase
     * letter in the current locale.
     *
     * @param $string   The string to test.
     * @param $charset  The charset to use when testing the string.
     *
     * @return bool     True if the parameter was lowercase.
     */
    function isLower($string, $charset = null)
    {
        return ((String::lower($string, true, $charset) === $string) &&
                String::isAlpha($string, $charset));
    }

    /**
     * Returns true if every character in the parameter is an uppercase
     * letter in the current locale.
     *
     * @param $string   The string to test.
     * @param $charset  The charset to use when testing the string.
     *
     * @return bool     True if the parameter was uppercase.
     */
    function isUpper($string, $charset = null)
    {
        return ((String::upper($string, true, $charset) === $string) &&
                String::isAlpha($string, $charset));
    }

}

--- NEW FILE: iCalendar.php ---
<?php

require_once(HORDE_BASE . '/lib/String.php');

/**
 * Class representing iCalendar files
 *
 * $Horde: horde/lib/iCalendar.php,v 1.18 2003/07/03 09:22:13 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
class Horde_iCalendar {

    var $_container = null;

    var $_attributes = array();

    var $_components = array();

    /**
     * Return a reference to a new component
     *
     * @param String $type       The type of component to return
     * @param Object $container  A container that this component
     *                           will be associtated with.
     *
     * @return Object Reference to a Horde_iCalendar_* object as specified.
     */
    function &newComponent($type, &$container)
    {
        $class = 'Horde_iCalendar_' . String::lower($type);
        @include_once dirname(__FILE__) . '/iCalendar/' . String::lower($type) . '.php';
        if (class_exists($class)) {
            return new $class($container);
        } else {
            // should return an dummy x-unknown type class here.
            return false;
        }
    }

    /**
     * Include all the component classes, usefull if unserializing
     * objects and needing to garantee all the classes have been defined.
     */
    function includeComponentClasses()
    {
        static $allreadyRun;

        if (!isset($allreadyRun) || $allreadyRun !== true) {
            $dir = opendir(dirname(__FILE__) . '/iCalendar/');
            while (($file = readdir($dir)) !== false) {
                if (strstr($file, '.php') !== false) {
                    include_once dirname(__FILE__) . '/iCalendar/' . $file;
                }
            }
            closedir($dir);
        }
        $allreadyRun == true;
    }

    /**
    * Set the value of an attribute
    *
    * @param String $name   The name of the attribute
    * @param String $value  The value of the attribute
    * @param Array  $params (optional) Array containing any addition
    *                       parameters for this attribute.
    */
  function setAttribute($name, $value, $params = array())
  {
    $found = false;
    for ($i = 0; $i < count($this->_attributes); $i++)
    {
      if ($this->_attributes[$i]['name'] == $name)
      {
        $this->_attributes[$i]['params'] = $params;
        $this->_attributes[$i]['value'] = $value;
        $found = true;
      }
    }
    if (!$found)  // If not found, append
      $this->_attributes[] = array('name' => $name, 'params' => $params, 'value' => $value);
  }

  function clearAttribute($name)
  {
    $cnt = count($this->_attributes);
    for ($i = 0; $i < $cnt; $i++)
    {
      if ($this->_attributes[$i]['name'] == $name)
        unset($this->_attributes[$i]);
    }
  }

    /**
     * Get the value of an attribute
     *
     * @param String  $name   The name of hte attribute
     * @param Boolean $params Return the parameters for this attribute
     *                        instead of its value
     *
     * @return mixed    (boolean) False if the attribute does not exist
     *                  (string)  The value of the attribute
     *                  (array)   The parameters for the attribute or
     *                            multiple values for an attribute
     */
    function getAttribute($name, $params = false)
    {
        $result = array();
        foreach ($this->_attributes as $attribute) {
            if ($attribute['name'] == $name) {
                if ($params) {
                    $result[] = $attribute['params'];
                } else {
                    $result[] = $attribute['value'];
                }
            }
        }
        if (count($result) == 0) {
            return PEAR::raiseError(_("Attribute Not Found"));
        } if (count($result) == 1 && !$params) {
            return $result[0];
        } else {
            return $result;
        }
    }

    /**
     * Get all attributes
     *
     * @return Array    Array containsing all the attributes and their types.
     *
     */
    function getAllAttributes()
    {
        return $this->_attributes;
    }

    /**
     * Add a vCalendar component (eg vEvent)
     *
     * @param Horde_iCalendar_* $component Component to add
     */
    function addComponent(&$component)
    {
        $this->_components[] =& $component;
    }

  function &addNewComponent($type)
  {
    $obj = Horde_iCalendar::newComponent($type, $this);
    if (!$obj) return NULL;
    $this->addComponent($obj);
    return $obj;
  }

    /**
     * Retrieve all the components.
     *
     * @return Array    Array of Horde_iCalendar_* objects
     */
    function getComponents()
    {
        return $this->_components;
    }

    /**
     * Retrieve a specific component.
     *
     * @param Integer $idx  The index of the object to retrieve.
     *
     * @return mixed    (boolean) False if the index does not exist.
     *                  (Horde_iCalendar_*) The requested component.
     */
    function getComponent($idx)
    {
        if (array_key_exists($idx, $this->_components)) {
            return $this->_components[$idx];
        } else {
            return false;
        }
    }

    /**
     * Export as vCalendar format
     */
    function exportvCalendar()
    {
        // Default values
        $requiredAttributes['VERSION'] = '2.0';
        $requiredAttributes['PRODID'] = '-//The Horde Project//Horde_iCalendar Library, Horde 3.0-cvs //EN';
        $requiredAttributes['METHOD'] = 'PUBLISH';

        foreach ($requiredAttributes as $name => $default_value) {
            if (is_a($this->getattribute($name), 'PEAR_Error')) {
                $this->setAttribute($name, $default_value);
            }
        }

        return $this->_exportvData('VCALENDAR') . "\n";
    }

  function &findComponent($childclass)
  {
    $childclass = String::lower($childclass);
    for ($i = 0; $i < count($this->_components); $i++)
      if (is_a($this->_components[$i], 'Horde_iCalendar_' . $childclass))
        return $this->_components[$i];

    return NULL;
  }

  function clear()
  {
    $this->_components = array();
    $this->_attributes = array();
  }

  function generateUID()
  {
    return uniqid('Horde.iCal-');
  }

    /**
     * Parse a string containing vCalendar data.
     *
     * @param String $text  The data to parse
     * @param String $base  The type of the base object.
     *
     */
    function parsevCalendar($text, $base = 'VCALENDAR', $clear = true)
    {
      if ($clear) $this->clear();

        if (preg_match('/(BEGIN:' . $base . '\r?\n)([\W\w]*)(END:' . $base . '\r?\n?)/', $text, $matches)) {
            $vCal = $matches[2];
        } else {
            return false;
        }

        // All sub components
        $matches = null;
        if (preg_match_all('/(BEGIN:)([\W\w]*)\r?\n?([\W\w]*)(END:)\2(\r\n|\r|\n)/', $vCal, $matches)) {
            foreach ($matches[0] as $key => $data) {
                $type = $matches[2][$key];

                $component = &Horde_iCalendar::newComponent(trim($type), $this);
                $component->parsevCalendar($data);

                $this->addComponent($component);

                // Remove from the vCalendar data
                $vCal = str_replace($data, '', $vCal);
            }
        }

        // Unfold any folded lines
        $vCal = preg_replace ('/(\r|\n)+ /', '', $vCal);

        // Parse the remain attributes
        if (preg_match_all('/(.*):(.*)(\r|\n)+/', $vCal, $matches)) {
            foreach ($matches[0] as $attribute) {
                preg_match('/([^;^:]*)((;[^:]*)?):(.*)/', $attribute, $parts);
                $tag = trim($parts[1]);
                $value = trim($parts[4]);
                $params = array();

                if (!empty($parts[2])) {
                    preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts);
                    foreach ($param_parts[2] as $key => $paramName) {
                        $paramValue = $param_parts[4][$key];
                        $params[$paramName] = $paramValue;
                    }
                }

                switch ($tag) {
                    case 'DESCRIPTION':
                        $value = preg_replace('/\\\\,/', ',', $value);
                        $value = preg_replace('/\\\\n/', "\n", $value);
                        $this->setAttribute($tag, $value, $params);
                        break;

                    // Date fields
                    case 'DTSTAMP':
                    case 'COMPLETED':
                    case 'CREATED':
                    case 'LAST-MODIFIED':
                        $this->setAttribute($tag, $this->_parseDateTime($value), $params);
                        break;

                    case 'DTEND':
                    case 'DTSTART':
                    case 'DUE':
                    case 'RECURRENCE-ID':
                        if (array_key_exists('VALUE', $params)) {
                            if ($params['VALUE'] == 'DATE') {
                                $this->setAttribute($tag, $this->_parseDate($value), $params);
                            } else {
                                $this->setAttribute($tag, $this->_parseDateTime($value), $params);
                            }
                        } else {
                            $this->setAttribute($tag, $this->_parseDateTime($value), $params);
                        }
                        break;

                    case 'RDATE':
                        if (array_key_exists('VALUE', $params)) {
                            if ($params['VALUE'] == 'DATE') {
                                $this->setAttribute($tag, $this->_parseDate($value), $params);
                            } else if ($params['VALUE'] == 'PERIOD') {
                                $this->setAttribute($tag, $this->_parsePeriod($value), $params);
                            } else {
                                $this->setAttribute($tag, $this->_parseDateTime($value), $params);
                            }
                        } else {
                            $this->setAttribute($tag, $this->_parseDateTime($value), $params);
                        }
                        break;

                    case 'TRIGGER':
                        if (array_key_exists('VALUE', $params)) {
                            if ($params['VALUE'] == 'DATE-TIME') {
                                $this->setAttribute($tag, $this->_parseDateTime($value), $params);
                            } else {
                                $this->setAttribute($tag, $this->_parseDuration($value), $params);
                            }
                        } else {
                            $this->setAttribute($tag, $this->_parseDuration($value), $params);
                        }
                        break;

                    case 'EXDATE':
                        // comma seperated dates
                        $values = array();
                        $dates = array();
                        preg_match_all('/,([^,]*)/', ',' . $value, $values);

                        foreach ($values as $value) {
                            if (array_key_exists('VALUE', $params)) {
                                if ($params['VALUE'] == 'DATE-TIME') {
                                    $dates[] = $this->_parseDateTime($value);
                                } else if ($params['VALUE'] == 'DATE') {
                                    $dates[] = $this->_parseDate($value);
                                }
                            } else {
                                $dates[] = $this->_parseDateTime($value);
                            }
                        }
                        $this->setAttribute($tag, $dates, $params);
                        break;

                    // Duration fields
                    case 'DURATION':
                        $this->setAttribute($tag, $this->_parseDuration($value), $params);
                        break;

                    // Period of time fields
                    case 'FREEBUSY':
                        $values = array();
                        $periods = array();
                        preg_match_all('/,([^,]*)/', ',' . $value, $values);
                        foreach ($values[1] as $value) {
                            $periods[] = $this->_parsePeriod($value);
                        }

                        $this->setAttribute($tag, $periods, $params);
                        break;

                    // UTC offset fields
                    case 'TZOFFSETFROM':
                    case 'TZOFFSETTO':
                        $this->setAttribute($tag, $this->_parseUtcOffset($value), $params);
                        break;

                    // Integer fields
                    case 'PERCENT-COMPLETE':
                    case 'PRIORITY':
                    case 'REPEAT':
                    case 'SEQUENCE':
                        $this->setAttribute($tag, intval($value), $params);
                        break;

                    // Geo fields
                    case 'GEO':
                        $floats = split(';', $value);
                        $value['latitude'] = floatval($floats[0]);
                        $value['longitude'] = floatval($floats[1]);
                        $this->setAttribute($tag, $value, $params);
                        break;

                    // Recursion fields
                    case 'EXRULE':
                    case 'RRULE':
                    default:
                        // string fields
                        $this->setAttribute($tag, trim($value), $params);
                        break;
                }
            }
        }

        return true;
    }

    /**
     * Export this component as vCal format
     *
     * @param String $base  (optional) The type of the base object.
     *
     * @return String vCal format data
     */
    function _exportvData($base = 'VCALENDAR')
    {
        $newline = "\n";

        $result  = "BEGIN:" . $base . $newline;

        foreach ($this->_attributes as $attribute) {
            $name = $attribute['name'];
            $params = $attribute['params'];
            $params_str = '';

            if (count($params) > 0) {
                foreach ($params as $param_name => $param_value) {
                    $params_str .= ";$param_name=$param_value";
                }
            }

            $value = $attribute['value'];
            switch ($name) {
                // Date fields
                case 'DTSTAMP':
                case 'COMPLETED':
                case 'CREATED':
                case 'LAST-MODIFIED':
                    $value = $this->_exportDateTime($value);
                    break;

                case 'DTEND':
                case 'DTSTART':
                case 'DUE':
                case 'RECURRENCE-ID':
                    if (array_key_exists('VALUE', $params)) {
                        if ($params['VALUE'] == 'DATE') {
                            $value = $this->_exportDate($value);
                        } else {
                            $value = $this->_exportDateTime($value);
                        }
                    } else {
                        $value = $this->_exportDateTime($value);
                    }
                    break;

                case 'RDATE':
                    if (array_key_exists('VALUE', $params)) {
                        if ($params['VALUE'] == 'DATE') {
                            $value = $this->_exportDate($value);
                        } else if ($params['VALUE'] == 'PERIOD') {
                            $value = $this->_exportPeriod($value);
                        } else {
                            $value = $this->_exportDateTime($value);
                        }
                    } else {
                        $value = $this->_exportDateTime($value);
                    }
                    break;

                case 'TRIGGER':
                    if (array_key_exists('VALUE', $params)) {
                        if ($params['VALUE'] == 'DATE-TIME') {
                            $value = $this->_exportDateTime($value);
                        } else if ($params['VALUE'] == 'DURATION') {
                            $value = $this->_exportDuration($value);
                        }
                    } else {
                        $value = $this->_exportDuration($value);
                    }
                    break;

                // Duration fields
                case 'DURATION':
                    $value = $this->_exportDuration($value);
                    break;

                // Period of time fields
                case 'FREEBUSY':
                    $value_str = '';
                    foreach ($value as $period) {
                        $value_str .= empty($value_str) ? '' : ',';
                        $value_str .= $this->_exportPeriod($period);
                    }
                    $value = $value_str;
                    break;


                // UTC offset fields
                case 'TZOFFSETFROM':
                case 'TZOFFSETTO':
                    $value = $this->_exportUtcOffset($value);
                    break;

                // Integer fields
                case 'PERCENT-COMPLETE':
                case 'PRIORITY':
                case 'REPEAT':
                case 'SEQUENCE':
                    $value = "$value";
                    break;

                // Geo fields
                case 'GEO':
                    $value = $value['latitude'] . ',' . $value['longitude'];
                    break;

                // Recursion fields
                case 'EXRULE':
                case 'RRULE':

            }

            $attr_string = "$name$params_str:$value";
            $result .= $this->_foldLine($attr_string, $newline) . $newline;
        }

        foreach ($this->getComponents() as $component) {
            $result .= $component->exportvCalendar($this) . $newline;
        }

        $result .= "END:" . $base;

        return $result;
    }

    /**
     * Parse a UTC Offset field
     */
    function _parseUtcOffset($text)
    {
        $offset = array();
        if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) {
            $offset['ahead']  = (boolean)($timeParts[1] == '+');
            $offset['hour']   = intval($timeParts[2]);
            $offset['minute'] = intval($timeParts[3]);
            if (array_key_exists(4, $timeParts)) {
                $offset['second'] = intval($timeParts[4]);
            }
            return $offset;
        } else {
            return false;
        }
    }

    /**
     * Export a UTC Offset field
     */
    function _exportUtcOffset($value)
    {
        $offset = $value['ahead'] ? '+' : '-';
        $offset .= sprintf('%02d%02d',
                    $value['hour'], $value['minute']);
        if (array_key_exists('second', $value)) {
            $offset .= sprintf('%02d', $value['second']);
        }

        return $offset;
    }

    /**
     * Parse a Time Period field
     */
    function _parsePeriod($text)
    {
        $periodParts = split('/', $text);

        $start = $this->_parseDateTime($periodParts[0]);

        if ($duration = $this->_parseDuration($periodParts[1])) {
            return array('start' => $start, 'duration' => $duration);
        } else if($end = $this->_parseDateTime($periodParts[1])) {
            return array('start' => $start, 'end' => $end);
        }
    }

    /**
     * Export a Time Period field
     */
    function _exportPeriod($value)
    {
        $period = $this->_exportDateTime($value['start']);
        $period .= '/';
        if (array_key_exists('duration', $value)) {
            $period .= $this->_exportDuration($value['duration']);
        } else {
            $period .= $this->_exportDateTime($value['end']);
        }
        return $period;
    }

    /**
     * Parse a DateTime field
     */
    function _parseDateTime($text)
    {
        $dateParts = split('T', $text);
        if (count($dateParts) != 2 && !empty($text)) {
            // not a date time field but may be just a date field
            if (!$date = $this->_parseDate($text)) {
                return $date;
            }
            return gmmktime (0, 0, 0, $date['month'], $date['mday'], $date['year']);
        }

        if (!$date = $this->_parseDate($dateParts[0])) {
            return $date;
        }
        if (!$time = $this->_parseTime($dateParts[1])) {
            return $time;
        }

        if ($time['zone'] == 'UTC') {
            return gmmktime ($time['hour'], $time['minute'], $time['second'],
                        $date['month'], $date['mday'], $date['year']);
        } else {
            return mktime ($time['hour'], $time['minute'], $time['second'],
                        $date['month'], $date['mday'], $date['year']);
        }
    }

    /**
     * Export a DateTime field
     */
    function _exportDateTime($value)
    {
        $temp = array();
        if (!is_object($value)) {
            $TZOffset  = 3600 * substr(date('O'), 0, 3);
            $TZOffset += 60 * substr(date('O'), 3, 2);
            $value -= $TZOffset;

            $temp['zone']   = 'UTC';
            $temp['year']   = date('Y', $value);
            $temp['month']  = date('n', $value);
            $temp['mday']   = date('j', $value);
            $temp['hour']   = date('G', $value);
            $temp['minute'] = date('i', $value);
            $temp['second'] = date('s', $value);

        } else {
            $dateOb = $value;

            // minutes
            $TZOffset = substr(date('O'), 3, 2);
            $thisMin = $dateOb->min - $TZOffset;

            // hours
            $TZOffset = substr(date('O'), 0, 3);
            $thisHour = $dateOb->hour - $TZOffset;

            if ($thisMin < 0) {
                $thisHour -= 1;
                $thisMin += 60;
            }

            if ($thisHour < 0) {
                require_once 'Date/Calc.php';
                $prevday = Date_Calc::prevDay($dateOb->mday, $dateOb->month, $dateOb->year);
                $dateOb->mday  = substr($prevday, 6, 2);
                $dateOb->month = substr($prevday, 4, 2);
                $dateOb->year  = substr($prevday, 0, 4);
                $thisHour += 24;
            }

            $temp['zone']   = 'UTC';
            $temp['year']   = $dateOb->year;
            $temp['month']  = $dateOb->month;
            $temp['mday']   = $dateOb->mday;
            $temp['hour']   = $thisHour;
            $temp['minute'] = $dateOb->min;
            $temp['second'] = $dateOb->sec;
        }

        return Horde_iCalendar::_exportDate($temp) . 'T' . Horde_iCalendar::_exportTime($temp);
    }

    /**
     * Parse a Time field
     */
    function _parseTime($text)
    {
        if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) {
            $time['hour'] = intval($timeParts[1]);
            $time['minute'] = intval($timeParts[2]);
            $time['second'] = intval($timeParts[3]);
            if (array_key_exists(4, $timeParts)) {
                $time['zone'] = 'UTC';
            } else {
                $time['zone'] = 'Local';
            }
            return $time;
        } else {
            return false;
        }
    }

    /**
     * Export a Time field
     */
    function _exportTime($value)
    {
        $time = sprintf('%02d%02d%02d',
                        $value['hour'], $value['minute'], $value['second']);
        if ($value['zone'] == 'UTC') {
            $time .= 'Z';
        }
        return $time;
    }

    /**
     * Parse a Date field
     */
    function _parseDate($text)
    {
        if (strlen($text) != 8) {
            return false;
        }

        $date['year']  = intval(substr($text, 0, 4));
        $date['month'] = intval(substr($text, 4, 2));
        $date['mday']  = intval(substr($text, 6, 2));

        return $date;
    }

    /**
     * Export a Date field
     */
    function _exportDate($value)
    {
        return sprintf('%04d%02d%02d',
                       $value['year'], $value['month'], $value['mday']);
    }

    /**
     * Parse a Duration Value field
     */
    function _parseDuration($text)
    {
        if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $durvalue)) {
            // weeks
            $duration = 7 * 86400 * intval($durvalue[3]);
            if (count($durvalue) > 4) {
                // days
                $duration += 86400 * intval($durvalue[4]);
            }
            if (count($durvalue) > 5) {
                // hours
                $duration += 3600 * intval($durvalue[7]);
                // mins
                if (array_key_exists(8, $durvalue)) {
                    $duration += 60 * intval($durvalue[8]);
                }
                // secs
                if (array_key_exists(9, $durvalue)) {
                    $duration += intval($durvalue[9]);
                }
            }
            // sign
            if ($durvalue[1] == "-") {
                $duration *= -1;
            }

            return $duration;
        } else {
            return false;
        }
    }

    /**
     * Export a duration value
     */
    function _exportDuration($value)
    {
        $duration = '';
        if ($value < 0) {
            $value *= -1;
            $duration .= '-';
        }
        $duration .= 'P';

        $weeks = floor($value / (7 * 86400));
        $value = $value % (7 * 86400);
        if ($weeks) {
            $duration .= $weeks . 'W';
        }

        $days = floor($value / (86400));
        $value = $value % (86400);
        if ($days) {
            $duration .= $days . 'D';
        }

        if ($value) {
            $duration .= 'T';

            $hours = floor($value / 3600);
            $value = $value % 3600;
            if ($hours) {
                $duration .= $hours . 'H';
            }

            $mins = floor($value / 60);
            $value = $value % 60;
            if ($mins) {
                $duration .= $mins . 'M';
            }

            if ($value) {
                $duration .= $value . 'S';
            }
        }

        return $duration;
    }

    /**
     * Return the folded version of a line
     */
    function _foldLine($line, $newline)
    {
        $line = preg_replace ('/(\r\n|\n|\r)/', '\n', $line);
        if (strlen($line) > 75) {
            $foldedline = '';
            while(!empty($line)) {
                $maxLine = substr($line, 0, 75);
                $cutPoint = max(60, max(strrpos($maxLine, ';'), strrpos($maxLine, ':')) + 1);

                $foldedline .= (empty($foldedline)) ?
                                    substr($line, 0, $cutPoint) :
                                    $newline . ' ' . substr($line, 0, $cutPoint);

                $line = (strlen($line) <= $cutPoint) ? '' : substr($line, $cutPoint);
            }
            return $foldedline;
        }
        return $line;

    }

}

--- NEW FILE: kronolith_kolab.php ---
<?php

/**
 * Horde Kronolith driver for the Kolab IMAP server.
 * Based on code by Adriaan Putter <kougom at yahoo.com>
 *
 * Copyright (C) 2003 Code Fusion, cc.
 * Written by Stuart Bingë <s.binge at codefusion.co.za>
 *
 * Created 2003-08-25
 * Last Modified 2003-08-29 by Stuart Bingë
 */

require_once(HORDE_BASE . '/lib/Kolab.php');
require_once(HORDE_BASE . '/lib/iCalendar.php');

class Kronolith_Driver_kolab extends Kronolith_Driver {

  var $params = array();
  var $_folder;
  var $_server;
  var $imap;

  function Kronolith_Driver_kolab($params = array())
  {
    $this->type = 'kolab';
    $this->params = $params;
    $this->_folder = $params['folder'];
    $this->_server = $params['server'];
  }

  function open($calendar)
  {
    $this->_calendar = $calendar;
    $this->imap = Kolab::OpenImapConnection($this->_server, $this->_folder);
    if (!$this->imap) return PEAR::raiseError('Unable to open IMAP connection.');
    return true;
  }

  function close()
  {
    Kolab::CloseIMAPConnection($this->imap);
  }

  function delete($calendar)
  {
    // nothing for now...
  }

  function listEvents($startDate = null, $endDate = null)
  {
    $events = array();

    $msgs = Kolab::GetMessageList($this->imap);
    $cal = new Horde_iCalendar;
    for ($i = 0; $i < count($msgs); $i++)
    {
      $body = imap_body($this->imap, $msgs[$i]);
      $cal->_components = array();
      $cal->parsevCalendar($body);

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

      foreach ($components as $component)
      {
        if (!is_a($component, 'Horde_iCalendar_vevent')) continue;

        $startattr = $component->getAttribute('DTSTART');
        if (is_array($startattr) || is_a($startattr, 'PEAR_Error')) continue;

        $start = getdate($startattr);
        if (!$ispresent &&
            $start['year'] >= $startDate->year && $start['year'] <= $endDate->year &&
            $start['mon'] >= $startDate->month && $start['mon'] <= $endDate->month &&
            $start['mday'] >= $startDate->mday && $start['mday'] <= $endDate->mday)
        {
          array_push($events, $component->getAttribute('UID'));
          $ispresent = true;
        }
      }
    }

    return $events;
  }

  function listAlarms($date)
  {
    $events = $this->listEvents($date, $date);
    $alarms = array();

    $cal = new Horde_iCalendar;
    foreach ($events as $event)
    {
      $matches = imap_search($this->imap, "SUBJECT $event");
      if (!is_array($matches) || count($matches) < 1) continue;

      $body = imap_body($this->imap, $matches[0]);
      $cal->parsevCalendar($body);
      $components = $cal->getComponents();

      foreach ($components as $component)
      {
        if (!is_a($component, 'Horde_iCalendar_vevent')) continue;

        $subcomps = $component->getComponents();
        foreach ($subcomps as $subcomp)
        {
          if (!is_a($subcomp, 'Horde_iCalendar_valarm')) continue;

          $ts = Kronolith::objectToTimestamp($date);

          $dateattr = $component->getAttribute('DTSTART');
          if (is_array($dateattr) || is_a($dateattr, 'PEAR_Error')) continue;

          $offset = $subcomp->getAttribute("TRIGGER");
          $diff = $ts - $dateattr;
          if ($diff >= $offset && $diff <= 0)
            array_push($alarms, $event);
        }
      }
    }

    return $alarms;
  }

  function &getEventObject($eventID = null)
  {
    if (!is_null($eventID)) {
      $cal = new Horde_iCalendar;
      $matches = imap_search($this->imap, "SUBJECT $eventID");
      if (!is_array($matches) || count($matches) < 1) return false;
      $body = imap_body($this->imap, $matches[0]);
      $headers = Kolab::GetMessageHeaders($this->imap, $matches[0]);
      $cat = Kolab::GetHeaderValue($headers, X_HEAD_CAT, 0);
      $cal->parsevCalendar($body); 
      $components = $cal->getComponents();

      foreach ($components as $component)
      {
        if (!is_a($component, 'Horde_iCalendar_vevent')) continue;

        $obj = new Kronolith_Event_kolab($this, $component);
        $obj->category = $cat;
        return $obj;
      }

      return false;
    } else {
      return new Kronolith_Event_kolab($this);
    }
  }

  function saveEvent($event)
  {
    // $event should be a Kronolith_Event_kolab

    list($user, $pass) = Kolab::GetAuthentication();
    $mailbox = Kolab::MailboxURI($this->_server, $this->_folder);

    $eventID = $event->getID();

    $cal = new Horde_iCalendar;
    $alobj = NULL;
    if (!is_null($eventID))
    {
      $matches = imap_search($this->imap, "SUBJECT $eventID");
      if (!is_array($matches) || count($matches) < 1) return false;
      $body = imap_body($this->imap, $matches[0]);
      imap_delete($this->imap, $matches[0]);
      imap_expunge($this->imap);
      $cal->parsevCalendar($body);

      $evobj =& $cal->findComponent('VEVENT');
      if (!is_null($evobj))
        $alobj =& $evobj->findComponent('VALARM');
    }
    else
    {
      $eventID = uniqid('kronolith-');
      $evobj =& $cal->addNewComponent('VEVENT');
      $evobj->setAttribute('CREATED', time());
      $evobj->setAttribute('UID', $eventID);
    }

    if (is_null($evobj)) return PEAR::raiseError("Unable to locate event object for event $eventID");

    $evobj->setAttribute('SUMMARY', $event->getTitle());
    $evobj->setAttribute('DESCRIPTION', $event->getDescription());
    $evobj->setAttribute('LOCATION', $event->getLocation());
    $evobj->setAttribute('LAST-MODIFIED', time());
    $evobj->setAttribute('ORGANIZER', $user);//$event->getCreatorID());
    $evobj->setAttribute('DTSTART', $event->getStartTimestamp());
    $evobj->setAttribute('DTEND', $event->getEndTimestamp());

    $trigger = $event->getAlarm();
    if ($trigger != 0)
    {
      if (is_null($alobj))
        $alobj =& $evobj->addNewComponent('VALARM');

      $alobj->setAttribute('TRIGGER', $trigger);
    }

    $body = str_replace("\n", MIME_NL, $cal->exportvCalendar());

    if (!Kolab::AddMessage($this->imap, $mailbox, $user, 'text/calendar', $body, 'Kronolith', array(
        'Subject' => $eventID,
        X_HEAD_CAT => $event->getCategory()
      )))
      return PEAR::raiseError('Unable to add task.');

    return true;
  }

  function nextRecurrence($eventID, $afterDate, $weekstart = KRONOLITH_SUNDAY)
  {
    return false; // nothing for now...
  }

  function deleteEvent($eventID)
  {
    $matches = imap_search($this->imap, "SUBJECT $eventID");
    if (!is_array($matches) || count($matches) < 1) return false;
    imap_delete($this->imap, $matches[0]);
    imap_expunge($this->imap);
  }
}

class Kronolith_Event_kolab extends Kronolith_Event {

  function toDriver()
  {
    // Nothing - all this is done in the drivers' saveEvent() function
  }

  function fromDriver($icalEvent)
  {
    // $icalEvent should be a Horde_iCalendar_vevent

    $tmp = $icalEvent->getAttribute('SUMMARY');
    if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
      $this->title = $tmp;

    $tmp = $icalEvent->getAttribute('DESCRIPTION');
    if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
      $this->description = $tmp;

    $tmp = $icalEvent->getAttribute('LOCATION');
    if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
      $this->location = $tmp;

    $tmp = $icalEvent->getAttribute('ORGANIZER');
    if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
      $this->creatorID = $tmp;

    $tmp = $icalEvent->getAttribute('DTSTART');
    if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
      {
        $this->startTimestamp = $tmp;
        $this->start = Kronolith::timestampToObject($this->startTimestamp);
      }

    $tmp = $icalEvent->getAttribute('DTEND');
    if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
      {
        $this->endTimestamp = $tmp;
        $this->end = Kronolith::timestampToObject($this->endTimestamp);
      }

    $this->durMin = ($this->endTimestamp - $this->startTimestamp) / 60;

    $this->eventID = $icalEvent->getAttribute('UID');

    // No recurring events for now...
    $this->recurType = KRONOLITH_RECUR_NONE;
    $this->recurInterval = 0;

    $subcomps = $icalEvent->getComponents();
    foreach ($subcomps as $subcomp)
    {
      if (!is_a($subcomp, 'Horde_iCalendar_valarm')) continue;                  

      $tmp = $subcomp->getAttribute('TRIGGER');
      if (!is_array($tmp) && !is_a($tmp, 'PEAR_Error'))
        $this->alarm = $tmp;
    }

    $this->initialized = true;
  }
}

--- NEW FILE: mnemo_kolab.php ---
<?php

/**
 * Horde Mnemo driver for the Kolab IMAP server.
 * Based on code by Adriaan Putter <kougom at yahoo.com>
 *
 * Copyright (C) 2003 Code Fusion, cc.
 * Written by Stuart Bingë <s.binge at codefusion.co.za>
 *
 * Created 2003-08-20
 * Last Modified 2003-08-29 by Stuart Bingë
 */

require_once(HORDE_BASE . '/lib/Kolab.php');
require_once(HORDE_BASE . '/lib/iCalendar.php');

class Mnemo_Driver_kolab extends Mnemo_Driver {

  var $_params = array();
  var $_folder;
  var $_server;

  function Mnemo_Driver_kolab($user, $params)
  {
    $this->type = 'kolab';
    $this->user = $user;
    $this->_params = $params;
    $this->_folder = $params['folder'];
    $this->_server = $params['server'];
  }

  function retrieve()
  {
    $imap = Kolab::OpenImapConnection($this->_server, $this->_folder);
    if (!$imap) return PEAR::raiseError('Unable to open IMAP connection.');

    $this->memos = array();

    $mod = false;
    $msgs = Kolab::GetMessageList($imap);
    for ($i = 0; $i < count($msgs); $i++)
    {
      $body = imap_body($imap, $msgs[$i]);
      $desc = explode(MIME_NL, $body);
      $body = str_replace(MIME_NL, "\n", $body);

      $headers = Kolab::GetMessageHeaders($imap, $msgs[$i]);
      $cat = Kolab::GetHeaderValue($headers, X_HEAD_CAT, 0);
      $uid = Kolab::GetHeaderValue($headers, X_HEAD_ID);
      $flags = 0;
      if (is_null($uid))
      {
        $uid = uniqid("0x");
        $flags = memo_MODIFIED;
        $mod = true;
      }

      $memo = array();
      $memo['message_id'] = $msgs[$i];
      $memo['memo_id'] = $uid;
      $memo['desc'] = $desc[0];
      $memo['body'] = $body;
      $memo['category'] = $cat;
      $memo['private'] = "";
      $memo['flags'] = $flags;

      $this->memos[$uid] = $memo;
    }

    Kolab::CloseIMAPConnection($imap);

    if ($mod)
      $this->store(); // Re-save the non-Mnemo memos to ensure they have their Message IDs set corerctly

    return true;
  }

  function store()
  {
    $added_memos = $this->listMemos(MEMO_ADDED);
    $modified_memos = $this->listMemos(MEMO_MODIFIED);
    $deleted_memos = $this->listMemos(MEMO_DELETED);

    if ((count($added_memos) == 0) && (count($modified_memos) == 0) && (count($deleted_memos) == 0))
      return true;  // No-op

    $imap = Kolab::OpenImapConnection($this->_server, $this->_folder);
    if (!$imap) return PEAR::raiseError('Unable to open IMAP connection.');

    list($user, $pass) = Kolab::GetAuthentication();
    $mailbox = Kolab::MailboxURI($this->_server, $this->_folder);

    foreach ($added_memos as $memo_id => $memo)
    {
      $body = str_replace("\n", MIME_NL, $memo['body']);
      if (!Kolab::AddMessage($imap, $mailbox, $user, 'text/plain', $body, 'Mnemo', array(
        X_HEAD_ID => strval($memo_id),
        X_HEAD_CAT => $memo['category']
      )))
      {
        Kolab::CloseIMAPConnection($imap);
        return PEAR::raiseError('Unable to add memo.');
      }

      $this->setFlag($memo_id, MEMO_ADDED, false);
    }

    foreach ($modified_memos as $memo_id => $memo)
    {
      $mid = $memo['message_id'];
      if (is_null($mid)) continue;
      @imap_delete($imap, $mid);

      $body = str_replace("\n", MIME_NL, $memo['body']);
      if (!Kolab::AddMessage($imap, $mailbox, $user, 'text/plain', $body, 'Mnemo', array(
        X_HEAD_ID => strval($memo_id),
        X_HEAD_CAT => $memo['category']
      )))
      {
        Kolab::CloseIMAPConnection($imap);
        return PEAR::raiseError('Unable to add memo.');
      }

      $this->setFlag($memo_id, MEMO_MODIFIED, false);
    }

    foreach ($deleted_memos as $memo_id => $memo)
    {
      $mid = $memo['message_id'];
      if (is_null($mid)) continue;
      @imap_delete($imap, $mid);
    }

    $this->purgeDeleted();

    Kolab::CloseIMAPConnection($imap);

    return true;
  }
}

--- NEW FILE: nag_kolab.php ---
<?php

/**
 * Horde Nag driver for the Kolab IMAP server.
 * Based on code by Adriaan Putter <kougom at yahoo.com>
 *
 * Copyright (C) 2003 Code Fusion, cc.
 * Written by Stuart Bingë <s.binge at codefusion.co.za>
 *
 * Created 2003-08-25
 * Last Modified 2003-08-29 by Stuart Bingë
 */

require_once(HORDE_BASE . '/lib/Kolab.php');
require_once(HORDE_BASE . '/lib/iCalendar.php');

class Nag_Driver_kolab extends Nag_Driver {

  var $_params = array();
  var $_folder;
  var $_server;

  function Nag_Driver_kolab($user, $params = array())
  {
    $this->type = 'kolab';
    $this->_user = $user;
    $this->_params = $params;
    $this->_folder = $params['folder'];
    $this->_server = $params['server'];
  }

  function retrieve()
  {
    $imap = Kolab::OpenImapConnection($this->_server, $this->_folder);
    if (!$imap) return PEAR::raiseError('Unable to open IMAP connection.');

    $this->_tasks = array();

    $mod = false;
    $cal = new Horde_iCalendar;
    $msgs = Kolab::GetMessageList($imap);
    for ($i = 0; $i < count($msgs); $i++)
    {
      $cal->parsevCalendar(imap_body($imap, $msgs[$i]));
      $todo =& $cal->findComponent('VTODO');
      if (is_null($todo)) continue;

      $headers = Kolab::GetMessageHeaders($imap, $msgs[$i]);
      $cat = Kolab::GetHeaderValue($headers, X_HEAD_CAT, 0);
      $uid = Kolab::GetHeaderValue($headers, X_HEAD_ID);
      $flags = 0;
      if (is_null($uid))
      {
        $uid = uniqid("0x");
        $flags = TASK_MODIFIED;
        $mod = true;
      }

      $due = $todo->getAttribute('DUE');
      if (is_a($due, 'PEAR_Error')) $due = 0;
      else if (is_array($due)) $due = Kolab::ArrayToTimestamp($due);

      $com = $todo->getAttribute('PERCENT-COMPLETE');
      if (is_a($com, 'PEAR_Error')) $com = 0; else if ($com == 100) $com = 1; else $com = 0;

      $task = array();
      $task['message_id'] = $msgs[$i];
      $task['task_id'] = $uid;
      $task['name'] = $todo->getAttribute('SUMMARY');
      $task['desc'] = $todo->getAttribute('DESCRIPTION');
      $task['category'] = $cat;
      $task['due'] = $due;
      $task['priority'] = $todo->getAttribute('PRIORITY');
      $task['completed'] = $com;
      $task['flags'] = $flags;

      $this->_tasks[$uid] = $task;
    }

    Kolab::CloseIMAPConnection($imap);

    if ($mod)
      $this->store(); // Re-save the non-Nag tasks to ensure they have their Message IDs set corerctly

    return true;
  }

  function store()
  {
    $added_tasks = $this->listTasks(TASK_ADDED);
    $modified_tasks = $this->listTasks(TASK_MODIFIED);
    $deleted_tasks = $this->listTasks(TASK_DELETED);

    if ((count($added_tasks) == 0) && (count($modified_tasks) == 0) && (count($deleted_tasks) == 0))
      return true;  // No-op

    $imap = Kolab::OpenImapConnection($this->_server, $this->_folder);
    if (!$imap) return PEAR::raiseError('Unable to open IMAP connection.');

    list($user, $pass) = Kolab::GetAuthentication();
    $cal = new Horde_iCalendar;
    $mailbox = Kolab::MailboxURI($this->_server, $this->_folder);

    foreach ($added_tasks as $task_id => $task)
    {
      $cal->clear();
      $todo =& $cal->addNewComponent('VTODO');
      if (!$todo)
      {
        Kolab::CloseIMAPConnection($imap);
        return PEAR::raiseError('Unable to add task.');
      }

      if ($task['completed']) $com = 100; else $com = 0;
      $uid = $cal->generateUID();

      $todo->setAttribute('UID', $uid);
      $todo->setAttribute('SUMMARY', $task['name']);
      $todo->setAttribute('DESCRIPTION', $task['desc']);
      $todo->setAttribute('PRIORITY', $task['priority']);
      $todo->setAttribute('PERCENT-COMPLETE', $com);
      $todo->setAttribute('ORGANIZER', 'MAILTO:' . $user);
      $todo->setAttribute('CREATED', time());
      $todo->setAttribute('LAST-MODIFIED', time());

      if ($task['due'] != 0) $todo->setAttribute('DUE', $task['due']);

      $body = str_replace("\n", MIME_NL, $cal->exportvCalendar());
      if (!Kolab::AddMessage($imap, $mailbox, $user, 'text/calendar', $body, 'Nag', array(
        'Subject' => $uid,
        X_HEAD_ID => strval($task_id),
        X_HEAD_CAT => $task['category']
      )))
      {
        Kolab::CloseIMAPConnection($imap);
        return PEAR::raiseError('Unable to add task.');
      }

      $this->setFlag($task_id, TASK_ADDED, false);
    }

    foreach ($modified_tasks as $task_id => $task)
    {
      $mid = $task['message_id'];
      if (is_null($mid)) continue;
      $cal->parsevCalendar(imap_body($imap, $mid));
      @imap_delete($imap, $mid);

      $todo =& $cal->findComponent('VTODO');
      if (!$todo)
      {
        Kolab::CloseIMAPConnection($imap);
        return PEAR::raiseError('Unable to modify task.');
      }

      if ($task['completed']) $com = 100; else $com = 0;

      $todo->setAttribute('SUMMARY', $task['name']);
      $todo->setAttribute('DESCRIPTION', $task['desc']);
      $todo->setAttribute('PRIORITY', $task['priority']);
      $todo->setAttribute('PERCENT-COMPLETE', $com);
      $todo->setAttribute('ORGANIZER', 'MAILTO:' . $user);
      $todo->setAttribute('LAST-MODIFIED', time());

      if ($task['due'] != 0)
        $todo->setAttribute('DUE', $task['due']);
      else
        $todo->clearAttribute('DUE');

      $body = str_replace("\n", MIME_NL, $cal->exportvCalendar());
      if (!Kolab::AddMessage($imap, $mailbox, $user, 'text/calendar', $body, 'Nag', array(
        'Subject' => $todo->getAttribute('UID'),
        X_HEAD_ID => strval($task_id),
        X_HEAD_CAT => $task['category']
      )))
      {
        Kolab::CloseIMAPConnection($imap);
        return PEAR::raiseError('Unable to modify task.');
      }

      $this->setFlag($task_id, TASK_MODIFIED, false);
    }

    foreach ($deleted_tasks as $task_id => $task)
    {
      $mid = $task['message_id'];
      if (is_null($mid)) continue;
      @imap_delete($imap, $mid);
    }

    $this->purgeDeleted();

    Kolab::CloseIMAPConnection($imap);

    return true;
  }

}
?>

--- NEW FILE: turba_kolab.php ---
<?php
/**
 * Turba directory driver implementation for KOLAB extension.
 *
 *
 * Adriaan Putter <kougom at yahoo.com>
 */
class Turba_Driver_kolab extends Turba_Driver {

  /** String containg the name of the IMAP Folder containing Contacts. */
  var $folder = '';

  var $server = '';

  var $cbox;	// imap stream

  var $err_mbox_dne = "Mailbox does not exist";

  // This is used for error checking on function return - is there a better way to do this other than string comparison?
  function TestError($error)
    {                     
      return strcasecmp(imap_last_error(), $error) == 0;
    }

    
  /**
   * Constructs a new Turba Kolab driver object.
   *
   * @param $params       Hash containing additional configuration parameters.
   */
  function Turba_Driver_kolab($params)
    {
      $this->type = 'kolab';
      $this->params = $params;
      $this->folder = $params['folder'];
      $this->server = $params['server'];

      // Use the same username and password to connect to IMAP server
      // It is also assumed that Horde uses the imap driver for authentication
      $user = Auth::getAuth();
      // $userID is of the format user at host@kolabtest, and we need the user at host portion, so use strrpos('@')
      //$user = substr($userID, 0, strrpos($userID, "@"));
      $pass = Auth::getCredential('password');

      $connbase = '{' . $this->server . ':143/imap/notls}';
      $connstr = $connbase . $this->folder;

      $this->cbox = imap_open($connstr, $user, $pass);

      if ($this->TestError($this->err_mbox_dne))
        {
          Horde::raiseMessage("Contacts folder does not exist, creating...", HORDE_MESSAGE);

          $this->cbox = imap_open($connbase, $user, $pass, OP_HALFOPEN);

          if (!$this->cbox)
            {
              Horde::raiseMessage("Error creating contacts folder - \"" . imap_last_error() . "\".", HORDE_ERROR);
              $this->errno = -1;
              return;
            }

          if (!@imap_createmailbox($this->cbox, $connstr))
            {
              Horde::raiseMessage("Error creating contacts folder - \"" . imap_last_error() . "\".", HORDE_ERROR);
              $this->errno = -1;
              return;
            }

          imap_close($this->cbox);

          $this->cbox = imap_open($connstr, $user, $pass);          
        }

      if (!$this->cbox)
        {
          Horde::raiseMessage("Error opening contacts folder - \"" . imap_last_error() . "\".", HORDE_ERROR);
          $this->errno = -1;
          return;
        }

      //      Horde::raiseMessage("IMAPHost=\"" . $connstr . "\" Login=\"" . $user . ":" . $pass . "\"" , HORDE_MESSAGE);
    }

  /**
   * Searches the Kolab contacts filter  with the given criteria and returns a
   * filtered list of results. If the criteria parameter is an empty
   * array, all records will be returned.
   *
   * @param $criteria      Array containing the search criteria.
   * @param $fields        List of fields to return.
   * @param $strict_fields (optional) Array of fields which must be matched
   *                       exactly.
   * @param const $match   Do an 'and' or an 'or' search (defaults to TURBA_SEARCH_AND).
   *
   * @return               Hash containing the search results.
   */
  function search($criteria, $fields, $strict_fields = array(), $match = TURBA_SEARCH_AND)
    {

      $results = array();
      /* Get how many messages is in folder */
      $msgs = imap_num_msg( $this->cbox );
      $sort = imap_sort( $this->cbox, SORTDATE, 0 );
      $entry = array();
      for ( $i = 0; $i < $msgs; $i++) {
        $vcard = imap_body( $this->cbox, $sort[$i] );
        $vc = $this->loadVCard( $vcard );
        if (count($criteria) > 0 ) {
          foreach( $criteria as $key => $val ) {
            /* This needs work to do advance searches */
            /* filter for each criteria */
            if( preg_match("/" . $val . "/i", $vc[$key] ) ) {
              foreach($fields as $field) {
                $entry[$field] = $vc[$field];
              }
            }
          }
        }
        else {
          foreach($fields as $field) {
            $entry[$field] = $vc[$field];
          }
        }
        $results[] = $entry;
      }
		
		
      return $results;
    }

  /**
   * Read the given data from the Kolab Contacts Folder and returns
   * the result's fields.
   *
   * @param $criteria      Search criteria.
   * @param $id            Data identifier.
   * @param $fields        List of fields to return.
   *
   * @return               Hash containing the search results.
   */
  function read($criteria, $id, $fields)
    {

      $result = array();

      $sort = imap_sort($this->cbox, SORTDATE, 0);
      $msgs = imap_num_msg( $this->cbox );
      $entry = array();
      // Go through each message to search for the types and values
      for ( $i = 0; $i < $msgs; $i++) {
        $vcard = imap_body( $this->cbox, $sort[$i]);
        $vc = $this->loadVCard( $vcard );
        if ( preg_match( "/" . $id . "/", $vc[$criteria] ) ) {
          foreach($fields as $field) {
            $entry[$field] = $vc[$field];
          }
	
          $result[] = $entry;
        }
      }

      return $result;
			

    }

  /**
   * Closes the current (IMAP) Kolab Contacts Folder connection.
   *
   * @return          The result of the disconnect() call.
   */
  function close()
    {
      return imap_close( $this->cbox );
    }

  /**
   * Adds the specified object to the Kolab Contacts Folder.
   */
  function addObject($attributes)
    {
    	$nl = "\r\n";
      if ( $attributes['fn'] == "" )
        return new PEAR_Error("Must specify a Name");

      $vcard = $this->createVCard( $attributes );
	
      // Create message header and add vCard
      $userID = Auth::getAuth();
      $msg = 'Content-Type: Text/X-VCard; charset="utf-8"'.$nl
	      .'From: '.$userID.$nl
	      .'Reply-To:'.$nl
	      .'To: '.$attributes['fn'].$nl
	      .'Subject: Contact: '.$attributes['fn'].$nl
	      .'User-Agent: Horde/Turba/Kolab'.$nl
	      .'Date: '.date('r').$nl
	      .$nl
	      .$vcard;

      $connstr = '{' . $this->server . ':143/imap}' . $this->folder;
      if (!imap_append( $this->cbox, $connstr, $msg )){ 
        return new PEAR_Error( 'Could not save message in Contacts folder! because, '.imap_last_error() );
      }
      else {
        return true;
      }
    }

  /**
   * Deletes the specified object from the Kolab Contacts Folder.
     
  */
  function removeObject($object_key, $object_id)
    {
    	// Find message with UID and delete and expunge the mailbox
      $msg = imap_search( $this->cbox, "BODY \"".strtoupper($object_key).":".$object_id."\"" );
      if ( count($msg) == 0 || count($msg) > 1 )
        return new PEAR_Error('Could not delete, there are none found or no many found');
      else
        {
          if(!imap_delete( $this->cbox, $msg[0] ))
            return new PEAR_Error('Could not delete, because: '.imap_last_error() );
          else
            imap_expunge( $this->cbox );
        }
      return true;
    }

  /**
   * Saves the specified object in the Kolab Contacts Folder.
   */
  function setObject($object_key, $object_id, $attributes)
    {
      // Find message that contains the UID
    	$msg = imap_search( $this->cbox, "BODY \"".strtoupper($object_key).":".$object_id."\"" );
      if ( count($msg) == 0 || count($msg) > 1 )
        return new PEAR_Error('Could not edit, there are none found or no many found');
	
      // Save original to get the types not supported by us
      $orig_vcard = $this->loadVCard(imap_body( $this->cbox, $msg[0] ));
      // Get original header, we don't change header info on already existing contacts
      $header = imap_fetchheader( $this->cbox, $msg[0], FT_INTERNAL );
      $rev = date("Ymd\THis\Z"); // create the REV: type so that other clients can see this contact has changed
      $vcard = $this->createVCard( $attributes, $rev, $orig_vcard['otherstuff'] );
		
      $m = $header . $vcard;
      $connstr = '{' . $this->server . ':143/imap}' . $this->folder;
      // Delete message and re-create it
      if (!imap_append( $this->cbox, $connstr, $m))
        return new PEAR_Error( 'Could not save message! Because: '.imap_last_error());
      else{
        imap_delete( $this->cbox, $msg[0] );
        imap_expunge($this->cbox );
        return true;
      }

    }

  /**
   * Create an object key for a new object.
   *
   * @param array $attributes  The attributes (in driver keys) of the
   *                           object being added.
   *
   * @return string  A unique ID for the new object.
   */
  function makeKey($attributes)
    {
      return md5(uniqid(rand()));
    }

  /**
   * Loads a vCard message into an array.
   *
   * @vcard string The vCard string message.
   *
   */
  function loadVCard( $vcard )
    {
      // Put whole message in nice array
      $lines = explode( "\n", $vcard );
      $data = array();
      foreach( $lines as $line )
        {
          $k = substr($line, 0, strpos($line,':'));
          $v = substr($line, strpos($line,':')+1);
          $attributes = explode(';', $k);
          $k = array_shift($attributes);
          if ( strncmp($k,"X-",2) != 0 )
            $k = strtolower($k);
          foreach($attributes as $attr)
            {
              $k .= ';' . strtolower($attr);
            }
          $data[$k] = $v;
        }

      // Create the expected array that the driver needs
      $vc = array();
      $vc['uid'] = trim($data['uid'],"\r\n");
      $vc['fn'] = $data['fn'];
      $vc['nickname'] = $data['nickname'];
      $vc['email'] = $data['email'];
      $vc['org'] = $data['org'];
      $address = explode(';',$data['adr;type=home']);
      /* KMail don't seem to split the street address with ',' which
       * is explained in the RFC2426, so I added extra replace for
       * new lines.
       */
      $vc['home_street'] = str_replace('\n',"\n",$address[2]);
      $vc['home_street'] = str_replace(',',"\n",$vc['home_street']);
      $vc['home_postalbox'] = $address[0];
      $vc['home_locality'] = $address[3];
      $vc['home_region'] = $address[4];
      $vc['home_code'] = $address[5];
      $vc['home_country'] = $address[6];
      $address = explode(';',$data['adr;type=work']);
      // KMail See above
      $vc['work_street'] = str_replace('\n', "\n", $address[2]);
      $vc['work_street'] = str_replace(',',"\n", $vc['work_street']);
      $vc['work_postalbox'] = $address[0];
      $vc['work_locality'] = $address[3];
      $vc['work_region'] = $address[4];
      $vc['work_code'] = $address[5];
      $vc['work_country'] = $address[6];
      $vc['work_phone'] = $data['tel;type=work'];
      $vc['home_phone'] = $data['tel;type=home'];
      $vc['cell_phone'] = $data['tel;type=cell'];
      $vc['fax'] = $data['tel;type=fax'];
      $vc['note'] = $data['note'];
	
      // All the other types that we don't support
      $vc['otherstuff'] = array();
      foreach($data as $key => $value )
        {
          switch($key){
          case "uid":
          case "fn":
          case "n":
          case "nickname":
          case "email":
          case "org":
          case "adr;type=home":
          case "adr;type=work":
          case "tel;type=work":
          case "tel;type=home":
          case "tel;type=cell":
          case "tel;type=fax":
          case "note":
          case "name":
          case "version":
          case "begin":
          case "rev":
          case "class":
          case "end":
          case "":
            break;
          default:
            $vc['otherstuff'][$key] = trim($value,"\r\n");
            break;
          }
        }

      return $vc;
    }
    
  /**
   * Creates a vCard body message.
   *
   * @attributes array  The attributes to be converted to msgbody.
   *
   * @rev string The REV tag date
   *
   * @otherstuff array An Array of types that was not created with
   *			 this Kolab/Turba client.
   */
  function createVCard( $attributes, $rev = "", $otherstuff = array() )
    {
      $nl = "\r\n";
      if ( $attributes['fn'] == "" )
        return new PEAR_Error("Must specify a Name");

      $vcard = 'BEGIN:VCARD'.$nl
        .'VERSION: 3.0'.$nl
        .'NAME:'.$attributes['fn'].$nl
        .'UID:'.trim($attributes['uid'],$nl).$nl
        .'FN:'.$attributes['fn'].$nl;


      if ( $attributes['email'] != "" )
        $vcard .= 'EMAIL:'.$attributes['email'].$nl;
	
	
      if(!strrchr($attributes['fn']," ")){
        $surname = "";
        $name = $attributes['fn'];
      }
      else {
        $surname = substr( strrchr($attributes['fn']," "),1);
        $name = substr( $attributes['fn'], 0, strpos($attributes['fn']," "));
      }
		
      $vcard .= 'N:'.$surname.';'.$name.';;;'.$nl;
      $vcard .= $attributes['nickname'] != "" ? 'NICKNAME:'.$attributes['nickname'].$nl : null;
	
      $vcard .= $attributes['org'] != "" ? 'ORG:'.$attributes['org'].$nl : "";
      $vcard .= $attributes['note'] != "" ? 'NOTE:'.$attributes['note'].$nl : "";
		 
      $homeaddy = $attributes['home_postalbox'].';;'.str_replace("\r\n",',',$attributes['home_street'])
		    .';'.$attributes['home_locality'].';'.$attributes['home_region'].';'.$attributes['home_code']
		    .';'.$attributes['home_country'];
      $vcard .= trim($homeaddy,';') != "" ? 'ADR;TYPE=home:'.$homeaddy.$nl : "";
		
      $workaddy = $attributes['work_postalbox'].';;'.str_replace("\r\n",',',$attributes['work_street'])
		    .';'.$attributes['work_locality'].';'.$attributes['work_region'].';'.$attributes['work_code']
		    .';'.$attributes['work_country'];
      $vcard .= trim($workaddy,';') != "" ? 'ADR;TYPE=work:'.$workaddy.$nl : "";
      $vcard .= $attributes['work_phone'] != "" ? 'TEL;TYPE=work:'.$attributes['work_phone'].$nl : "";
      $vcard .= $attributes['home_phone'] != "" ? 'TEL;TYPE=home:'.$attributes['home_phone'].$nl : "";
      $vcard .= $attributes['cell_phone'] != "" ? 'TEL;TYPE=cell:'.$attributes['cell_phone'].$nl : "";
      $vcard .= $attributes['fax'] != "" ? 'TEL;TYPE=fax:'.$attributes['fax'].$nl : "";
      $vcard .= $rev != "" ? 'REV:'.$rev.$nl : "";

      // Add the types that we did not create
      foreach( $otherstuff as $key => $value ){
        if ( strncmp($key, "X-",2) != 0 )
          $vcard .= strtoupper($key) . ":" . $value . $nl;
        else
          $vcard .= $key . ":" . $value . $nl;
      }

      $vcard .= 'CLASS:PRIVATE'.$nl.'END:VCARD'.$nl;
	
      return $vcard;
    }
}

--- NEW FILE: valarm.php ---
<?php
/**
 * Class representing vAlarms
 *
 * $Horde: horde/lib/iCalendar/valarm.php,v 1.3 2003/07/03 09:16:39 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
 class Horde_iCalendar_valarm extends Horde_iCalendar {

    function getType()
    {
        return 'vAlarm';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'VALARM');
    }

    function exportvCalendar(&$container)
    {
        return parent::_exportvData('VALARM');
    }
}


--- NEW FILE: vevent.php ---
<?php
/**
 * Class representing vEvents
 *
 * $Horde: horde/lib/iCalendar/vevent.php,v 1.13 2003/07/03 09:16:39 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
 class Horde_iCalendar_vevent extends Horde_iCalendar {

    function getType()
    {
        return 'vEvent';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'VEVENT');
    }

    function exportvCalendar(&$container)
    {
        // Default values
        $requiredAttributes = array();
        $requiredAttributes['DTSTAMP']   = time();
        $requiredAttributes['ORGANIZER'] = _('Unknown Organizer');
        $requiredAttributes['UID']       = $this->_exportDateTime(time()) . '@' . $_SERVER["SERVER_NAME"];
        switch ($container->getAttribute('METHOD')) {
            case 'PUBLISH':
                $requiredAttributes['DTSTART']   = time();
                $requiredAttributes['SUMMARY']   = '';
                break;

            case 'REQUEST':
                $requiredAttributes['ATTENDEE']  = '';
                $requiredAttributes['DTSTART']   = time();
                $requiredAttributes['SUMMARY']   = '';
                break;

            case 'REPLY':
                $requiredAttributes['ATTENDEE']  = '';
                break;

            case 'ADD':
                $requiredAttributes['DTSTART']   = time();
                $requiredAttributes['SEQUENCE']  = 1;
                $requiredAttributes['SUMMARY']   = '';
                break;

            case 'CANCEL':
                $requiredAttributes['ATTENDEE']  = '';
                $requiredAttributes['SEQUENCE']  = 1;
                break;

            case 'REFRESH':
                $requiredAttributes['ATTENDEE']  = '';
                break;

        }

        foreach ($requiredAttributes as $name => $default_value) {
            if (is_a($this->getAttribute($name), 'PEAR_Error')) {
                $this->setAttribute($name, $default_value);
            }
        }

        return parent::_exportvData('VEVENT');
    }

    /**
     * Update the status of an attendee of an event
     *
     * @param $email    The email address of the attendee
     * @param $status   The participant status to set.
     * @param $fullname The full name of the participant to set.
     */
    function updateAttendee($email, $status, $fullname = '')
    {
        foreach ($this->_attributes as $key => $attribute) {
            if ($attribute['name'] == 'ATTENDEE' && $attribute['value'] == 'MAILTO:' . $email) {
                $this->_attributes[$key]['params']['PARTSTAT'] = $status;
                if (!empty($fullname)) {
                    $this->_attributes[$key]['params']['CN'] = $fullname;
                }
                unset($this->_attributes[$key]['params']['RSVP']);
                return;
            }
        }
        $params = array('PARTSTAT' => $status);
        if (!empty($fullname)) {
            $params['CN'] = $fullname;
        }
        $this->setAttribute('ATTENDEE', 'MAILTO:' . $email, $params);
    }

    /**
     * Convert this event to a Kronolith Hash
     *
     * @return Array    Array containing the details of the event as used
     *                  by Kronolith
     */
    function toKronolithHash()
    {
        $hash = array();
        $title = $this->getAttribute('SUMMARY');
        if (!is_array($title) && !is_a($title, 'PEAR_Error')) {
            $hash['title'] = $title;
        }
        $desc = $this->getAttribute('DESCRIPTION');
        if (!is_array($desc) && !is_a($desc, 'PEAR_Error')) {
            $hash['description'] = $desc;
        }
        $location = $this->getAttribute('LOCATION');
        if (!is_array($location) && !is_a($location, 'PEAR_Error')) {
            $hash['location'] = $location;
        }

        $start = $this->getAttribute('DTSTART');
        if (!is_array($start) && !is_a($start, 'PEAR_Error')) {
            $hash['start_date'] = date('Y', $start) . '-' . date('m', $start) . '-' . date('j', $start);
            $hash['start_time'] = date('H', $start) . ':' . date('i', $start) . ':' . date('s', $start);
        }

        $end = $this->getAttribute('DTEND');
        if (!is_array($end) && !is_a($end, 'PEAR_Error')) {
            $hash['end_date'] = date('Y', $end) . '-' . date('m', $end) . '-' . date('j', $end);
            $hash['end_time'] = date('H', $end) . ':' . date('i', $end) . ':' . date('s', $end);
        }
        return $hash;
    }

    /**
     * Return the organizer display name or email.
     *
     * @return String   The organizer name to display for this event.
     */
    function organizerName()
    {
        $organizer = $this->getAttribute('ORGANIZER', true);
        if (array_key_exists('CN', $organizer[0])) {
            return $organizer[0]['CN'];
        }

        $organizer = parse_url($this->getAttribute('ORGANIZER'));

        return $organizer['path'];
    }

    /**
     * Update this event with details from another event.
     *
     * @param Horde_iCalendar_vEvent $vevent     The vEvent with latest details
     */
    function updateFromvEvent($vevent)
    {
        $newAttributes = $vevent->getAllAttributes();
        foreach ($newAttributes as $newAttribute) {
            $currentValue = $this->getAttribute($newAttribute['name']);
            if (is_a($currentValue, 'PEAR_error')) {
                // already exists so just add it
                $this->setAttribute($newAttribute['name'], $newAttribute['value'], $newAttribute['params']);
            } else {
                // already exists so locate and modify
                $found = false;
                // Try matching the attribte name and value incase only the
                // params changed (eg attendee updating status).
                foreach ($this->_attributes as $id => $attr) {
                    if ($attr['name'] == $newAttribute['name'] &&
                        $attr['value'] == $newAttribute['value']) {
                        // merge the params
                        foreach ($newAttribute['params'] as $param_id => $param_name) {
                            $this->_attributes[$id]['params'][$param_id] = $param_name;
                        }
                        $found = true;
                        break;
                    }
                }
                if (!$found) {
                    // Else match the first attribute with the same name (eg changing
                    // start time)
                    foreach ($this->_attributes as $id => $attr) {
                        if ($attr['name'] == $newAttribute['name']) {
                            $this->_attributes[$id]['value'] = $newAttribute['value'];
                            // merge the params
                            foreach ($newAttribute['params'] as $param_id => $param_name) {
                                $this->_attributes[$id]['params'][$param_id] = $param_name;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * Update just the attendess of event with details from another event.
     *
     * @param Horde_iCalendar_vEvent $vevent     The vEvent with latest details
     */
    function updateAttendeesFromvEvent($vevent)
    {
        $newAttributes = $vevent->getAllAttributes();
        foreach ($newAttributes as $newAttribute) {
            if (!$newAttribute['name'] == 'ATTENDEE') {
                continue;
            }
            $currentValue = $this->getAttribute($newAttribute['name']);
            if (is_a($currentValue, 'PEAR_error')) {
                // already exists so just add it
                $this->setAttribute($newAttribute['name'], $newAttribute['value'], $newAttribute['params']);
            } else {
                // already exists so locate and modify
                $found = false;
                // Try matching the attribte name and value incase only the
                // params changed (eg attendee updating status).
                foreach ($this->_attributes as $id => $attr) {
                    if ($attr['name'] == $newAttribute['name'] &&
                        $attr['value'] == $newAttribute['value']) {
                        // merge the params
                        foreach ($newAttribute['params'] as $param_id => $param_name) {
                            $this->_attributes[$id]['params'][$param_id] = $param_name;
                        }
                        $found = true;
                        break;
                    }
                }
                if (!$found) {
                    // Else match the first attribute with the same name (eg changing
                    // start time)
                    foreach ($this->_attributes as $id => $attr) {
                        if ($attr['name'] == $newAttribute['name']) {
                            $this->_attributes[$id]['value'] = $newAttribute['value'];
                            // merge the params
                            foreach ($newAttribute['params'] as $param_id => $param_name) {
                                $this->_attributes[$id]['params'][$param_id] = $param_name;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }

}

--- NEW FILE: vfreebusy.php ---
<?php
/**
 * Class representing vFreebusys
 *
 * $Horde: horde/lib/iCalendar/vfreebusy.php,v 1.9 2003/07/03 09:16:39 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
class Horde_iCalendar_vfreebusy extends Horde_iCalendar {

    var $_busyPeriods = array();

    function Horde_iCalendar_vfreebusy(&$container)
    {
        $this->_container = $container;
    }

    function getType()
    {
        return 'vFreebusy';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'VFREEBUSY');

        // do something with all the busy periods
        foreach ($this->_attributes as $key => $attribute) {
            if ($attribute['name'] == 'FREEBUSY') {
                foreach ($attribute['value'] as $value) {
                    if (array_key_exists('duration', $attribute['value'])) {
                       $this->addBusyPeriod('BUSY', $value['start'], null, $value['duration']);
                    } else {
                       $this->addBusyPeriod('BUSY', $value['start'], $value['end']);
                    }
                }
                unset($this->_attributes[$key]);
            }
        }


    }

    function exportvCalendar(&$container)
    {
        foreach ($this->_busyPeriods as $start => $end) {
            $periods = array(array('start' => $start, 'end' => $end));
            $this->setAttribute('FREEBUSY', $periods);
        }

        $res =  parent::_exportvData('VFREEBUSY');

        foreach ($this->_attributes as $key => $attribute) {
            if ($attribute['name'] == 'FREEBUSY') {
                unset($this->_attributes[$key]);
            }
        }

        return $res;
    }

    /**
     * Get a display name for this object
     */
    function getName()
    {
        $name = '';
        $method = $this->_container->getAttribute('METHOD');
        if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
            $attr = 'ORGANIZER';
        } else if ($method == 'REPLY') {
            $attr = 'ATTENDEE';
        }

        $name = $this->getAttribute($attr, true);
        if (array_key_exists('CN', $name[0])) {
            return $name[0]['CN'];
        }

        $name = $this->getAttribute($attr);
        if (is_a($name, 'PEAR_Error')) {
            return '';
        } else {
            $name = parse_url($name);
            return $name['path'];
        }
    }

    /**
     * Get the email address for this object
     */
    function getEmail()
    {
        $name = '';
        $method = $this->_container->getAttribute('METHOD');
        if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
            $attr = 'ORGANIZER';
        } else if ($method == 'REPLY') {
            $attr = 'ATTENDEE';
        }

        $name = $this->getAttribute($attr);
        if (is_a($name, 'PEAR_Error')) {
            return '';
        } else {
            $name = parse_url($name);
            return $name['path'];
        }
    }

    function getBusyPeriods()
    {
        return $this->_busyPeriods;
    }

    /**
     * Return all the free periods of time in a given period
     */
    function getFreePeriods($startStamp, $endStamp)
    {
        $this->simplify();
        $periods = array();

        // Check that we have data for some part of this period
        if ($this->getEnd() < $startStamp || $this->getStart() > $endStamp) {
            return $periods;
        }

        // Locate the first time in the requested period we have
        // data for.
        $nextstart = max($startStamp, $this->getStart());

        // Check each busy period and add free periods in between
        foreach ($this->_busyPeriods as $start => $end) {
            if ($start <= $endStamp && $end >= $nextstart) {
                $periods[$nextstart] = min($start, $endStamp);
                $nextstart = min($end, $endStamp);
            }
        }

        // If we didn't read the end of the requested period but
        // still have data then mark as free to the end of the period
        // or available data.
        if ($nextstart < $endStamp && $nextstart < $this->getEnd()) {
            $periods[$nextstart] = min($this->getEnd(), $endStamp);
        }

        return $periods;
    }

    /**
     * Add a busy period to the info.
     */
    function addBusyPeriod($type, $start, $end = null, $duration = null)
    {
        if ($type == "FREE") {
            // Make sure this period is not marked as busy.
            return false;
        }

        // Calculate the end time is duration was specified
        $tempEnd = is_null($duration) ? $end : $start + $duration;

        // Make sure the period length is always positive.
        $end = max($start, $tempEnd);
        $start = min($start, $tempEnd);

        if (isset($this->_busyPeriods[$start])) {
            // Allready a period starting at this time. Extend to
            // the length of the longest of the two
            $this->_busyPeriods[$start] = max($end, $this->_busyPeriods[$start]);
        } else {
            // Add a new busy period
            $this->_busyPeriods[$start] = $end;
        }

        return true;
    }

    /**
     * Get the timestamp of the start of the time period this
     * free busy information covers.
     */
    function getStart()
    {
        if (!is_a($this->getAttribute('DTSTART'), 'PEAR_Error')) {
            return $this->getAttribute('DTSTART');
        } else if (count($this->_busyPeriods)) {
            return min(array_keys($this->_busyPeriods));
        } else {
            return false;
        }
    }

    /**
     * Get the timestamp of the end of the time period this
     * free busy information covers.
     */
    function getEnd()
    {
        if (!is_a($this->getAttribute('DTEND'), 'PEAR_Error')) {
            return $this->getAttribute('DTEND');
        } else if (count($this->_busyPeriods)) {
            return max(array_values($this->_busyPeriods));
        } else {
            return false;
        }
    }

    /**
     * Merge the busy periods of another VFreebusy into this one.
     */
    function merge($freebusy, $simplify = true)
    {
        if (!is_a($freebusy, 'Horde_iCalendar_vfreebusy')) {
            return false;
        }

        foreach ($freebusy->getBusyPeriods() as $start => $end) {
            $this->addBusyPeriod('BUSY', $start, $end);
        }
        if ($simplify) {
            $this->simplify();
        }
        return true;
    }

    /**
     * Remove all overlaps and simplify the busy periods array
     * as much as possible.
     */
    function simplify()
    {
        $checked = array();
        $checkedEmpty = true;
        foreach ($this->_busyPeriods as $start => $end) {
            if ($checkedEmpty) {
                $checked[$start] = $end;
                $checkedEmpty = false;
            } else {
                $added = false;
                foreach ($checked as $testStart => $testEnd) {
                    if ($start == $testStart) {
                        $checked[$testStart] = max($testEnd, $end);
                        $added = true;
                    } else if ($end <= $testEnd && $end >= $testStart) {
                        unset($checked[$testStart]);
                        $checked[min($testStart, $start)] = max($testEnd, $end);
                        $added = true;
                    }
                    if ($added) {
                        break;
                    }
                }
                if (!$added) {
                    $checked[$start] = $end;
                }
            }
        }
        ksort($checked, SORT_NUMERIC);
        $this->_busyPeriods = $checked;
    }
}

--- NEW FILE: vjournal.php ---
<?php
/**
 * Class representing vJounrals
 *
 * $Horde: horde/lib/iCalendar/vjournal.php,v 1.3 2003/07/03 09:16:40 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
class Horde_iCalendar_vjournal extends Horde_iCalendar {

    function getType()
    {
        return 'vJournal';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'VJOURNAL');
    }

    function exportvCalendar(&$container)
    {
        return parent::_exportvData('VJOURNAL');
    }
}


--- NEW FILE: vtimezone.php ---
<?php
/**
 * Class representing vTimezones
 *
 * $Horde: horde/lib/iCalendar/vtimezone.php,v 1.3 2003/07/03 09:16:40 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
 class Horde_iCalendar_vtimezone extends Horde_iCalendar {

    function getType()
    {
        return 'vTimeZone';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'VTIMEZONE');
    }

    function exportvCalendar(&$container)
    {
        return parent::_exportvData('VTIMEZONE');
    }
}

class Horde_iCalendar_standard extends Horde_iCalendar {

    function getType()
    {
        return 'standard';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'STANDARD');
    }

    function exportvCalendar(&$container)
    {
        return parent::_exportvData('STANDARD');
    }
}

class Horde_iCalendar_daylight extends Horde_iCalendar {

    function getType()
    {
        return 'daylight';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'DAYLIGHT');
    }

    function exportvCalendar(&$container)
    {
        return parent::_exportvData('DAYLIGHT');
    }
}

--- NEW FILE: vtodo.php ---
<?php
/**
 * Class representing vTodos
 *
 * $Horde: horde/lib/iCalendar/vtodo.php,v 1.3 2003/07/03 09:16:40 mikec Exp $
 *
 * Copyright 2003 Mike Cochrane <mike at graftonhall.co.nz>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Mike Cochrane <mike at graftonhall.co.nz>
 * @version $Revision: 1.1 $
 * @since   Horde 3.0
 * @package horde.iCalendar
 */
 class Horde_iCalendar_vtodo extends Horde_iCalendar {

    function getType()
    {
        return 'vTodo';
    }

    function parsevCalendar($data)
    {
        parent::parsevCalendar($data, 'VTODO');
    }

    function exportvCalendar(&$container)
    {
        return parent::_exportvData('VTODO');
    }
}





More information about the commits mailing list