Initial import of Kolab_Sever from Horde cvs HEAD.
authorGunnar Wrobel <p@rdus.de>
Wed, 11 Feb 2009 19:53:25 +0000 (19:53 +0000)
committerGunnar Wrobel <p@rdus.de>
Wed, 11 Feb 2009 19:53:25 +0000 (19:53 +0000)
36 files changed:
framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt [new file with mode: 0644]
framework/Kolab_Server/examples/Horde/Kolab/Server/server.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/IMAP.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Server/test.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Session.php [new file with mode: 0644]
framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php [new file with mode: 0644]
framework/Kolab_Server/package.xml [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php [new file with mode: 0644]
framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php [new file with mode: 0644]

diff --git a/framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt b/framework/Kolab_Server/doc/Horde/Kolab/Server/usage.txt
new file mode 100644 (file)
index 0000000..6980b27
--- /dev/null
@@ -0,0 +1,113 @@
+The "Horde_Kolab_Server" package allows you to access the Kolab
+Server user database.
+
+
+Installation of the package
+===========================
+
+The package is being distributed as a standard PEAR package by the
+Horde project. As long as you have PEAR installed, installation should
+be straight forward.
+
+ pear channel-discover pear.horde.org
+ pear install --force channel://pear.horde.org/Horde_Kolab_Server
+
+"pear" will probably complain about the library (and its dependencies)
+not being marked stable yet but the "--force" option allows to ignore
+these warnings.
+
+
+Using the package
+===========================
+
+This section will present some examples on how to fetch information
+from the Kolab Server database.
+
+Currently the package only provides a LDAP and a test driver as
+possible backends. The test driver is for internal testing purposes
+only and emulates a LDAP system without requiring access to a real
+LDAP database. So most developers will use the LDAP back end. In order
+to enable the driver some basic configuration is required.
+
+Configurations for Horde are always set in the global "$conf"
+variable. A possible configuration for "Horde_Kolab_Server" could look
+like this:
+
+ global $conf;
+ $conf['kolab']['server']['driver'] = 'ldap';
+ $conf['kolab']['server']['params']['server'] = 'example.com';
+ $conf['kolab']['server']['params']['base_dn'] = 'dc=example,dc=com';
+ $conf['kolab']['server']['params']['bind_dn'] = 'cn=nobody,cn=internal,dc=example,dc=com';
+ $conf['kolab']['server']['params']['bind_pw'] = 'MY_VERY_SECRET_PASSWORD';
+
+With this base configuration the developer can load the code and
+initialize the server object:
+
+ require_once 'Horde/Kolab/Server.php';
+ $server = Horde_Kolab_Server::singleton();
+
+This server handler serves two primary purposes:
+
+ * Identifying the ID of an object on basis of a given attribute
+ * Fetching an object from the database based on the ID
+
+A common request to the Kolab LDAP database is to return the ID of a
+user given a mail address. The "$server" object will return the "DN"
+("distinguished name") of the object in the LDAP database:
+
+ $dn = $server->dnForMailAddress('wrobel@example.com');
+
+The returned "DN" could look like this:
+
+ var_dump($dn);
+
+ string(32) "cn=Gunnar Wrobel,dc=example,dc=com"
+
+For alternative methods of retrieving a particular "DN" you should
+consult the detailed package documentation (see below).
+
+This "DN" can now be used to fetch the object from the database:
+
+ $object = $server->fetch($dn);
+
+The "$server" object will always return an object of class
+"Horde_Kolab_Server_Object". But you will receive a sub class of this
+parent class that matches the type of the object. For the example
+given above the server handler returns an object of type
+"Horde_Kolab_Server_Object_user":
+
+ var_dump(get_class($object));
+
+ string(30) "Horde_Kolab_Server_Object_user"
+
+Depending on the sub class such an object presents different
+features. One primary method is common to all the objects though:
+Fetching attribute values with "get()".
+
+ $attr = $object->get(KOLAB_ATTR_CN);
+
+"KOLAB_ATTR_CN" is being used to retrieve the "common name" of the
+object:
+
+ var_dump($attr);
+
+ string(13) "Gunnar Wrobel"
+
+For additional details about the different objects and attribute types
+you should consult the detailed package documentation (see below).
+
+
+Detailed package documentation
+==============================
+
+A detailed documentation based on the code comments and extracted via
+phpDocumentor can be found at
+http://dev.horde.org/api/framework/. Simply select the package
+"Kolab_Server" in the package selection box in the upper right
+corner.
+
+
+ToDo
+====
+
+Currently the package only implements read access. At some point it should allow write access, too. This is required to convert the Kolab webadmin to use this package.
diff --git a/framework/Kolab_Server/examples/Horde/Kolab/Server/server.php b/framework/Kolab_Server/examples/Horde/Kolab/Server/server.php
new file mode 100644 (file)
index 0000000..55c7c30
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Demonstrates the use of Horde_Kolab_Server::
+ *
+ * $Horde: framework/Kolab_Server/examples/Horde/Kolab/Server/server.php,v 1.2 2008/08/01 07:49:25 wrobel Exp $
+ *
+ * @package Kolab_Server
+ */
+
+/** Configure the system for LDAP access */
+global $conf;
+
+/** Adapt these settings to match your Kolab LDAP server */
+$conf['kolab']['server']['driver'] = 'ldap';
+$conf['kolab']['server']['params']['server'] = 'example.com';
+$conf['kolab']['server']['params']['base_dn'] = 'dc=example,dc=com';
+$conf['kolab']['server']['params']['bind_dn'] = 'cn=nobody,cn=internal,dc=example,dc=com';
+$conf['kolab']['server']['params']['bind_pw'] = 'MY_VERY_SECRET_PASSWORD';
+
+/** Require the main package class */
+require_once 'Horde/Kolab/Server.php';
+
+/** Initialize the server object */
+$server = Horde_Kolab_Server::singleton();
+
+/** Fetch a dn for a mail address */
+$dn = $server->dnForMailAddress('wrobel@example.com');
+var_dump($dn);
+
+/** Fetch the corresponding object */
+$object = $server->fetch($dn);
+var_dump(get_class($object));
+
+/** Display object attributes */
+var_dump($object->get(KOLAB_ATTR_CN));
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP.php
new file mode 100644 (file)
index 0000000..9c4bcc5
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+/**
+ * @package Kolab_Storage
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP.php,v 1.2 2009/01/06 17:49:25 jan Exp $
+ */
+
+/**
+ * The Horde_Kolab_IMAP class provides a wrapper around two different Kolab IMAP
+ * connection types.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP.php,v 1.2 2009/01/06 17:49:25 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.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  Gunnar Wrobel <wrobel@pardus.de>
+ * @author  Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_IMAP {
+
+    /**
+     * IMAP server to connect to.
+     *
+     * @var string
+     */
+    var $_server;
+
+    /**
+     * IMAP server port to connect to.
+     *
+     * @var int
+     */
+    var $_port;
+
+    /**
+     * IMAP connection.
+     *
+     * @var mixed
+     */
+    var $_imap;
+
+    /**
+     * Connection reuse detection signature.
+     *
+     * @var string
+     */
+    var $_reuse_detection;
+
+    /**
+     * Constructor.
+     *
+     * @param string     $server   Server to connect to
+     * @param int        $port     Port to connect to
+     */
+    function Horde_Kolab_IMAP($server, $port)
+    {
+        $this->_server = $server;
+        $this->_port   = $port;
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Horde_Kolab_IMAP instance.
+     * It will only create a new instance if no Horde_Kolab_IMAP instance
+     * exists.
+     *
+     * @static
+     *
+     * @param string     $server                Server name
+     * @param int        $port                  Port
+     * @param boolean    $annotation_required   Do we actually need
+     *                                          the annotation calls?
+     *
+     * @return Horde_Kolab_IMAP|PEAR_Error The concrete reference.
+     */
+    function &singleton($server, $port, $annotation_required = true)
+    {
+        static $instances = array();
+
+        /**
+         * There are Kolab specific PHP functions available that make the IMAP
+         * access more efficient. If these are detected, or if they are not
+         * required for the current operation, the PHP IMAP implementation
+         * should be used.
+         *
+         * The c-client Kolab driver provides quicker IMAP routines so is
+         * preferable whenever possible.
+         */
+        if ($annotation_required) {
+            if (function_exists('imap_status_current')
+                && function_exists('imap_getannotation')) {
+                $driver = 'cclient';
+            } else {
+                $driver = 'pear';
+            }
+        } else {
+            $driver = 'cclient';
+        }
+
+        if (isset($GLOBALS['KOLAB_TESTING'])) {
+            $driver = 'test';
+        }
+
+        $signature = "$server|$port|$driver";
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = &Horde_Kolab_IMAP::factory($server, $port, $driver);
+        }
+
+        return $instances[$signature];
+    }
+
+    /**
+     * Attempts to return a concrete Horde_Kolab_IMAP instance based on the
+     * available PHP functionality.
+     *
+     * @param string     $server                Server name.
+     * @param int        $port                  Server port.
+     * @param string     $driver                Which driver should we use?
+     *
+     * @return Horde_Kolab_IMAP|PEAR_Error The newly created concrete
+     *                                     Horde_Kolab_IMAP instance.
+     */
+    function &factory($server, $port, $driver = 'cclient')
+    {
+        @include_once dirname(__FILE__) . '/IMAP/' . $driver . '.php';
+
+        $class = 'Horde_Kolab_IMAP_' . $driver;
+        if (class_exists($class)) {
+            $driver = &new $class($server, $port);
+        } else {
+            return PEAR::raiseError(sprintf(_("Failed to load Kolab IMAP driver %s"), $driver));
+        }
+
+        return $driver;
+    }
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php
new file mode 100644 (file)
index 0000000..88d3e9f
--- /dev/null
@@ -0,0 +1,778 @@
+<?php
+/**
+ * @package Kolab_Storage
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php,v 1.3 2009/01/06 17:49:25 jan Exp $
+ */
+
+/**
+ * The Horde_Kolab_IMAP_Connection_cclient class connects to an IMAP server using
+ * the IMAP functionality within PHP.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/cclient.php,v 1.3 2009/01/06 17:49:25 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.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  Gunnar Wrobel <wrobel@pardus.de>
+ * @author  Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_IMAP_cclient extends Horde_Kolab_IMAP {
+
+    /**
+     * Basic IMAP connection string.
+     *
+     * @var string
+     */
+    var $_base_mbox;
+
+    /**
+     * IMAP connection string that includes the folder.
+     *
+     * @var string
+     */
+    var $_mbox;
+
+    /**
+     * The signature of the current connection.
+     *
+     * @var string
+     */
+    var $_signature;
+
+    /**
+     * IMAP user name.
+     *
+     * @var string
+     */
+    var $_login;
+
+    /**
+     * IMAP password.
+     *
+     * @var string
+     */
+    var $_password;
+
+    /**
+     * Connects to the IMAP server.
+     *
+     * @param string $login     The user account name.
+     * @param string $password  The user password.
+     * @param boolean $tls      Should TLS be used for the connection?
+     *
+     * @return boolean|PEAR_Error  True in case the connection was opened
+     *                             successfully.
+     */
+    function connect($login, $password, $tls = false)
+    {
+        $options = '';
+        if (!$tls) {
+            $options = '/notls';
+        }
+
+        $mbox = '{' . $this->_server . ':' . $this->_port
+            . $options . '}';
+
+        $this->_signature = "$mbox|$login|$password";
+        if ($this->_signature == $this->_reuse_detection) {
+            return true;
+        }
+
+        $this->_mbox = $this->_base_mbox = $mbox;
+        $this->_login = $login;
+        $this->_password = $password;
+        $this->_imap = null;
+
+        $this->_reuse_detection = $this->_signature;
+
+        return true;
+    }
+
+    /**
+     * Lazy connect to the IMAP server.
+     *
+     * @return mixed  True in case the connection was opened successfully, a
+     *                PEAR error otherwise.
+     */
+    function _connect()
+    {
+        $result = @imap_open($this->_base_mbox, $this->_login, $this->_password, OP_HALFOPEN);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Server: %s. Error: %s"), $this->_server, @imap_last_error()));
+        }
+        $this->_imap = $result;
+        return true;
+    }
+
+    /**
+     * Disconnects from the IMAP server. If not really necessary this
+     * should not be called. Once the page got served the connections
+     * should be closed anyhow and if there is a chance to reuse the
+     * connection it should be used.
+     *
+     * @return mixed  True in case the connection was closed successfully, a
+     *                PEAR error otherwise.
+     */
+    function disconnect()
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $this->_reuse_detection = null;
+
+        $result = @imap_close($this->_imap);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Server: %s. Error: %s"), $this->_server, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Opens the given folder.
+     *
+     * @param string $folder  The folder to open.
+     *
+     * @return mixed  True in case the folder was opened successfully, a PEAR
+     *                error otherwise.
+     */
+    function select($folder)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $this->_mbox = $this->_base_mbox . $folder;
+
+        $result = @imap_reopen($this->_imap, $this->_mbox);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Does the given folder exist?
+     *
+     * @param string $folder  The folder to check.
+     *
+     * @return mixed  True in case the folder exists, false otherwise
+     */
+    function exists($folder)
+    {
+        $folders = $this->getMailboxes();
+        if (is_a($folders, 'PEAR_Error')) {
+            return $folders;
+        }
+        return in_array($folder, $folders);
+    }
+
+    /**
+     * Create the specified folder.
+     *
+     * @param string $folder  The folder to create.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function create($folder)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $mbox = $this->_base_mbox . $folder;
+        $result = @imap_createmailbox($this->_imap, $mbox);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Delete the specified folder.
+     *
+     * @param string $folder  The folder to delete.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function delete($folder)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $mbox = $this->_base_mbox . $folder;
+        $result = @imap_deletemailbox($this->_imap, $mbox);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Rename the specified folder.
+     *
+     * @param string $old  The folder to rename.
+     * @param string $new  The new name of the folder.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function rename($old, $new)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_renamemailbox($this->_imap,
+                                      $this->_base_mbox . $old,
+                                      $this->_base_mbox . $new);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $old, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Returns the status of the current folder.
+     *
+     * @return array  An array that contains 'uidvalidity' and 'uidnext'.
+     */
+    function status()
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $status = @imap_status_current($this->_imap, SA_MESSAGES | SA_UIDVALIDITY | SA_UIDNEXT);
+        if (!$status) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error()));
+        }
+
+        return array('uidvalidity' => $status->uidvalidity,
+                     'uidnext' => $status->uidnext);
+    }
+
+    /**
+     * Returns the uids of the messages in this folder.
+     *
+     * @return mixed  The message ids or a PEAR error in case of an error.
+     */
+    function getUids()
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $uids = @imap_search($this->_imap, 'UNDELETED', SE_UID);
+        if (!is_array($uids)) {
+            $uids = array();
+        }
+
+        return $uids;
+    }
+
+    /**
+     * Searches the current folder using the given list of search criteria.
+     *
+     * @param string $search_list  A list of search criteria.
+     *
+     * @return mixed  The list of matching message ids or a PEAR error in case
+     *                of an error.
+     */
+    function search($search_list)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_search($this->_imap, $search_list, SE_UID);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Searches the headers of the messages. c-client does not allow using
+     * "HEADER" as it is possible with Net/IMAP, so we need a workaround.
+     *
+     * @param string $field  The name of the header field.
+     * @param string $value  The value that field should match.
+     *
+     * @return mixed  The list of matching message ids or a PEAR error in case
+     *                of an error.
+     */
+    function searchHeaders($field, $value)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $uids = $this->getUids();
+        if (is_a($uids, 'PEAR_Error')) {
+            return $uids;
+        }
+
+        $result = array();
+        foreach ($uids as $uid) {
+            $header = $this->getMessageHeader($uid, false);
+            if (is_a($header, 'PEAR_Error')) {
+                return $header;
+            }
+            $header_array = MIME_Headers::parseHeaders($header);
+            if (isset($header_array[$field]) && $header_array[$field] == $value) {
+                $result[] = $uid;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Retrieves the message headers for a given message id.
+     *
+     * @param integer $uid            The message id.
+     * @param boolean $peek_for_body  Prefetch the body.
+     *
+     * @return mixed  The message header or a PEAR error in case of an error.
+     */
+    function getMessageHeader($uid, $peek_for_body = true)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $flags = FT_UID;
+        if ($peek_for_body) {
+            $flags |= FT_PREFETCHTEXT;
+        }
+
+        $result = @imap_fetchheader($this->_imap, $uid, $flags);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error()));
+        }
+
+        return $result;
+    }
+
+    /**
+     * Retrieves the message body for a given message id.
+     *
+     * @param integer $uid  The message id.
+     *
+     * @return mixed  The message body or a PEAR error in case of an error.
+     */
+    function getMessageBody($uid)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_body($this->_imap, $uid, FT_UID);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error()));
+        }
+
+        return $result;
+    }
+
+    /**
+     * Retrieves the full message text for a given message id.
+     *
+     * @param integer $uid  The message id.
+     *
+     * @return mixed  The message text or a PEAR error in case of an error.
+     */
+    function getMessage($uid)
+    {
+        $header = $this->getMessageHeader($uid);
+        if (is_a($header, 'PEAR_Error')) {
+            return $header;
+        }
+
+        $body = $this->getMessageBody($uid);
+        if (is_a($body, 'PEAR_Error')) {
+            return $body;
+        }
+
+        return $header . $body;
+    }
+
+    /**
+     * Retrieves a list of mailboxes on the server.
+     *
+     * @return mixed  The list of mailboxes or a PEAR error in case of an
+     *                error.
+     */
+    function getMailboxes()
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $folders = array();
+
+        $result = @imap_list($this->_imap, $this->_base_mbox, '*');
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_base_mbox, @imap_last_error()));
+        }
+
+        $server_len = strlen($this->_base_mbox);
+        foreach ($result as $folder) {
+            if (substr($folder, 0, $server_len) == $this->_base_mbox) {
+                $folders[] = substr($folder, $server_len);
+            }
+        }
+
+        return $folders;
+    }
+
+    /**
+     * Fetches the annotation on a folder.
+     *
+     * @param string $entries        The entry to fetch.
+     * @param string $value          The specific value to fetch.
+     * @param string $mailbox_name   The name of the folder.
+     *
+     * @return mixed  The annotation value or a PEAR error in case of an error.
+     */
+    function getAnnotation($entries, $value, $mailbox_name)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        static $annotations = array();
+
+        $signature = "$this->_signature|$entries|$value|$mailbox_name";
+
+        if (!isset($annotations[$signature])) {
+            $result = @imap_getannotation($this->_imap, $mailbox_name, $entries, $value);
+            if (isset($result[$value])) {
+                $annotations[$signature] = $result[$value];
+            } else {
+                $annotations[$signature] = '';
+            }
+        }
+
+        return $annotations[$signature];
+    }
+
+    /**
+     * Sets the annotation on a folder.
+     *
+     * @param string $entries        The entry to set.
+     * @param array  $values         The values to set
+     * @param string $mailbox_name   The name of the folder.
+     *
+     * @return mixed  True if successfull, a PEAR error otherwise.
+     */
+    function setAnnotation($entries, $values, $mailbox_name)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        foreach ($values as $key => $value) {
+            $result = @imap_setannotation($this->_imap, $mailbox_name, $entries, $key, $value);
+            if (!$result) {
+                return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $mailbox_name, @imap_last_error()));
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Retrieve the access rights from a folder
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     *
+     * @return mixed An array of rights if successfull, a PEAR error
+     * otherwise.
+     */
+    function getACL($folder)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_getacl($this->_imap, $folder);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Retrieve the access rights from a folder not owned by the current user
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     *
+     * @return mixed An array of rights if successfull, a PEAR error
+     * otherwise.
+     */
+    function getMyRights($folder)
+    {
+        if (!function_exists('imap_myrights')) {
+            return PEAR::raiseError(sprintf(_("PHP does not support imap_myrights."), $folder, @imap_last_error()));
+        }
+
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_myrights($this->_imap, $folder);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error()));
+        }
+        return $result;
+
+    }
+
+    /**
+     * Set the access rights for a folder
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     * @param string $user    The user to set the ACLs for
+     * @param string $acl     The ACLs
+     *
+     * @return mixed True if successfull, a PEAR error otherwise.
+     */
+    function setACL($folder, $user, $acl)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_setacl($this->_imap, $folder, $user, $acl);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Delete the access rights for a user.
+     *
+     * @param string $folder  The folder that should be modified.
+     * @param string $user    The user that should get the ACLs removed
+     *
+     * @return mixed True if successfull, a PEAR error otherwise.
+     */
+    function deleteACL($folder, $user)
+    {
+        return $this->setACL($folder, $user, '');
+    }
+
+    /**
+     * Appends a message to the current folder.
+     *
+     * @param string $msg  The message to append.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function appendMessage($msg)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_append($this->_imap, $this->_mbox, $msg);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $this->_mbox, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Copies a message to a new folder.
+     *
+     * @param integer $uid        IMAP message id.
+     * @param string $new_folder  Target folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function copyMessage($uid, $new_folder)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_mail_copy($this->_imap, $uid, $new_folder, CP_UID);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $new_folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Moves a message to a new folder.
+     *
+     * @param integer $uid        IMAP message id.
+     * @param string $new_folder  Target folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function moveMessage($uid, $new_folder)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_mail_move($this->_imap, $uid, $new_folder, CP_UID);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Folder: %s. Error: %s"), $new_folder, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Deletes messages from the current folder.
+     *
+     * @param integer $uids  IMAP message ids.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function deleteMessages($uids)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        if (!is_array($uids)) {
+            $uids = array($uids);
+        }
+
+        foreach($uids as $uid) {
+            $result = @imap_delete($this->_imap, $uid, FT_UID);
+            if (!$result) {
+                return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error()));
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Undeletes a message in the current folder.
+     *
+     * @param integer $uid  IMAP message id.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function undeleteMessages($uid)
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_undelete($this->_imap, $uid, FT_UID);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $uid, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Expunges messages in the current folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function expunge()
+    {
+        if (!isset($this->_imap)) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $result = @imap_expunge($this->_imap);
+        if (!$result) {
+            return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"), $this->_mbox, @imap_last_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Return the currently selected mailbox
+     *
+     * @return string  The mailbox currently selected
+     */
+    function current()
+    {
+        return $this->_mbox;
+    }
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php
new file mode 100644 (file)
index 0000000..49b911e
--- /dev/null
@@ -0,0 +1,520 @@
+<?php
+/**
+ * @package Kolab_Storage
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php,v 1.2 2009/01/06 17:49:25 jan Exp $
+ */
+
+/**
+ * The Horde_Kolab library requires version >= 1.0.3 of Net_IMAP (i.e. a
+ * version that includes support for the ANNOTATEMORE IMAP extension). The
+ * latest version of Net_IMAP can be obtained from
+ * http://pear.php.net/get/Net_IMAP
+ */
+require_once 'Net/IMAP.php';
+
+/**
+ * The Horde_Kolab_IMAP_Connection_pear class connects to an IMAP server using the
+ * Net_IMAP PEAR package.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/pear.php,v 1.2 2009/01/06 17:49:25 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.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  Gunnar Wrobel <wrobel@pardus.de>
+ * @author  Thomas Jarosch <thomas.jarosch@intra2net.com>
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_IMAP_pear extends Horde_Kolab_IMAP {
+
+    /**
+     * The signature of the current connection
+     *
+     * @var string
+     */
+    var $_signature;
+
+    /**
+     * Connects to the IMAP server.
+     *
+     * @param string  $login     The user account name.
+     * @param string  $password  The user password.
+     * @param boolean $tls       Should TLS be used for the connection?
+     *
+     * @return mixed  True in case the connection was opened successfully, a
+     *                PEAR error otherwise.
+     */
+    function connect($login, $password, $tls = false)
+    {
+        $this->_signature = $this->_server . '|' . $this->_port . "|$login|$password|$tls";
+
+        // Reuse existing connection?
+        if ($this->_signature == $this->_reuse_detection) {
+            return true;
+        }
+
+        $this->_imap = &new Net_IMAP($this->_server, $this->_port);
+        $result = $this->_imap->login($login, $password, true, false);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $this->_reuse_detection = $this->_signature;
+
+        return true;
+    }
+
+    /**
+     * Disconnects from the IMAP server.
+     *
+     * @return mixed  True in case the connection was closed successfully, a
+     *                PEAR error otherwise.
+     */
+    function disconnect()
+    {
+        $this->_reuse_detection = null;
+        return $this->_imap->disconnect();
+    }
+
+    /**
+     * Opens the given folder.
+     *
+     * @param string $folder  The folder to open
+     *
+     * @return mixed  True in case the folder was opened successfully, a PEAR
+     *                error otherwise.
+     */
+    function select($folder)
+    {
+        return $this->_imap->selectMailbox($folder);
+    }
+
+    /**
+     * Does the given folder exist?
+     *
+     * @param string $folder  The folder to check.
+     *
+     * @return mixed True in case the folder exists, false otherwise
+     */
+    function exists($folder)
+    {
+        return $this->_imap->mailboxExist($folder);
+    }
+
+    /**
+     * Create the specified folder.
+     *
+     * @param string $folder  The folder to create.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function create($folder)
+    {
+        return $this->_imap->createMailbox($folder);
+    }
+
+    /**
+     * Delete the specified folder.
+     *
+     * @param string $folder  The folder to delete.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function delete($folder)
+    {
+        return $this->_imap->deleteMailbox($folder);
+    }
+
+    /**
+     * Rename the specified folder.
+     *
+     * @param string $old  The folder to rename.
+     * @param string $new  The new name of the folder.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function rename($old, $new)
+    {
+        return $this->_imap->renameMailbox($old, $new);
+    }
+
+    /**
+     * Returns the status of the current folder.
+     *
+     * @return array  An array that contains 'uidvalidity' and 'uidnext'.
+     */
+    function status()
+    {
+        $result = array();
+
+        $mailbox = $this->_imap->getCurrentMailbox();
+
+        // Net_IMAP is not very efficent here
+        $ret = $this->_imap->cmdStatus($mailbox, 'UIDVALIDITY');
+        $result['uidvalidity'] = $ret['PARSED']['STATUS']['ATTRIBUTES']['UIDVALIDITY'];
+
+        $ret = $this->_imap->cmdStatus($mailbox, 'UIDNEXT');
+        $result['uidnext'] = $ret['PARSED']['STATUS']['ATTRIBUTES']['UIDNEXT'];
+
+        return $result;
+    }
+
+    /**
+     * Returns the message ids of the messages in this folder.
+     *
+     * @return array  The message ids.
+     */
+    function getUids()
+    {
+        $uids = $this->_imap->search('UNDELETED', true);
+        if (!is_array($uids)) {
+            $uids = array();
+        }
+        return $uids;
+    }
+
+    /**
+     * Searches the current folder using the given list of search criteria.
+     *
+     * @param string $search_list  A list of search criteria.
+     *
+     * @return mixed  The list of matching message ids or a PEAR error in case
+     *                of an error.
+     */
+    function search($search_list, $uidSearch = true)
+    {
+        return $this->_imap->search($search_list, $uidSearch);
+    }
+
+    /**
+     * Searches the headers of the messages.
+     *
+     * @param string $field  The name of the header field.
+     * @param string $value  The value that field should match.
+     *
+     * @return mixed  The list of matching message ids or a PEAR error in case
+     *                of an error.
+     */
+    function searchHeaders($field, $value)
+    {
+        return $this->_imap->search('HEADER "' . $field . '" "' . $value . '"', true);
+    }
+
+    /**
+     * Retrieves the message headers for a given message id.
+     *
+     * @param int $uid                The message id.
+     * @param boolean $peek_for_body  Prefetch the body.
+     *
+     * @return mixed  The message header or a PEAR error in case of an error.
+     */
+    function getMessageHeader($uid, $peek_for_body = true)
+    {
+        $ret = $this->_imap->cmdUidFetch($uid, 'BODY[HEADER]');
+        if (String::upper($ret['RESPONSE']['CODE']) != 'OK') {
+            return PEAR::raiseError(sprintf(_("Failed fetching headers of IMAP message %s. Error was %s"),
+                                            $uid,
+                                            $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE']));
+        }
+
+        if (isset($ret['PARSED'])) {
+            foreach ($ret['PARSED'] as $msg) {
+                if (isset($msg['EXT']['BODY[HEADER]']['CONTENT'])) {
+                    return $msg['EXT']['BODY[HEADER]']['CONTENT'];
+                }
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * Retrieves the message body for a given message id.
+     *
+     * @param integet $uid  The message id.
+     *
+     * @return mixed  The message body or a PEAR error in case of an error.
+     */
+    function getMessageBody($uid)
+    {
+        $ret = $this->_imap->cmdUidFetch($uid, 'BODY[TEXT]');
+        if (String::upper($ret['RESPONSE']['CODE']) != 'OK') {
+            return PEAR::raiseError(sprintf(_("Failed fetching body of IMAP message %s. Error was %s"),
+                                            $uid,
+                                            $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE']));
+        }
+
+        if (isset($ret['PARSED'])) {
+            foreach ($ret['PARSED'] as $msg) {
+                if (isset($msg['EXT']['BODY[TEXT]']['CONTENT'])) {
+                    return $msg['EXT']['BODY[TEXT]']['CONTENT'];
+                }
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * Retrieves the full message text for a given message id.
+     *
+     * @param integer $uid  The message id.
+     *
+     * @return mixed  The message text or a PEAR error in case of an error.
+     */
+    function getMessage($uid)
+    {
+        $ret = $this->_imap->cmdUidFetch($uid, 'RFC822');
+        if (String::upper($ret['RESPONSE']['CODE']) != 'OK') {
+            return PEAR::raiseError(sprintf(_("Failed fetching IMAP message %s. Error was %s"),
+                                            $uid,
+                                            $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE']));
+        }
+
+        if (isset($ret['PARSED'])) {
+            foreach ($ret['PARSED'] as $msg) {
+                if (isset($msg['EXT']['RFC822']['CONTENT'])) {
+                    return $msg['EXT']['RFC822']['CONTENT'];
+                }
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * Retrieves a list of mailboxes on the server.
+     *
+     * @return mixed  The list of mailboxes or a PEAR error in case of an
+     *                error.
+     */
+    function getMailboxes()
+    {
+        return $this->_imap->getMailboxes();
+    }
+
+    /**
+     * Fetches the annotation on a folder.
+     *
+     * @param string $entries       The entry to fetch.
+     * @param string $value         The specific value to fetch.
+     * @param string $mailbox_name  The name of the folder.
+     *
+     * @return mixed  The annotation value or a PEAR error in case of an error.
+     */
+    function getAnnotation($entries, $value, $mailbox_name)
+    {
+        static $annotations = array();
+
+        $signature = "$this->_signature|$entries|$value|$mailbox_name";
+
+        if (!isset($annotations[$signature])) {
+            $annotations[$signature] = $this->_imap->getAnnotation($entries, $value, $mailbox_name);
+        }
+
+        return $annotations[$signature];
+    }
+
+    /**
+     * Sets the annotation on a folder.
+     *
+     * @param string $entries        The entry to set.
+     * @param array  $values         The values to set
+     * @param string $mailbox_name   The name of the folder.
+     *
+     * @return mixed  True if successfull, a PEAR error otherwise.
+     */
+    function setAnnotation($entries, $values, $mailbox_name)
+    {
+        return $this->_imap->setAnnotation($entries, $values, $mailbox_name);
+    }
+
+    /**
+     * Retrieve the access rights from a folder
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     *
+     * @return mixed An array of rights if successfull, a PEAR error
+     * otherwise.
+     */
+    function getACL($folder)
+    {
+        $result = $this->_imap->getACL($folder);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $acl = array();
+        foreach ($result as $user) {
+            $acl[$user['USER']] = $user['RIGHTS'];
+        }
+        return $acl;
+    }
+
+    /**
+     * Retrieve the access rights on a folder not owned by the current user
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     *
+     * @return mixed An array of rights if successfull, a PEAR error
+     * otherwise.
+     */
+    function getMyRights($folder)
+    {
+        $result = $this->_imap->getMyRights($folder);
+        return $result;
+    }
+
+    /**
+     * Set the access rights for a folder
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     * @param string $user    The user to set the ACLs for
+     * @param string $acl     The ACLs
+     *
+     * @return mixed True if successfull, a PEAR error otherwise.
+     */
+    function setACL($folder, $user, $acl)
+    {
+        return $this->_imap->setACL($folder, $user, $acl);
+    }
+
+    /**
+     * Delete the access rights for a user.
+     *
+     * @param string $folder  The folder that should be modified.
+     * @param string $user    The user that should get the ACLs removed
+     *
+     * @return mixed True if successfull, a PEAR error otherwise.
+     */
+    function deleteACL($folder, $user)
+    {
+        return $this->_imap->deleteACL($folder, $user);
+    }
+
+    /**
+     * Appends a message to the current folder.
+     *
+     * @param string $msg  The message to append.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function appendMessage($msg)
+    {
+        return $this->_imap->appendMessage($msg);
+    }
+
+    /**
+     * Copies a message to a new folder.
+     *
+     * @param integer $uid        IMAP message id.
+     * @param string $new_folder  Target folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function copyMessage($uid, $new_folder)
+    {
+        $ret = $this->_imap->cmdUidCopy($uid, $new_folder);
+        if (String::upper($ret['RESPONSE']['CODE']) != 'OK') {
+            return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"),
+                                            $uid,
+                                            $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE']));
+        }
+        return true;
+    }
+
+    /**
+     * Moves a message to a new folder.
+     *
+     * @param integer $uid        IMAP message id.
+     * @param string $new_folder  Target folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function moveMessage($uid, $new_folder)
+    {
+        $result = $this->copyMessage($uid, $new_folder);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $result = $this->deleteMessages($uid);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $result = $this->expunge();
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        return true;
+    }
+
+    /**
+     * Deletes messages from the current folder.
+     *
+     * @param integer $uids  IMAP message ids.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function deleteMessages($uids)
+    {
+        if (!is_array($uids)) {
+            $uids = array($uids);
+        }
+
+        foreach ($uids as $uid) {
+            $ret = $this->_imap->cmdUidStore($uid, '+FLAGS.SILENT', '\Deleted');
+            if (String::upper($ret['RESPONSE']['CODE']) != 'OK') {
+                return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"),
+                                                $uid,
+                                                $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE']));
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Undeletes a message in the current folder.
+     *
+     * @param integer $uid  IMAP message id.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function undeleteMessages($uid)
+    {
+        $ret = $this->_imap->cmdUidStore($uid, '-FLAGS.SILENT', '\Deleted');
+        if (String::upper($ret['RESPONSE']['CODE']) != 'OK') {
+            return PEAR::raiseError(sprintf(_("IMAP error. Message: %s. Error: %s"),
+                                            $uid,
+                                            $ret['RESPONSE']['CODE'] . ', ' . $ret['RESPONSE']['STR_CODE']));
+        }
+        return true;
+    }
+
+    /**
+     * Expunges messages in the current folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function expunge()
+    {
+        return $this->_imap->expunge();
+    }
+
+    /**
+     * Return the currently selected mailbox
+     *
+     * @return string  The mailbox currently selected
+     */
+    function current()
+    {
+        return $this->_imap->getCurrentMailbox();
+    }
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php b/framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php
new file mode 100644 (file)
index 0000000..ee8d0c4
--- /dev/null
@@ -0,0 +1,728 @@
+<?php
+/**
+ * @package Kolab_Storage
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php,v 1.3 2009/01/14 23:39:11 wrobel Exp $
+ */
+
+/**
+ * Indicate that a mail has been marked as deleted
+ */
+define('KOLAB_IMAP_FLAG_DELETED', 1);
+
+/**
+ * The Horde_Kolab_IMAP_Connection_test class simulates an IMAP server for
+ * testing purposes.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/IMAP/test.php,v 1.3 2009/01/14 23:39:11 wrobel Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.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  Gunnar Wrobel <wrobel@pardus.de>
+ * @package Kolab_Storage
+ */
+class Horde_Kolab_IMAP_test extends Horde_Kolab_IMAP {
+
+    /**
+     * If we are supposed to be connected this holds the user
+     * credentials and some connection details.
+     *
+     * @var string
+     */
+    var $_connected;
+
+    /**
+     * Login of the current user
+     *
+     * @var string
+     */
+    var $_user;
+
+    /**
+     * The data of the mailbox currently opened
+     *
+     * @var array
+     */
+    var $_mbox = null;
+
+    /**
+     * The name of the mailbox currently opened
+     *
+     * @var array
+     */
+    var $_mboxname = null;
+
+    /**
+     * Prepare the dummy server.
+     *
+     * @param string  $login     The user account name.
+     * @param string  $password  The user password.
+     * @param boolean $tls       Should TLS be used for the connection?
+     *
+     * @return mixed  True in case the connection was opened successfully, a
+     *                PEAR error otherwise.
+     */
+    function connect($login, $password, $tls = false)
+    {
+        if (!is_array($GLOBALS['KOLAB_TESTING'])) {
+            /* Simulate an empty IMAP server */
+            $GLOBALS['KOLAB_TESTING'] = array();
+        }
+
+        $tls = ($tls) ? 'tls' : 'notls';
+        $this->_connected = $login . ':' . $password . ':' . $tls;
+        $this->_user = $login;
+        $this->_mbox = null;
+        $this->_mboxname = null;
+    }
+
+    /**
+     * Disconnects from the IMAP server.
+     *
+     * @return mixed  True in case the connection was closed successfully, a
+     *                PEAR error otherwise.
+     */
+    function disconnect()
+    {
+        $this->_connected = null;
+    }
+
+    function _parseFolder($folder)
+    {
+        if (substr($folder, 0, 5) == 'INBOX') {
+            $user = split('@', $this->_user);
+            return 'user/' . $user[0] . substr($folder, 5);
+        }
+        return $folder;
+    }
+
+    /**
+     * Opens the given folder.
+     *
+     * @param string $folder  The folder to open
+     *
+     * @return mixed  True in case the folder was opened successfully, a PEAR
+     *                error otherwise.
+     */
+    function select($folder)
+    {
+        $folder = $this->_parseFolder($folder);
+        if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) {
+            return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $folder));
+        }
+        $this->_mbox = &$GLOBALS['KOLAB_TESTING'][$folder];
+        $this->_mboxname = $folder;
+        return true;
+    }
+
+    /**
+     * Does the given folder exist?
+     *
+     * @param string $folder  The folder to check.
+     *
+     * @return mixed True in case the folder exists, false otherwise
+     */
+    function exists($folder)
+    {
+        $folder = $this->_parseFolder($folder);
+        if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Create the specified folder.
+     *
+     * @param string $folder  The folder to create.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function create($folder)
+    {
+        $folder = $this->_parseFolder($folder);
+        if (isset($GLOBALS['KOLAB_TESTING'][$folder])) {
+            return PEAR::raiseError(sprintf("IMAP folder %s does already exist!", $folder));
+        }
+        $GLOBALS['KOLAB_TESTING'][$folder] = array(
+            'status' => array(
+                'uidvalidity' => time(),
+                'uidnext' => 1),
+            'mails' => array(),
+            'permissions' => array(),
+            'annotations' => array(),
+        );
+        return true;
+    }
+
+    /**
+     * Delete the specified folder.
+     *
+     * @param string $folder  The folder to delete.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function delete($folder)
+    {
+        $folder = $this->_parseFolder($folder);
+        if (!isset($GLOBALS['KOLAB_TESTING'][$folder])) {
+            return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $folder));
+        }
+        unset($GLOBALS['KOLAB_TESTING'][$folder]);
+        return true;
+    }
+
+    /**
+     * Rename the specified folder.
+     *
+     * @param string $old  The folder to rename.
+     * @param string $new  The new name of the folder.
+     *
+     * @return mixed True in case the operation was successfull, a
+     *               PEAR error otherwise.
+     */
+    function rename($old, $new)
+    {
+        $old = $this->_parseFolder($old);
+        $new = $this->_parseFolder($new);
+
+        if (!isset($GLOBALS['KOLAB_TESTING'][$old])) {
+            return PEAR::raiseError(sprintf("IMAP folder %s does not exist!", $old));
+        }
+        if (isset($GLOBALS['KOLAB_TESTING'][$new])) {
+            return PEAR::raiseError(sprintf("IMAP folder %s does already exist!", $new));
+        }
+        $GLOBALS['KOLAB_TESTING'][$new] = $GLOBALS['KOLAB_TESTING'][$old];
+        unset($GLOBALS['KOLAB_TESTING'][$old]);
+        return true;
+    }
+
+    /**
+     * Returns the status of the current folder.
+     *
+     * @return array  An array that contains 'uidvalidity' and 'uidnext'.
+     */
+    function status()
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        return $this->_mbox['status'];
+    }
+
+    /**
+     * Returns the message ids of the messages in this folder.
+     *
+     * @return array  The message ids.
+     */
+    function getUids()
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        $uids = array();
+        foreach ($this->_mbox['mails'] as $uid => $mail) {
+            if (!($mail['flags'] & KOLAB_IMAP_FLAG_DELETED)) {
+                $uids[] = $uid;
+            }
+        }
+        return $uids;
+    }
+
+    /**
+     * Searches the current folder using the given list of search criteria.
+     *
+     * @param string $search_list  A list of search criteria.
+     *
+     * @return mixed  The list of matching message ids or a PEAR error in case
+     *                of an error.
+     */
+    function search($search_list, $uidSearch = true)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        $uids = array();
+        if (substr($search_list, 0, 7) == 'SUBJECT') {
+            $needle = '^Subject: ' . substr($search_list, 8);
+            foreach ($this->_mbox['mails'] as $uid => $mail) {
+                if (preg_match($needle, $mail['header'])) {
+                    $uids[] = $uid;
+                }
+            }
+        } else if (substr($search_list, 0, 6) == 'HEADER') {
+            preg_match('([^ ]*) ([^ ]*)', substr($search_list, 7), $matches);
+            $needle = '^' . $matches[0] . ': ' . $matches[1];
+            foreach ($this->_mbox['mails'] as $uid => $mail) {
+                if (preg_match($needle, $mail['header'])) {
+                    $uids[] = $uid;
+                }
+            }
+
+        }
+        return $uids;
+    }
+
+    /**
+     * Searches the headers of the messages.
+     *
+     * @param string $field  The name of the header field.
+     * @param string $value  The value that field should match.
+     *
+     * @return mixed  The list of matching message ids or a PEAR error in case
+     *                of an error.
+     */
+    function searchHeaders($field, $value)
+    {
+        return $this->search('HEADER "' . $field . '" "' . $value . '"', true);
+    }
+
+    /**
+     * Retrieves the message headers for a given message id.
+     *
+     * @param int $uid                The message id.
+     * @param boolean $peek_for_body  Prefetch the body.
+     *
+     * @return mixed  The message header or a PEAR error in case of an error.
+     */
+    function getMessageHeader($uid, $peek_for_body = true)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        if (!isset($this->_mbox['mails'][$uid])) {
+            return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+        }
+        return $this->_mbox['mails'][$uid]['header'];
+    }
+
+    /**
+     * Retrieves the message body for a given message id.
+     *
+     * @param integet $uid  The message id.
+     *
+     * @return mixed  The message body or a PEAR error in case of an error.
+     */
+    function getMessageBody($uid)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        if (!isset($this->_mbox['mails'][$uid])) {
+            return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+        }
+        return $this->_mbox['mails'][$uid]['body'];
+    }
+
+    /**
+     * Retrieves the full message text for a given message id.
+     *
+     * @param integer $uid  The message id.
+     *
+     * @return mixed  The message text or a PEAR error in case of an error.
+     */
+    function getMessage($uid)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        if (!isset($this->_mbox['mails'][$uid])) {
+            return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+        }
+        return $this->_mbox['mails'][$uid]['header'] . $this->_mbox['mails'][$uid]['body'];
+    }
+
+    /**
+     * Retrieves a list of mailboxes on the server.
+     *
+     * @return mixed  The list of mailboxes or a PEAR error in case of an
+     *                error.
+     */
+    function getMailboxes()
+    {
+        $mboxes = array_keys($GLOBALS['KOLAB_TESTING']);
+        $user = split('@', $this->_user);
+        $pattern = '#^user/' . $user[0] . '#';
+        $result = array();
+        foreach ($mboxes as $mbox) {
+            $result[] = preg_replace($pattern, 'INBOX', $mbox);
+        }
+        return $result;
+    }
+
+    /**
+     * Fetches the annotation on a folder.
+     *
+     * @param string $entries       The entry to fetch.
+     * @param string $value         The specific value to fetch.
+     * @param string $mailbox_name  The name of the folder.
+     *
+     * @return mixed  The annotation value or a PEAR error in case of an error.
+     */
+    function getAnnotation($entries, $value, $mailbox_name)
+    {
+        $mailbox_name = $this->_parseFolder($mailbox_name);
+        $old_mbox = null;
+        if ($mailbox_name != $this->_mboxname) {
+            $old_mbox = $this->_mboxname;
+            $result = $this->select($mailbox_name);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        if (!isset($this->_mbox['annotations'][$entries])
+            || !isset($this->_mbox['annotations'][$entries][$value])) {
+            return false;
+        }
+        $annotation = $this->_mbox['annotations'][$entries][$value];
+        if ($old_mbox) {
+            $this->select($old_mbox);
+        }
+        return $annotation;
+    }
+
+    /**
+     * Sets the annotation on a folder.
+     *
+     * @param string $entries        The entry to set.
+     * @param array  $values         The values to set
+     * @param string $mailbox_name   The name of the folder.
+     *
+     * @return mixed  True if successfull, a PEAR error otherwise.
+     */
+    function setAnnotation($entries, $values, $mailbox_name)
+    {
+        $mailbox_name = $this->_parseFolder($mailbox_name);
+        $old_mbox = null;
+        if ($mailbox_name != $this->_mboxname) {
+            $old_mbox = $this->_mboxname;
+            $result = $this->select($mailbox_name);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        if (!isset($this->_mbox['annotations'][$entries])) {
+            $this->_mbox['annotations'][$entries] = array();
+        }
+        foreach ($values as $key => $value) {
+            $this->_mbox['annotations'][$entries][$key] = $value;
+        }
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Retrieve the access rights from a folder
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     *
+     * @return mixed An array of rights if successfull, a PEAR error
+     * otherwise.
+     */
+    function getACL($folder)
+    {
+        $folder = $this->_parseFolder($folder);
+        $old_mbox = null;
+        if ($folder != $this->_mboxname) {
+            $old_mbox = $this->_mboxname;
+            $result = $this->select($folder);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        $acl = $this->_mbox['permissions'];
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return $acl;
+    }
+
+    /**
+     * Retrieve the access rights on a folder not owned by the current user
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     *
+     * @return mixed An array of rights if successfull, a PEAR error
+     * otherwise.
+     */
+    function getMyRights($folder)
+    {
+        $folder = $this->_parseFolder($folder);
+        $old_mbox = null;
+        if ($folder != $this->_mboxname) {
+            $old_mbox = $this->_mboxname;
+            $result = $this->select($folder);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        $acl = '';
+        if (isset($this->_mbox['permissions'][$this->_user])) {
+            $acl = $this->_mbox['permissions'][$this->_user];
+        }
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return $acl;
+    }
+
+    /**
+     * Set the access rights for a folder
+     *
+     * @param string $folder  The folder to retrieve the ACLs from.
+     * @param string $user    The user to set the ACLs for
+     * @param string $acl     The ACLs
+     *
+     * @return mixed True if successfull, a PEAR error otherwise.
+     */
+    function setACL($folder, $user, $acl)
+    {
+        $folder = $this->_parseFolder($folder);
+        $old_mbox = null;
+        if ($folder != $this->_mboxname) {
+            $old_mbox = $this->_mboxname;
+            $result = $this->select($folder);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        $this->_mbox['permissions'][$user] = $acl;
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Delete the access rights for a user.
+     *
+     * @param string $folder  The folder that should be modified.
+     * @param string $user    The user that should get the ACLs removed
+     *
+     * @return mixed True if successfull, a PEAR error otherwise.
+     */
+    function deleteACL($folder, $user)
+    {
+        $folder = $this->_parseFolder($folder);
+        $old_mbox = null;
+        if ($folder != $this->_mboxname) {
+            $old_mbox = $this->_mboxname;
+            $result = $this->select($folder);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        unset($this->_mbox['permissions'][$user]);
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Appends a message to the current folder.
+     *
+     * @param string $msg  The message to append.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function appendMessage($msg)
+    {
+        $split = strpos($msg, "\r\n\r\n");
+        $mail = array('header' => substr($msg, 0, $split + 2),
+                      'body' => substr($msg, $split + 3));
+        return $this->_appendMessage($mail);
+    }
+
+    /**
+     * Appends a message to the current folder.
+     *
+     * @param array $msg  The message to append.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function _appendMessage($msg)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        $mail = array();
+        $mail['flags'] = 0;
+        $mail['header'] = $msg['header'];
+        $mail['body'] = $msg['body'];
+
+
+        $this->_mbox['mails'][$this->_mbox['status']['uidnext']] = $mail;
+        $this->_mbox['status']['uidnext']++;
+        return true;
+    }
+
+    /**
+     * Copies a message to a new folder.
+     *
+     * @param integer $uid        IMAP message id.
+     * @param string $new_folder  Target folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function copyMessage($uid, $new_folder)
+    {
+        $new_folder = $this->_parseFolder($new_folder);
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        if (!isset($this->_mbox['mails'][$uid])) {
+            return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+        }
+        $mail = $this->_mbox['mails'][$uid];
+
+        $old_mbox = null;
+        $result = $this->select($new_folder);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $this->_appendMessage($mail);
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Moves a message to a new folder.
+     *
+     * @param integer $uid        IMAP message id.
+     * @param string $new_folder  Target folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function moveMessage($uid, $new_folder)
+    {
+        $new_folder = $this->_parseFolder($new_folder);
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+        if (!isset($this->_mbox['mails'][$uid])) {
+            return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+        }
+        $mail = $this->_mbox['mails'][$uid];
+        unset($this->_mbox['mails'][$uid]);
+
+        $old_mbox = null;
+        $result = $this->select($new_folder);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $this->_appendMessage($mail);
+        if ($old_mbox) {
+            $result = $this->select($old_mbox);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Deletes messages from the current folder.
+     *
+     * @param integer $uids  IMAP message ids.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function deleteMessages($uids)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+
+        if (!is_array($uids)) {
+            $uids = array($uids);
+        }
+
+        foreach ($uids as $uid) {
+
+            if (!isset($this->_mbox['mails'][$uid])) {
+                return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+            }
+            $this->_mbox['mails'][$uid]['flags'] |= KOLAB_IMAP_FLAG_DELETED;
+        }
+        return true;
+    }
+
+    /**
+     * Undeletes a message in the current folder.
+     *
+     * @param integer $uid  IMAP message id.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function undeleteMessages($uid)
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+
+        if (!isset($this->_mbox['mails'][$uid])) {
+            return PEAR::raiseError(sprintf("No IMAP message %s!", $uid));
+        }
+        $this->_mbox['mails'][$uid]['flags'] &= ~KOLAB_IMAP_FLAG_DELETED;
+        return true;
+    }
+
+    /**
+     * Expunges messages in the current folder.
+     *
+     * @return mixed  True or a PEAR error in case of an error.
+     */
+    function expunge()
+    {
+        if (!$this->_mbox) {
+            return PEAR::raiseError("No IMAP folder selected!");
+        }
+
+        $remaining = array();
+        foreach ($this->_mbox['mails'] as $uid => $mail) {
+            if (!($mail['flags'] & KOLAB_IMAP_FLAG_DELETED)) {
+                $remaining[$uid] = $mail;
+            }
+        }
+        $this->_mbox['mails'] = $remaining;
+        return true;
+    }
+
+    /**
+     * Return the currently selected mailbox
+     *
+     * @return string  The mailbox currently selected
+     */
+    function current()
+    {
+        return $this->_mboxname;
+    }
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server.php b/framework/Kolab_Server/lib/Horde/Kolab/Server.php
new file mode 100644 (file)
index 0000000..d8089b7
--- /dev/null
@@ -0,0 +1,673 @@
+<?php
+/**
+ * A library for accessing the Kolab user database.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server.php,v 1.9 2009/01/08 07:12:43 wrobel Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/** We need PEAR */
+require_once 'PEAR.php';
+
+/** Provide access to the Kolab specific objects. */
+require_once 'Horde/Kolab/Server/Object.php';
+
+/** Define types of return values. */
+define('KOLAB_SERVER_RESULT_SINGLE', 1);
+define('KOLAB_SERVER_RESULT_STRICT', 2);
+define('KOLAB_SERVER_RESULT_MANY',   3);
+
+/**
+ * This class provides methods to deal with Kolab objects stored in
+ * the Kolab object db.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server.php,v 1.9 2009/01/08 07:12:43 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server {
+
+    /**
+     * Server parameters.
+     *
+     * @var array
+     */
+    var $_params = array();
+
+    /**
+     * The UID of the current user.
+     *
+     * @var string
+     */
+    var $uid;
+
+    /**
+     * Valid Kolab object types
+     *
+     * @var array
+     */
+    var $valid_types = array(
+        KOLAB_OBJECT_ADDRESS,
+        KOLAB_OBJECT_ADMINISTRATOR,
+        KOLAB_OBJECT_DISTLIST,
+        KOLAB_OBJECT_DOMAINMAINTAINER,
+        KOLAB_OBJECT_GROUP,
+        KOLAB_OBJECT_MAINTAINER,
+        KOLAB_OBJECT_SERVER,
+        KOLAB_OBJECT_SHAREDFOLDER,
+        KOLAB_OBJECT_USER,
+    );
+
+    /**
+     * Construct a new Horde_Kolab_Server object.
+     *
+     * @param array $params Parameter array.
+     */
+    function Horde_Kolab_Server($params = array())
+    {
+        $this->_params = $params;
+        if (isset($params['uid'])) {
+            $this->uid = $params['uid'];
+        }
+    }
+
+    /**
+     * Attempts to return a concrete Horde_Kolab_Server instance based
+     * on $driver.
+     *
+     * @param mixed $driver The type of concrete Horde_Kolab_Server subclass to
+     *                      return.
+     * @param array $params A hash containing any additional
+     *                      configuration or connection parameters a subclass
+     *                      might need.
+     *
+     * @return Horde_Kolab_Server|PEAR_Error The newly created concrete
+     *                                       Horde_Kolab_Server instance.
+     */
+    function &factory($driver, $params = array())
+    {
+        $driver = basename($driver);
+        if (empty($driver) || $driver == 'none') {
+            $db = new Horde_Kolab_Server($params);
+            return $db;
+        }
+
+        if (file_exists(dirname(__FILE__) . '/Server/' . $driver . '.php')) {
+            include_once dirname(__FILE__) . '/Server/' . $driver . '.php';
+        }
+
+        $class = 'Horde_Kolab_Server_' . $driver;
+        if (class_exists($class)) {
+            $db = new $class($params);
+        } else {
+            $db = PEAR::raiseError('Class definition of ' . $class . ' not found.');
+        }
+
+        return $db;
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Horde_Kolab_Server
+     * instance based on $driver. It will only create a new instance
+     * if no Horde_Kolab_Server instance with the same parameters currently
+     * exists.
+     *
+     * This method must be invoked as:
+     * $var = &Horde_Kolab_Server::singleton()
+     *
+     * @param array $params An array of optional login parameters. May
+     *                      contain "uid" (for the login uid), "user"
+     *                      (if the uid is not yet known), and "pass"
+     *                      (for a password).
+     *
+     * @return Horde_Kolab_Server|PEAR_Error The concrete Horde_Kolab_Server
+     *                                       reference.
+     */
+    function &singleton($params = null)
+    {
+        global $conf;
+
+        static $instances = array();
+
+        if (isset($conf['kolab']['server']['driver'])) {
+            $driver = $conf['kolab']['server']['driver'];
+            if (isset($conf['kolab']['server']['params'])) {
+                $server_params = $conf['kolab']['server']['params'];
+            } else {
+                $server_params = array();
+            }
+        } else if (isset($conf['kolab']['ldap']['server'])
+                   && isset($conf['kolab']['ldap']['basedn'])
+                   && isset($conf['kolab']['ldap']['phpdn'])
+                   && isset($conf['kolab']['ldap']['phppw'])) {
+            $driver = 'ldap';
+
+            $server_params = array('server'  => $conf['kolab']['ldap']['server'],
+                                  'base_dn' => $conf['kolab']['ldap']['basedn'],
+                                  'uid'     => $conf['kolab']['ldap']['phpdn'],
+                                  'pass'    => $conf['kolab']['ldap']['phppw']);
+        } else {
+            $driver        = null;
+            $server_params = array();
+        }
+
+        if (!empty($params)) {
+            if (isset($params['user'])) {
+                $tmp_server = &Horde_Kolab_Server::factory($driver, $server_params);
+
+                $uid = $tmp_server->uidForIdOrMail($params['user']);
+                if (is_a($uid, 'PEAR_Error')) {
+                    return PEAR::raiseError(sprintf(_("Failed identifying the UID of the Kolab user %s. Error was: %s"),
+                                                    $params['user'],
+                                                    $uid->getMessage()));
+                }
+                $server_params['uid'] = $uid;
+            }
+            if (isset($params['pass'])) {
+                if (isset($server_params['pass'])) {
+                    $server_params['search_pass'] = $server_params['pass'];
+                }
+                $server_params['pass'] = $params['pass'];
+            }
+            if (isset($params['uid'])) {
+                if (isset($server_params['uid'])) {
+                    $server_params['search_uid'] = $server_params['pass'];
+                }
+                $server_params['uid'] = $params['uid'];
+            }
+        }
+
+        $sparam         = $server_params;
+        $sparam['pass'] = isset($sparam['pass']) ? md5($sparam['pass']) : '';
+        $signature      = serialize(array($driver, $sparam));
+        if (empty($instances[$signature])) {
+            $instances[$signature] = &Horde_Kolab_Server::factory($driver,
+                                                                  $server_params);
+        }
+
+        return $instances[$signature];
+    }
+
+    /**
+     * Return the root of the UID values on this server.
+     *
+     * @return string The base UID on this server (base DN on ldap).
+     */
+    function getBaseUid()
+    {
+        return '';
+    }
+
+    /**
+     * Fetch a Kolab object.
+     *
+     * @param string $uid  The UID of the object to fetch.
+     * @param string $type The type of the object to fetch.
+     *
+     * @return Kolab_Object|PEAR_Error The corresponding Kolab object.
+     */
+    function &fetch($uid = null, $type = null)
+    {
+        if (!isset($uid)) {
+            $uid = $this->uid;
+        }
+        if (empty($type)) {
+            $type = $this->_determineType($uid);
+            if (is_a($type, 'PEAR_Error')) {
+                return $type;
+            }
+        } else {
+            if (!in_array($type, $this->valid_types)) {
+                return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."),
+                                                $type));
+            }
+        }
+
+        $object = &Horde_Kolab_Server_Object::factory($type, $uid, $this);
+        return $object;
+    }
+
+    /**
+     * Add a Kolab object.
+     *
+     * @param array $info The object to store.
+     *
+     * @return Kolab_Object|PEAR_Error The newly created Kolab object.
+     */
+    function &add($info)
+    {
+        if (!isset($info['type'])) {
+            return PEAR::raiseError('The type of a new object must be specified!');
+        }
+        if (!in_array($info['type'], $this->valid_types)) {
+            return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."),
+                                            $type));
+        }
+
+        $uid = $this->generateUid($info['type'], $info);
+        if (is_a($uid, 'PEAR_Error')) {
+            return $uid;
+        }
+
+        $object = &Horde_Kolab_Server_Object::factory($info['type'], $uid, $this);
+        if (is_a($object, 'PEAR_Error')) {
+            return $object;
+        }
+
+        if ($object->exists()) {
+            return PEAR::raiseError('The object does already exist!');
+        }
+
+        $result = $object->save($info);
+        if (is_a($result, 'PEAR_Error')) {
+            return PEAR::raiseError(sprintf('Adding object failed: %s',
+                                            $result->getMessage()));
+        }
+        return $object;
+    }
+
+    /**
+     * Update or create a Kolab object.
+     *
+     * @param string $type The type of the object to store.
+     * @param array  $info Any additional information about the object to store.
+     * @param string $uid  The unique id of the object to store.
+     *
+     * @return Kolab_Object|PEAR_Error The updated Kolab object.
+     */
+    function &store($type, $info, $uid = null)
+    {
+        if (!in_array($type, $this->valid_types)) {
+            return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."),
+                                            $type));
+        }
+        if (empty($uid)) {
+            $uid = $this->generateUid($type, $info);
+        }
+
+        $object = &Horde_Kolab_Server_Object::factory($type, $uid, $this);
+        $result = $object->save($info);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        return $object;
+    }
+
+    /**
+     * Get the groups for this object
+     *
+     * @param string $uid The UID of the object to fetch.
+     *
+     * @return array|PEAR_Error An array of group ids.
+     */
+    function getGroups($uid)
+    {
+        return array();
+    }
+
+    /**
+     * Read object data.
+     *
+     * @param string $uid   The object to retrieve.
+     * @param string $attrs Restrict to these attributes.
+     *
+     * @return array|PEAR_Error An array of attributes.
+     */
+    function read($uid, $attrs = null)
+    {
+        return $this->_read($uid, $attrs);
+    }
+
+    /**
+     * Stub for reading object data.
+     *
+     * @param string $uid   The object to retrieve.
+     * @param string $attrs Restrict to these attributes.
+     *
+     * @return array|PEAR_Error An array of attributes.
+     */
+    function _read($uid, $attrs = null)
+    {
+        return PEAR::raiseError(_("Not implemented!"));
+    }
+
+    /**
+     * Stub for saving object data.
+     *
+     * @param string $uid  The object to save.
+     * @param string $data The data of the object.
+     *
+     * @return array|PEAR_Error An array of attributes.
+     */
+    function save($uid, $data)
+    {
+        return PEAR::raiseError(_("Not implemented!"));
+    }
+
+    /**
+     * Determine the type of a Kolab object.
+     *
+     * @param string $uid The UID of the object to examine.
+     *
+     * @return string The corresponding Kolab object type.
+     */
+    function _determineType($uid)
+    {
+        return KOLAB_OBJECT_USER;
+    }
+
+    /**
+     * Identify the primary mail attribute for the first object found
+     * with the given ID or mail.
+     *
+     * @param string $id Search for objects with this ID/mail.
+     *
+     * @return mixed|PEAR_Error The mail address or false if there was
+     *                          no result.
+     */
+    function mailForIdOrMail($id)
+    {
+        /* In the default class we just return the id */
+        return $id;
+    }
+
+    /**
+     * Returns a list of allowed email addresses for the given user.
+     *
+     * @param string $user The user name.
+     *
+     * @return array|PEAR_Error An array of allowed mail addresses.
+     */
+    function addrsForIdOrMail($user)
+    {
+        /* In the default class we just return the user name */
+        return $user;
+    }
+
+    /**
+     * Return the UID for a given primary mail, uid, or alias.
+     *
+     * @param string $mail A valid mail address for the user.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForMailAddress($mail)
+    {
+        /* In the default class we just return the mail address */
+        return $mail;
+    }
+
+    /**
+     * Identify the UID for the first user found using a specified
+     * attribute value.
+     *
+     * @param string $attr     The name of the attribute used for searching.
+     * @param string $value    The desired value of the attribute.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForAttr($attr, $value,
+                       $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        /* In the default class we just return false */
+        return false;
+    }
+
+    /**
+     * Identify the GID for the first group found using a specified
+     * attribute value.
+     *
+     * @param string $attr     The name of the attribute used for searching.
+     * @param string $value    The desired value of the attribute.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The GID or false if there was no result.
+     */
+    function gidForAttr($attr, $value,
+                        $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        /* In the default class we just return false */
+        return false;
+    }
+
+    /**
+     * Is the given UID member of the group with the given mail address?
+     *
+     * @param string $uid  UID of the user.
+     * @param string $mail Search the group with this mail address.
+     *
+     * @return boolean|PEAR_Error True in case the user is in the
+     *                            group, false otherwise.
+     */
+    function memberOfGroupAddress($uid, $mail)
+    {
+        /* No groups in the default class */
+        return false;
+    }
+
+    /**
+     * Identify the UID for the first object found with the given ID.
+     *
+     * @param string $id       Search for objects with this ID.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForId($id,
+                      $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        return $this->uidForAttr('uid', $id);
+    }
+
+    /**
+     * Identify the UID for the first user found with the given mail.
+     *
+     * @param string $mail     Search for users with this mail address.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForMail($mail,
+                        $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        return $this->uidForAttr('mail', $mail);
+    }
+
+    /**
+     * Identify the GID for the first group found with the given mail.
+     *
+     * @param string $mail     Search for groups with this mail address.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The GID or false if there was no result.
+     */
+    function gidForMail($mail,
+                        $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        return $this->gidForAttr('mail', $mail);
+    }
+
+    /**
+     * Identify the UID for the first object found with the given ID or mail.
+     *
+     * @param string $id Search for objects with this uid/mail.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForIdOrMail($id)
+    {
+        $uid = $this->uidForAttr('uid', $id);
+        if (!$uid) {
+            $uid = $this->uidForAttr('mail', $id);
+        }
+        return $uid;
+    }
+
+    /**
+     * Identify the UID for the first object found with the given alias.
+     *
+     * @param string $mail     Search for objects with this mail alias.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForAlias($mail,
+                      $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        return $this->uidForAttr('alias', $mail);
+    }
+
+    /**
+     * Identify the UID for the first object found with the given mail
+     * address or alias.
+     *
+     * @param string $mail Search for objects with this mail address
+     * or alias.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForMailOrAlias($mail)
+    {
+        $uid = $this->uidForAttr('alias', $mail);
+        if (!$uid) {
+            $uid = $this->uidForAttr('mail', $mail);
+        }
+        return $uid;
+    }
+
+    /**
+     * Identify the UID for the first object found with the given ID,
+     * mail or alias.
+     *
+     * @param string $id Search for objects with this ID/mail/alias.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForMailOrIdOrAlias($id)
+    {
+        $uid = $this->uidForAttr('uid', $id);
+        if (!$uid) {
+            $uid = $this->uidForAttr('mail', $id);
+            if (!$uid) {
+                $uid = $this->uidForAttr('alias', $id);
+            }
+        }
+        return $uid;
+    }
+
+    /**
+     * Generate a hash representation for a list of objects.
+     *
+     * @param string $type   The type of the objects to be listed
+     * @param array  $params Additional parameters.
+     *
+     * @return array|PEAR_Error An array of Kolab objects.
+     */
+    function listHash($type, $params = null)
+    {
+        $list = $this->_listObjects($type, $params);
+        if (is_a($list, 'PEAR_Error')) {
+            return $list;
+        }
+
+        if (isset($params['attributes'])) {
+            $attributes = $params['attributes'];
+        } else {
+            $attributes = null;
+        }
+
+        $hash = array();
+        foreach ($list as $entry) {
+            $hash[] = $entry->toHash($attributes);
+        }
+
+        return $hash;
+    }
+
+    /**
+     * List all objects of a specific type
+     *
+     * @param string $type   The type of the objects to be listed
+     * @param array  $params Additional parameters.
+     *
+     * @return array|PEAR_Error An array of Kolab objects.
+     */
+    function listObjects($type, $params = null)
+    {
+        if (!in_array($type, $this->valid_types)) {
+            return PEAR::raiseError(sprintf(_("Invalid Kolab object type \"%s\"."),
+                                            $type));
+        }
+
+        return $this->_listObjects($type, $params);
+    }
+
+    /**
+     * List all objects of a specific type
+     *
+     * @param string $type   The type of the objects to be listed
+     * @param array  $params Additional parameters.
+     *
+     * @return array|PEAR_Error An array of Kolab objects.
+     */
+    function _listObjects($type, $params = null)
+    {
+        return array();
+    }
+
+    /**
+     * Generates a unique ID for the given information.
+     *
+     * @param string $type The type of the object to create.
+     * @param array  $info Any additional information about the object to create.
+     *
+     * @return string|PEAR_Error The UID.
+     */
+    function generateUid($type, $info)
+    {
+        if (!class_exists($type)) {
+            $result = Horde_Kolab_Server_Object::loadClass($type);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $id = call_user_func(array($type, 'generateId'), $info);
+        if (is_a($id, 'PEAR_Error')) {
+            return $id;
+        }
+        return $this->_generateUid($type, $id, $info);
+    }
+
+    /**
+     * Generates a UID for the given information.
+     *
+     * @param string $type The type of the object to create.
+     * @param string $id   The id of the object.
+     * @param array  $info Any additional information about the object to create.
+     *
+     * @return string|PEAR_Error The UID.
+     */
+    function _generateUid($type, $id, $info)
+    {
+        return $id;
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php
new file mode 100644 (file)
index 0000000..ceb1300
--- /dev/null
@@ -0,0 +1,505 @@
+<?php
+/**
+ * The base class representing Kolab objects stored in the server
+ * database.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php,v 1.9 2009/01/08 21:00:07 wrobel Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/** Define the different Kolab object types */
+define('KOLAB_OBJECT_ADDRESS',          'Horde_Kolab_Server_Object_address');
+define('KOLAB_OBJECT_ADMINISTRATOR',    'Horde_Kolab_Server_Object_administrator');
+define('KOLAB_OBJECT_DOMAINMAINTAINER', 'Horde_Kolab_Server_Object_domainmaintainer');
+define('KOLAB_OBJECT_GROUP',            'Horde_Kolab_Server_Object_group');
+define('KOLAB_OBJECT_DISTLIST',         'Horde_Kolab_Server_Object_distlist');
+define('KOLAB_OBJECT_MAINTAINER',       'Horde_Kolab_Server_Object_maintainer');
+define('KOLAB_OBJECT_SHAREDFOLDER',     'Horde_Kolab_Server_Object_sharedfolder');
+define('KOLAB_OBJECT_USER',             'Horde_Kolab_Server_Object_user');
+define('KOLAB_OBJECT_SERVER',           'Horde_Kolab_Server_Object_server');
+
+/** Define the possible Kolab object attributes */
+define('KOLAB_ATTR_UID',          'dn');
+define('KOLAB_ATTR_ID',           'id');
+define('KOLAB_ATTR_SN',           'sn');
+define('KOLAB_ATTR_CN',           'cn');
+define('KOLAB_ATTR_GIVENNAME',    'givenName');
+define('KOLAB_ATTR_FN',           'fn');
+define('KOLAB_ATTR_LNFN',         'lnfn');
+define('KOLAB_ATTR_FNLN',         'fnln');
+define('KOLAB_ATTR_MAIL',         'mail');
+define('KOLAB_ATTR_SID',          'uid');
+define('KOLAB_ATTR_ACL',          'acl');
+define('KOLAB_ATTR_MEMBER',       'member');
+define('KOLAB_ATTR_USERTYPE',     'usertype');
+define('KOLAB_ATTR_DOMAIN',       'domain');
+define('KOLAB_ATTR_FOLDERTYPE',   'kolabFolderType');
+define('KOLAB_ATTR_USERPASSWORD', 'userPassword');
+define('KOLAB_ATTR_DELETED',      'kolabDeleteFlag');
+define('KOLAB_ATTR_FREEBUSYHOST', 'kolabFreeBusyServer');
+define('KOLAB_ATTR_IMAPHOST',     'kolabImapServer');
+define('KOLAB_ATTR_HOMESERVER',   'kolabHomeServer');
+define('KOLAB_ATTR_KOLABDELEGATE','kolabDelegate');
+define('KOLAB_ATTR_IPOLICY',      'kolabInvitationPolicy');
+define('KOLAB_ATTR_QUOTA',        'cyrus-userquota');
+define('KOLAB_ATTR_FBPAST',       'kolabFreeBusyPast');
+define('KOLAB_ATTR_FBFUTURE',     'kolabFreeBusyFuture');
+define('KOLAB_ATTR_VISIBILITY',   'visible');
+
+/** Define the possible Kolab object classes */
+define('KOLAB_OC_TOP',                'top');
+define('KOLAB_OC_INETORGPERSON',      'inetOrgPerson');
+define('KOLAB_OC_KOLABINETORGPERSON', 'kolabInetOrgPerson');
+define('KOLAB_OC_HORDEPERSON',        'hordePerson');
+define('KOLAB_OC_KOLABGROUPOFNAMES',  'kolabGroupOfNames');
+define('KOLAB_OC_KOLABSHAREDFOLDER',  'kolabSharedFolder');
+
+/** Define the possible Kolab user types */
+define('KOLAB_UT_STANDARD',           0);
+define('KOLAB_UT_INTERNAL',           1);
+define('KOLAB_UT_GROUP',              2);
+define('KOLAB_UT_RESOURCE',           3);
+
+/**
+ * This class provides methods to deal with Kolab objects stored in
+ * the Kolab db.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object.php,v 1.9 2009/01/08 21:00:07 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object {
+
+    /**
+     * Link into the Kolab server.
+     *
+     * @var Kolab_Server
+     */
+    var $_db;
+
+    /**
+     * UID of this object on the Kolab server.
+     *
+     * @var string
+     */
+    var $_uid;
+
+    /**
+     * The cached LDAP result
+     *
+     * FIXME: Include _ldap here
+     *
+     * @var mixed
+     */
+    var $_cache = false;
+
+    /** FIXME: Add an attribute cache for the get() function */
+
+    /**
+     * The LDAP filter to retrieve this object type.
+     *
+     * @var string
+     */
+    var $filter = '';
+
+    /**
+     * The group the UID must be member of so that this object really
+     * matches this class type. This may not include the root UID.
+     *
+     * @var string
+     */
+    var $required_group;
+
+    /**
+     * The LDAP attributes supported by this class.
+     *
+     * @var array
+     */
+    var $_supported_attributes = array();
+
+    /**
+     * Attributes derived from the LDAP values.
+     *
+     * @var array
+     */
+    var $_derived_attributes = array(
+        KOLAB_ATTR_ID,
+    );
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array();
+
+    /**
+     * The ldap classes for this type of object.
+     *
+     * @var array
+     */
+    var $_object_classes = array();
+
+    /**
+     * Sort by this attributes (must be a LDAP attribute).
+     *
+     * @var string
+     */
+    var $sort_by = KOLAB_ATTR_SN;
+
+    /**
+     * Initialize the Kolab Object. Provide either the UID or a
+     * LDAP search result.
+     *
+     * @param Horde_Kolab_Server &$db  The link into the Kolab db.
+     * @param string             $uid  UID of the object.
+     * @param array              $data A possible array of data for the object
+     */
+    function Horde_Kolab_Server_Object(&$db, $uid = null, $data = null)
+    {
+        $this->_db = &$db;
+        if (empty($uid)) {
+            if (empty($data) || !isset($data[KOLAB_ATTR_UID])) {
+                $this->_cache = PEAR::raiseError(_('Specify either the UID or a search result!'));
+                return;
+            }
+            if (is_array($data[KOLAB_ATTR_UID])) {
+                $this->_uid = $data[KOLAB_ATTR_UID][0];
+            } else {
+                $this->_uid = $data[KOLAB_ATTR_UID];
+            }
+            $this->_cache = $data;
+        } else {
+            $this->_uid = $uid;
+        }
+    }
+
+    /**
+     * Attempts to return a concrete Horde_Kolab_Server_Object instance based on
+     * $type.
+     *
+     * @param mixed  $type     The type of the Horde_Kolab_Server_Object subclass
+     *                         to return.
+     * @param string $uid      UID of the object
+     * @param array  &$storage A link to the Kolab_Server class handling read/write.
+     * @param array  $data     A possible array of data for the object
+     *
+     * @return Horde_Kolab_Server_Object|PEAR_Error The newly created concrete
+     *                                 Horde_Kolab_Server_Object instance.
+     */
+    function &factory($type, $uid, &$storage, $data = null)
+    {
+        $result = Horde_Kolab_Server_Object::loadClass($type);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        if (class_exists($type)) {
+            $object = &new $type($storage, $uid, $data);
+        } else {
+            $object = PEAR::raiseError('Class definition of ' . $type . ' not found.');
+        }
+
+        return $object;
+    }
+
+    /**
+     * Attempts to load the concrete Horde_Kolab_Server_Object class based on
+     * $type.
+     *
+     * @param mixed $type The type of the Horde_Kolab_Server_Object subclass.
+     *
+     * @static
+     *
+     * @return true|PEAR_Error True if successfull.
+     */
+    function loadClass($type)
+    {
+        if (in_array($type, array(KOLAB_OBJECT_ADDRESS, KOLAB_OBJECT_ADMINISTRATOR,
+                                  KOLAB_OBJECT_DISTLIST, KOLAB_OBJECT_DOMAINMAINTAINER,
+                                  KOLAB_OBJECT_GROUP, KOLAB_OBJECT_MAINTAINER,
+                                  KOLAB_OBJECT_SHAREDFOLDER, KOLAB_OBJECT_USER,
+                                  KOLAB_OBJECT_SERVER))) {
+            $name = substr($type, 26);
+        } else {
+            return PEAR::raiseError(sprintf('Object type "%s" not supported.',
+                                            $type));
+        }
+
+        $name = basename($name);
+
+        if (file_exists(dirname(__FILE__) . '/Object/' . $name . '.php')) {
+            include_once dirname(__FILE__) . '/Object/' . $name . '.php';
+        }
+    }
+
+    /**
+     * Does the object exist?
+     *
+     * @return NULL
+     */
+    function exists()
+    {
+        $this->_read();
+        if (!$this->_cache || is_a($this->_cache, 'PEAR_Error')) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Read the object into the cache
+     *
+     * @return NULL
+     */
+    function _read()
+    {
+        $this->_cache = $this->_db->read($this->_uid,
+                                         $this->_supported_attributes);
+    }
+
+    /**
+     * Get the specified attribute of this object
+     *
+     * @param string $attr The attribute to read
+     *
+     * @return string the value of this attribute
+     */
+    function get($attr)
+    {
+        if ($attr != KOLAB_ATTR_UID) {
+            if (!in_array($attr, $this->_supported_attributes)
+                && !in_array($attr, $this->_derived_attributes)) {
+                return PEAR::raiseError(sprintf(_("Attribute \"%s\" not supported!"),
+                                                $attr));
+            }
+            if (!$this->_cache) {
+                $this->_read();
+            }
+            if (is_a($this->_cache, 'PEAR_Error')) {
+                return $this->_cache;
+            }
+        }
+
+        if (in_array($attr, $this->_derived_attributes)) {
+            return $this->_derive($attr);
+        }
+
+        switch ($attr) {
+        case KOLAB_ATTR_UID:
+            return $this->_getUID();
+        case KOLAB_ATTR_FN:
+            return $this->_getFn();
+        case KOLAB_ATTR_SN:
+        case KOLAB_ATTR_CN:
+        case KOLAB_ATTR_GIVENNAME:
+        case KOLAB_ATTR_MAIL:
+        case KOLAB_ATTR_SID:
+        case KOLAB_ATTR_USERPASSWORD:
+        case KOLAB_ATTR_DELETED:
+        case KOLAB_ATTR_IMAPHOST:
+        case KOLAB_ATTR_FREEBUSYHOST:
+        case KOLAB_ATTR_HOMESERVER:
+        case KOLAB_ATTR_FBPAST:
+        case KOLAB_ATTR_FBFUTURE:
+        case KOLAB_ATTR_FOLDERTYPE:
+            return $this->_get($attr, true);
+        default:
+            return $this->_get($attr, false);
+        }
+    }
+
+    /**
+     * Get the specified attribute of this object
+     *
+     * @param string  $attr   The attribute to read
+     * @param boolean $single Should a single value be returned
+     *                        or are multiple values allowed?
+     *
+     * @return string the value of this attribute
+     */
+    function _get($attr, $single = true)
+    {
+        if (isset($this->_cache[$attr])) {
+            if ($single && is_array($this->_cache[$attr])) {
+                return $this->_cache[$attr][0];
+            } else {
+                return $this->_cache[$attr];
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Derive an attribute value.
+     *
+     * @param string $attr The attribute to derive.
+     *
+     * @return mixed The value of the attribute.
+     */
+    function _derive($attr)
+    {
+        switch ($attr) {
+        case KOLAB_ATTR_ID:
+            $result = split(',', $this->_uid);
+            if (substr($result[0], 0, 3) == 'cn=') {
+                return substr($result[0], 3);
+            } else {
+                return $result[0];
+            }
+        case KOLAB_ATTR_LNFN:
+            $gn = $this->_get(KOLAB_ATTR_GIVENNAME, true);
+            $sn = $this->_get(KOLAB_ATTR_SN, true);
+            return sprintf('%s, %s', $sn, $gn);
+        case KOLAB_ATTR_FNLN:
+            $gn = $this->_get(KOLAB_ATTR_GIVENNAME, true);
+            $sn = $this->_get(KOLAB_ATTR_SN, true);
+            return sprintf('%s %s', $gn, $sn);
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array();
+        }
+        $result = array();
+        foreach ($attrs as $key) {
+            $value = $this->get($key);
+            if (is_a($value, 'PEAR_Error')) {
+                return $value;
+            }
+            $result[$key] = $value;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Get the UID of this object
+     *
+     * @return string the UID of this object
+     */
+    function _getUid()
+    {
+        return $this->_uid;
+    }
+
+    /**
+     * Get the "first name" attribute of this object
+     *
+     * FIXME: This should get refactored to be combined with the Id value.
+     *
+     * @return string the "first name" of this object
+     */
+    function _getFn()
+    {
+        $sn = $this->_get(KOLAB_ATTR_SN, true);
+        $cn = $this->_get(KOLAB_ATTR_CN, true);
+        return trim(substr($cn, 0, strlen($cn) - strlen($sn)));
+    }
+
+    /**
+     * Get the groups for this object
+     *
+     * @return mixed An array of group ids or a PEAR Error in case of
+     *               an error.
+     */
+    function getGroups()
+    {
+        return array();
+    }
+
+    /**
+     * Returns the server url of the given type for this user.
+     *
+     * This method can be used to encapsulate multidomain support.
+     *
+     * @param string $server_type The type of server URL that should be returned.
+     *
+     * @return string|PEAR_Error The server url or empty.
+     */
+    function getServer($server_type)
+    {
+        return PEAR::raiseError('Not implemented!');
+    }
+
+    /**
+     * Generates an ID for the given information.
+     *
+     * @param array $info The data of the object.
+     *
+     * @static
+     *
+     * @return string|PEAR_Error The ID.
+     */
+    function generateId($info)
+    {
+        $id_mapfields = array('givenName', 'sn');
+        $id_format    = '%s %s';
+
+        $fieldarray = array();
+        foreach ($id_mapfields as $mapfield) {
+            if (isset($info[$mapfield])) {
+                $fieldarray[] = $info[$mapfield];
+            } else {
+                $fieldarray[] = '';
+            }
+        }
+
+        return trim(vsprintf($id_format, $fieldarray), " \t\n\r\0\x0B,");
+    }
+
+    /**
+     * Saves object information.
+     *
+     * @param array $info The information about the object.
+     *
+     * @return boolean|PEAR_Error True on success.
+     */
+    function save($info)
+    {
+        foreach ($this->_required_attributes as $attribute) {
+            if (!isset($info[$attribute])) {
+                return PEAR::raiseError(sprintf('The value for "%s" is missing!',
+                                                $attribute));
+            }
+        }
+
+        $info['objectClass'] = $this->_object_classes;
+
+        $result = $this->_db->save($this->_uid, $info);
+        if ($result === false || is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $this->_cache = $info;
+
+        return $result;
+    }
+};
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php
new file mode 100644 (file)
index 0000000..ca81da1
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * An entry in the global addressbook.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php,v 1.6 2009/01/08 21:00:08 wrobel Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * This class provides methods to deal with global address book
+ * entries for Kolab.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/address.php,v 1.6 2009/01/08 21:00:08 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_address extends Horde_Kolab_Server_Object {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(&(objectclass=inetOrgPerson)(!(uid=*))(sn=*))';
+
+    /**
+     * The attributes supported by this class
+     *
+     * @var array
+     */
+    var $_supported_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_CN,
+        KOLAB_ATTR_GIVENNAME,
+        KOLAB_ATTR_FN,
+        KOLAB_ATTR_LNFN,
+        KOLAB_ATTR_MAIL,
+        KOLAB_ATTR_DELETED,
+    );
+
+    /**
+     * Attributes derived from the LDAP values.
+     *
+     * @var array
+     */
+    var $_derived_attributes = array(
+        KOLAB_ATTR_LNFN,
+        KOLAB_ATTR_FNLN,
+    );
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_GIVENNAME,
+    );
+
+    /**
+     * The ldap classes for this type of object.
+     *
+     * @var array
+     */
+    var $_object_classes = array(
+        KOLAB_OC_TOP,
+        KOLAB_OC_INETORGPERSON,
+        KOLAB_OC_KOLABINETORGPERSON,
+    );
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array(
+                KOLAB_ATTR_LNFN,
+            );
+        }
+        return parent::toHash($attrs);
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php
new file mode 100644 (file)
index 0000000..33b0750
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * A system administrator.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+require_once 'Horde/Kolab/Server/Object/adminrole.php';
+
+/**
+ * This class provides methods to deal with administrator
+ * entries for Kolab.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/administrator.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_administrator extends Horde_Kolab_Server_Object_adminrole {
+
+    /**
+     * The group the UID must be member of so that this object really
+     * matches this class type. This may not include the root UID.
+     *
+     * @var string
+     */
+    var $required_group = 'cn=admin,cn=internal';
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php
new file mode 100644 (file)
index 0000000..d87aa72
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+/**
+ * A Kolab object of type administrator.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php,v 1.3 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * This class provides methods to deal with administrator object types.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/adminrole.php,v 1.3 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_adminrole extends Horde_Kolab_Server_Object {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(&(cn=*)(objectClass=inetOrgPerson)(!(uid=manager))(sn=*))';
+
+    /**
+     * The attributes supported by this class
+     *
+     * @var array
+     */
+    var $_supported_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_CN,
+        KOLAB_ATTR_GIVENNAME,
+        KOLAB_ATTR_FN,
+        KOLAB_ATTR_SID,
+        KOLAB_ATTR_USERPASSWORD,
+        KOLAB_ATTR_DELETED,
+    );
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_GIVENNAME,
+        KOLAB_ATTR_USERPASSWORD,
+        KOLAB_ATTR_SID,
+    );
+
+    /**
+     * Attributes derived from the LDAP values.
+     *
+     * @var array
+     */
+    var $_derived_attributes = array(
+        KOLAB_ATTR_ID,
+        KOLAB_ATTR_LNFN,
+    );
+
+    /**
+     * The ldap classes for this type of object.
+     *
+     * @var array
+     */
+    var $_object_classes = array(
+        KOLAB_OC_TOP,
+        KOLAB_OC_INETORGPERSON,
+        KOLAB_OC_KOLABINETORGPERSON,
+    );
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array(
+                KOLAB_ATTR_SID,
+                KOLAB_ATTR_LNFN,
+            );
+        }
+        return parent::toHash($attrs);
+    }
+
+    /**
+     * Saves object information.
+     *
+     * @param array $info The information about the object.
+     *
+     * @return boolean|PEAR_Error True on success.
+     */
+    function save($info)
+    {
+        if (!isset($info['cn'])) {
+            if (!isset($info['sn']) || !isset($info['givenName'])) {
+                return PEAR::raiseError('Either the last name or the given name is missing!');
+            } else {
+                $info['cn'] = $this->generateId($info);
+            }
+        }
+
+        $admins_uid = sprintf('%s,%s', $this->required_group,
+                              $this->_db->getBaseUid());
+
+        $admin_group = $this->_db->fetch($admins_uid, KOLAB_OBJECT_GROUP);
+        if (is_a($admin_group, 'PEAR_Error') || !$admin_group->exists()) {
+
+            $members = array($this->_uid);
+
+            //FIXME: This is not okay and also contains too much LDAP knowledge
+            $parts = split(',', $this->required_group);
+            list($groupname) = sscanf($parts[0], 'cn=%s');
+
+            $result = $this->_db->add(array(KOLAB_ATTR_CN => $groupname,
+                                            'type' => KOLAB_OBJECT_GROUP,
+                                            KOLAB_ATTR_MEMBER => $members,
+                                            KOLAB_ATTR_VISIBILITY => false));
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        } else {
+            $result = $admin_group->isMember($this->_uid);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            if ($result === false) {
+                $members   = $admin_group->getMembers();
+                $members[] = $this->_uid;
+                $admin_group->save(array(KOLAB_ATTR_MEMBER => $members));
+            }
+        }
+        return parent::save($info);
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php
new file mode 100644 (file)
index 0000000..d41164b
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Representation of a Kolab distribution list.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php,v 1.2 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+require_once 'Horde/Kolab/Server/Object/group.php';
+
+/**
+ * This class provides methods to deal with distribution lists for Kolab.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/distlist.php,v 1.2 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_distlist extends Horde_Kolab_Server_Object_group {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(&(objectClass=kolabGroupOfNames)(mail=*))';
+
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_MAIL,
+    );
+};
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php
new file mode 100644 (file)
index 0000000..c4517b4
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+/**
+ * A Kolab domain maintainer.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php,v 1.6 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+require_once 'Horde/Kolab/Server/Object/adminrole.php';
+
+/**
+ * This class provides methods associated to Kolab domain maintainers.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/domainmaintainer.php,v 1.6 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_domainmaintainer extends Horde_Kolab_Server_Object_adminrole {
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_GIVENNAME,
+        KOLAB_ATTR_USERPASSWORD,
+        KOLAB_ATTR_SID,
+        KOLAB_ATTR_DOMAIN,
+    );
+
+    /**
+     * Attributes derived from the LDAP values.
+     *
+     * @var array
+     */
+    var $_derived_attributes = array(
+        KOLAB_ATTR_ID,
+        KOLAB_ATTR_LNFN,
+        KOLAB_ATTR_DOMAIN,
+    );
+
+    /**
+     * The group the UID must be member of so that this object really
+     * matches this class type. This may not include the root UID.
+     *
+     * @var string
+     */
+    var $required_group = 'cn=domain-maintainer,cn=internal';
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array(
+                KOLAB_ATTR_SID,
+                KOLAB_ATTR_LNFN,
+                KOLAB_ATTR_DOMAIN,
+            );
+        }
+        return parent::toHash($attrs);
+    }
+
+    /**
+     * Saves object information.
+     *
+     * @param array $info The information about the object.
+     *
+     * @return boolean|PEAR_Error True on success.
+     */
+    function save($info)
+    {
+        foreach ($info[KOLAB_ATTR_DOMAIN] as $domain) {
+            $domain_uid = sprintf('cn=%s,cn=domain,cn=internal,%s',
+                                  $domain, $this->_db->getBaseUid());
+
+            //FIXME: This should be made easier by the group object
+
+            $domain_group = $this->_db->fetch($domain_uid, KOLAB_OBJECT_GROUP);
+            if (is_a($domain_group, 'PEAR_Error')) {
+                return $domain_group;
+            }
+            if (!$domain_group->exists()) {
+                $members = array($this->_uid);
+                $domain_group->save(array(KOLAB_ATTR_MEMBER => $members));
+            } else {
+                $result = $domain_group->isMember($this->_uid);
+                if (is_a($result, 'PEAR_Error')) {
+                    return $result;
+                }
+                if ($result === false) {
+                    $members   = $domain_group->getMembers();
+                    $members[] = $this->_uid;
+                    $domain_group->save(array(KOLAB_ATTR_MEMBER => $members));
+                }
+            }
+        }
+        return parent::save($info);
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php
new file mode 100644 (file)
index 0000000..76e4736
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Representation of a Kolab user group.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * This class provides methods to deal with groups for Kolab.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/group.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_group extends Horde_Kolab_Server_Object {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(objectClass=kolabGroupOfNames)';
+
+    /**
+     * The attributes supported by this class
+     *
+     * @var array
+     */
+    var $_supported_attributes = array(
+        KOLAB_ATTR_CN,
+        KOLAB_ATTR_MAIL,
+        KOLAB_ATTR_MEMBER,
+        KOLAB_ATTR_DELETED,
+    );
+
+    /**
+     * Attributes derived from the LDAP values.
+     *
+     * @var array
+     */
+    var $_derived_attributes = array(
+        KOLAB_ATTR_ID,
+        KOLAB_ATTR_VISIBILITY,
+    );
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_CN,
+    );
+
+    /**
+     * The ldap classes for this type of object.
+     *
+     * @var array
+     */
+    var $_object_classes = array(
+        KOLAB_OC_TOP,
+        KOLAB_OC_KOLABGROUPOFNAMES,
+    );
+
+    /**
+     * Sort by this attributes (must be a LDAP attribute).
+     *
+     * @var string
+     */
+    var $sort_by = KOLAB_ATTR_MAIL;
+
+    /**
+     * Derive an attribute value.
+     *
+     * @param string $attr The attribute to derive.
+     *
+     * @return mixed The value of the attribute.
+     */
+    function _derive($attr)
+    {
+        switch ($attr) {
+        case KOLAB_ATTR_VISIBILITY:
+            return strpos($this->_uid, 'cn=internal') === false;
+        default:
+            return parent::_derive($attr);
+        }
+    }
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array(
+                KOLAB_ATTR_ID,
+                KOLAB_ATTR_MAIL,
+                KOLAB_ATTR_VISIBILITY,
+            );
+        }
+        return parent::toHash($attrs);
+    }
+
+    /**
+     * Generates an ID for the given information.
+     *
+     * @param array $info The data of the object.
+     *
+     * @static
+     *
+     * @return string|PEAR_Error The ID.
+     */
+    function generateId($info)
+    {
+        if (isset($info['mail'])) {
+            return trim($info['mail'], " \t\n\r\0\x0B,");
+        } else {
+            return trim($info['cn'], " \t\n\r\0\x0B,");
+        }
+    }
+
+    /**
+     * Saves object information.
+     *
+     * @param array $info The information about the object.
+     *
+     * @return boolean|PEAR_Error True on success.
+     */
+    function save($info)
+    {
+        if (!isset($info['cn'])) {
+            if (!isset($info['mail'])) {
+                return PEAR::raiseError('Either the mail address or the common name has to be specified for a group object!');
+            } else {
+                $info['cn'] = $info['mail'];
+            }
+        }
+        return parent::save($info);
+    }
+
+    /**
+     * Retrieve the member list for this group.
+     *
+     * @return array|PEAR_Error The list of members in this group.
+     */
+    function getMembers()
+    {
+        return $this->_get(KOLAB_ATTR_MEMBER, false);
+    }
+
+    /**
+     * Add a member to this group.
+     *
+     * @param string $member The UID of the member to add.
+     *
+     * @return array|PEAR_Error True if successful.
+     */
+    function addMember($member)
+    {
+        $members = $this->getMembers();
+        if (is_a($members, 'PEAR_Error')) {
+            return $members;
+        }
+        if (!in_array($member, $members)) {
+            $this->_cache[KOLAB_ATTR_MEMBER][] = $member;
+        } else {
+            return PEAR::raiseError(_("The UID %s is already a member of the group %s!"),
+                                    $member, $this->_uid);
+        }
+        return $this->save($this->_cache);
+    }
+
+    /**
+     * Delete a member from this group.
+     *
+     * @param string $member The UID of the member to delete.
+     *
+     * @return array|PEAR_Error True if successful.
+     */
+    function deleteMember($member)
+    {
+        $members = $this->getMembers();
+        if (is_a($members, 'PEAR_Error')) {
+            return $members;
+        }
+        if (in_array($member, $members)) {
+            $this->_cache[KOLAB_ATTR_MEMBER] = array_diff($this->_cache[KOLAB_ATTR_MEMBER],
+                                                          array($member));
+        } else {
+            return PEAR::raiseError(_("The UID %s is no member of the group %s!"),
+                                    $member, $this->_uid);
+
+        }
+        return $this->save($this->_cache);
+    }
+
+    /**
+     * Is the specified UID member of this group?
+     *
+     * @param string $member The UID of the member to check.
+     *
+     * @return boolean|PEAR_Error True if the UID is a member of the group,
+     *                            false otherwise.
+     */
+    function isMember($member)
+    {
+        $members = $this->getMembers();
+        if (is_a($members, 'PEAR_Error') || !is_array($members)) {
+            return $members;
+        }
+        if (!in_array($member, $members)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php
new file mode 100644 (file)
index 0000000..342bd7b
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * A Kolab maintainer.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+require_once 'Horde/Kolab/Server/Object/adminrole.php';
+
+/**
+ * This class provides methods to deal with maintainer
+ * entries for Kolab.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/maintainer.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_maintainer extends Horde_Kolab_Server_Object_adminrole {
+
+    /**
+     * The group the UID must be member of so that this object really
+     * matches this class type. This may not include the root UID.
+     *
+     * @var string
+     */
+    var $required_group = 'cn=maintainer,cn=internal';
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php
new file mode 100644 (file)
index 0000000..c10dccc
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/**
+ * The server configuration.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * This class provides methods to deal with Kolab server configuration.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/server.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_server extends Horde_Kolab_Server_Object {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(&((k=kolab))(objectclass=kolab))';
+
+    /**
+     * The attributes supported by this class
+     *
+     * @var array
+     */
+    var $_supported_attributes = array(
+        KOLAB_ATTR_FBPAST,
+    );
+
+}
\ No newline at end of file
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php
new file mode 100644 (file)
index 0000000..05800bc
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * A shared IMAP folder.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * This class provides methods to deal with shared folders
+ * entries for Kolab.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/sharedfolder.php,v 1.5 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_sharedfolder extends Horde_Kolab_Server_Object {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(objectClass=kolabSharedFolder)';
+
+    /**
+     * The attributes supported by this class
+     *
+     * @var array
+     */
+    var $_supported_attributes = array(
+        KOLAB_ATTR_CN,
+        KOLAB_ATTR_DELETED,
+        KOLAB_ATTR_FOLDERTYPE,
+        KOLAB_ATTR_HOMESERVER,
+        KOLAB_ATTR_IMAPHOST,
+        KOLAB_ATTR_QUOTA,
+        KOLAB_ATTR_ACL,
+    );
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_CN,
+        KOLAB_ATTR_HOMESERVER,
+    );
+
+    /**
+     * The ldap classes for this type of object.
+     *
+     * @var array
+     */
+    var $_object_classes = array(
+        KOLAB_OC_TOP,
+        KOLAB_OC_KOLABSHAREDFOLDER,
+    );
+
+    /**
+     * Generates an ID for the given information.
+     *
+     * @param array $info The data of the object.
+     *
+     * @static
+     *
+     * @return string|PEAR_Error The ID.
+     */
+    function generateId($info)
+    {
+        return trim($info['cn'], " \t\n\r\0\x0B,");
+    }
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array(
+                KOLAB_ATTR_CN,
+                KOLAB_ATTR_HOMESERVER,
+                KOLAB_ATTR_FOLDERTYPE,
+            );
+        }
+        return parent::toHash($attrs);
+    }
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php
new file mode 100644 (file)
index 0000000..8b47e90
--- /dev/null
@@ -0,0 +1,312 @@
+<?php
+/**
+ * A standard Kolab user.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php,v 1.11 2009/01/08 21:00:08 wrobel Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * This class provides methods to deal with Kolab users stored in
+ * the Kolab db.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/Object/user.php,v 1.11 2009/01/08 21:00:08 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_Object_user extends Horde_Kolab_Server_Object {
+
+    /**
+     * The LDAP filter to retrieve this object type
+     *
+     * @var string
+     */
+    var $filter = '(&(objectClass=kolabInetOrgPerson)(uid=*)(mail=*)(sn=*))';
+
+    /**
+     * The attributes supported by this class
+     *
+     * @var array
+     */
+    var $_supported_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_CN,
+        KOLAB_ATTR_GIVENNAME,
+        KOLAB_ATTR_FN,
+        KOLAB_ATTR_SID,
+        KOLAB_ATTR_USERPASSWORD,
+        KOLAB_ATTR_MAIL,
+        KOLAB_ATTR_DELETED,
+        KOLAB_ATTR_IMAPHOST,
+        KOLAB_ATTR_FREEBUSYHOST,
+        KOLAB_ATTR_HOMESERVER,
+        KOLAB_ATTR_KOLABDELEGATE,
+        KOLAB_ATTR_IPOLICY,
+        KOLAB_ATTR_FBFUTURE,
+    );
+
+    /**
+     * Attributes derived from the LDAP values.
+     *
+     * @var array
+     */
+    var $_derived_attributes = array(
+        KOLAB_ATTR_ID,
+        KOLAB_ATTR_USERTYPE,
+        KOLAB_ATTR_LNFN,
+        KOLAB_ATTR_FNLN,
+    );
+
+    /**
+     * The attributes required when creating an object of this class.
+     *
+     * @var array
+     */
+    var $_required_attributes = array(
+        KOLAB_ATTR_SN,
+        KOLAB_ATTR_GIVENNAME,
+        KOLAB_ATTR_USERPASSWORD,
+        KOLAB_ATTR_MAIL,
+        KOLAB_ATTR_HOMESERVER,
+    );
+
+    /**
+     * The ldap classes for this type of object.
+     *
+     * @var array
+     */
+    var $_object_classes = array(
+        KOLAB_OC_TOP,
+        KOLAB_OC_INETORGPERSON,
+        KOLAB_OC_KOLABINETORGPERSON,
+        KOLAB_OC_HORDEPERSON,
+    );
+
+    /**
+     * Initialize the Kolab Object. Provide either the UID or a
+     * LDAP search result.
+     *
+     * @param Horde_Kolab_Server &$db  The link into the Kolab db.
+     * @param string             $dn   UID of the object.
+     * @param array              $data A possible array of data for the object
+     */
+    function Horde_Kolab_Server_Object_user(&$db, $dn = null, $data = null)
+    {
+        global $conf;
+
+        /** Allows to customize the supported user attributes. */
+        if (isset($conf['kolab']['server']['user_supported_attrs'])) {
+            $this->_supported_attributes = $conf['kolab']['server']['user_supported_attrs'];
+        }
+
+        /** Allows to customize the required user attributes. */
+        if (isset($conf['kolab']['server']['user_required_attrs'])) {
+            $this->_required_attributes = $conf['kolab']['server']['user_required_attrs'];
+        }
+
+        /** Allows to customize the user object classes. */
+        if (isset($conf['kolab']['server']['user_objectclasses'])) {
+            $this->_object_classes = $conf['kolab']['server']['user_object_classes'];
+        }
+
+        Horde_Kolab_Server_Object::Horde_Kolab_Server_Object($db, $dn, $data);
+    }
+
+    /**
+     * Derive an attribute value.
+     *
+     * @param string $attr The attribute to derive.
+     *
+     * @return mixed The value of the attribute.
+     */
+    function _derive($attr)
+    {
+        switch ($attr) {
+        case KOLAB_ATTR_USERTYPE:
+            if (strpos($this->_uid, 'cn=internal')) {
+                return KOLAB_UT_INTERNAL;
+            } else if (strpos($this->_uid, 'cn=group')) {
+                return KOLAB_UT_GROUP;
+            } else if (strpos($this->_uid, 'cn=resource')) {
+                return KOLAB_UT_RESOURCE;
+            } else {
+                return KOLAB_UT_STANDARD;
+            }
+        default:
+            return parent::_derive($attr);
+        }
+    }
+
+    /**
+     * Convert the object attributes to a hash.
+     *
+     * @param string $attrs The attributes to return.
+     *
+     * @return array|PEAR_Error The hash representing this object.
+     */
+    function toHash($attrs = null)
+    {
+        if (!isset($attrs)) {
+            $attrs = array(
+                KOLAB_ATTR_SID,
+                KOLAB_ATTR_FN,
+                KOLAB_ATTR_MAIL,
+                KOLAB_ATTR_USERTYPE,
+            );
+        }
+        return parent::toHash($attrs);
+    }
+
+    /**
+     * Get the groups for this object
+     *
+     * @return mixed|PEAR_Error An array of group ids, false if no groups were
+     *                          found.
+     */
+    function getGroups()
+    {
+        return $this->_db->getGroups($this->_uid);
+    }
+
+    /**
+     * Returns the server url of the given type for this user.
+     *
+     * This method is used to encapsulate multidomain support.
+     *
+     * @param string $server_type The type of server URL that should be returned.
+     *
+     * @return string The server url or empty on error.
+     */
+    function getServer($server_type)
+    {
+        global $conf;
+
+        switch ($server_type) {
+        case 'freebusy':
+            $server = $this->get(KOLAB_ATTR_FREEBUSYHOST);
+            if (!is_a($server, 'PEAR_Error') && !empty($server)) {
+                return $server;
+            }
+            $server = $this->getServer('homeserver');
+            if (is_a($server, 'PEAR_Error')) {
+                return $server;
+            }
+            if (empty($server)) {
+                $server = $_SERVER['SERVER_NAME'];
+            }
+            if (isset($conf['kolab']['freebusy']['server'])) {
+                return $conf['kolab']['freebusy']['server'];
+            }
+            if (isset($conf['kolab']['server']['freebusy_url_format'])) {
+                return sprintf($conf['kolab']['server']['freebusy_url_format'],
+                               $server);
+            } else {
+                return 'https://' . $server . '/freebusy';
+            }
+        case 'imap':
+            $server = $this->get(KOLAB_ATTR_IMAPHOST);
+            if (!is_a($server, 'PEAR_Error') && !empty($server)) {
+                return $server;
+            }
+        case 'homeserver':
+        default:
+            $server = $this->get(KOLAB_ATTR_HOMESERVER);
+            if (empty($server)) {
+                $server = $_SERVER['SERVER_NAME'];
+            }
+            return $server;
+        }
+    }
+
+    /**
+     * Generates an ID for the given information.
+     *
+     * @param array $info The data of the object.
+     *
+     * @static
+     *
+     * @return string|PEAR_Error The ID.
+     */
+    function generateId($info)
+    {
+        global $conf;
+
+        /** The fields that should get mapped into the user ID. */
+        if (isset($conf['kolab']['server']['user_id_mapfields'])) {
+            $id_mapfields = $conf['kolab']['server']['user_id_mapfields'];
+        } else {
+            $id_mapfields = array('givenName', 'sn');
+        }
+
+        /** The user ID format. */
+        if (isset($conf['kolab']['server']['user_id_format'])) {
+            $id_format = $conf['kolab']['server']['user_id_format'];
+        } else {
+            $id_format = '%s %s';
+        }
+
+        $fieldarray = array();
+        foreach ($id_mapfields as $mapfield) {
+            if (isset($info[$mapfield])) {
+                $fieldarray[] = $info[$mapfield];
+            } else {
+                $fieldarray[] = '';
+            }
+        }
+        return trim(vsprintf($id_format, $fieldarray), " \t\n\r\0\x0B,");
+    }
+
+    /**
+     * Saves object information.
+     *
+     * @param array $info The information about the object.
+     *
+     * @return boolean|PEAR_Error True on success.
+     */
+    function save($info)
+    {
+        if (!isset($info['cn'])) {
+            if (!isset($info['sn']) || !isset($info['givenName'])) {
+                return PEAR::raiseError('Either the last name or the given name is missing!');
+            } else {
+                $info['cn'] = $this->generateId($info);
+            }
+        }
+
+        if (isset($conf['kolab']['server']['user_mapping'])) {
+            $mapped = array();
+            $map    = $conf['kolab']['server']['user_mapping'];
+            foreach ($map as $key => $val) {
+                $mapped[$val] = $info[$key];
+            }
+            $info = $mapped;
+        }
+
+        if (isset($conf['kolab']['server']['user_mapping'])) {
+            $mapped = array();
+            $map    = $conf['kolab']['server']['user_mapping'];
+            foreach ($map as $key => $val) {
+                $mapped[$val] = $info[$key];
+            }
+            $info = $mapped;
+        }
+
+        return parent::save($info);
+    }
+};
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php
new file mode 100644 (file)
index 0000000..3e9d529
--- /dev/null
@@ -0,0 +1,987 @@
+<?php
+/**
+ * The driver for accessing the Kolab user database stored in LDAP.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php,v 1.9 2009/01/08 07:12:44 wrobel Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/** We need the Horde LDAP tools for this class **/
+require_once 'Horde/LDAP.php';
+
+/**
+ * This class provides methods to deal with Kolab objects stored in
+ * the standard Kolab LDAP db.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/ldap.php,v 1.9 2009/01/08 07:12:44 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_ldap extends Horde_Kolab_Server {
+
+    /**
+     * LDAP connection handle.
+     *
+     * @var resource
+     */
+    var $_connection;
+
+    /**
+     * Flag that indicates bound state for the LDAP connection.
+     *
+     * @var boolean
+     */
+    var $_bound;
+
+    /**
+     * The base dn .
+     *
+     * @var boolean
+     */
+    var $_base_dn;
+
+    /**
+     * Connects to the LDAP server.
+     *
+     * @param string $server  LDAP server URL.
+     * @param string $base_dn LDAP server base DN.
+     *
+     * @return boolean|PEAR_Error True if the connection succeeded.
+     */
+    function _connect($server = null, $base_dn = null)
+    {
+        if (!function_exists('ldap_connect')) {
+            return PEAR::raiseError(_("Cannot connect to the Kolab LDAP server. PHP does not support LDAP!"));
+        }
+
+        if (!$server) {
+            if (isset($this->_params['server'])) {
+                $server = $this->_params['server'];
+            } else {
+                return PEAR::raiseError(_("Horde_Kolab_Server_ldap needs a server parameter!"));
+            }
+        }
+        if (!$base_dn) {
+            if (isset($this->_params['base_dn'])) {
+                $this->_base_dn = $this->_params['base_dn'];
+            } else {
+                return PEAR::raiseError(_("Horde_Kolab_Server_ldap needs a base_dn parameter!"));
+            }
+        } else {
+            $this->_base_dn = $base_dn;
+        }
+
+        $this->_connection = @ldap_connect($server);
+        if (!$this->_connection) {
+            return PEAR::raiseError(sprintf(_("Error connecting to LDAP server %s!"),
+                                            $server));
+        }
+
+        /* We need version 3 for Kolab */
+        if (!ldap_set_option($this->_connection, LDAP_OPT_PROTOCOL_VERSION, 3)) {
+            return PEAR::raiseError(sprintf(_("Error setting LDAP protocol on server %s to v3: %s"),
+                                            $server,
+                                            ldap_error($this->_connection)));
+        }
+
+        return true;
+    }
+
+    /**
+     * Binds the LDAP connection with a specific user and pass.
+     *
+     * @param string $dn DN to bind with
+     * @param string $pw Password associated to this DN.
+     *
+     * @return boolean|PEAR_Error  Whether or not the binding succeeded.
+     */
+    function _bind($dn = false, $pw = '')
+    {
+        if (!$this->_connection) {
+            $result = $this->_connect();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        if (!$dn) {
+            if (isset($this->_params['uid'])) {
+                $dn = $this->_params['uid'];
+            } else {
+                $dn = '';
+            }
+        }
+        if (!$pw) {
+            if (isset($this->_params['pass'])) {
+                $pw = $this->_params['pass'];
+            }
+        }
+
+        $this->_bound = @ldap_bind($this->_connection, $dn, $pw);
+
+        if (!$this->_bound) {
+            return PEAR::raiseError(sprintf(_("Unable to bind to the LDAP server as %s!"),
+                                            $dn));
+        }
+        return true;
+    }
+
+    /**
+     * Disconnect from LDAP.
+     *
+     * @return NULL
+     */
+    function unbind()
+    {
+        $result = @ldap_unbind($this->_connection);
+        if (!$result) {
+            return PEAR::raiseError("Failed to unbind from the LDAP server!");
+        }
+
+        $this->_bound = false;
+    }
+
+    /**
+     * Search for an object.
+     *
+     * @param string $filter     Filter criteria.
+     * @param array  $attributes Restrict the search result to
+     *                           these attributes.
+     * @param string $base       The base location for searching.
+     *
+     * @return array|PEAR_Error A LDAP search result.
+     */
+    function _search($filter, $attributes = null, $base = null)
+    {
+        if (!$this->_bound) {
+            $result = $this->_bind();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        if (empty($base)) {
+            $base = $this->_base_dn;
+        }
+
+        if (isset($attributes)) {
+            $result = @ldap_search($this->_connection, $base, $filter, $attributes);
+        } else {
+            $result = @ldap_search($this->_connection, $base, $filter);
+        }
+        if (!$result && $this->_errno()) {
+            return PEAR::raiseError(sprintf(_("LDAP Error: Failed to search using filter %s. Error was: %s"),
+                                            $filter, $this->_error()));
+        }
+        return $result;
+    }
+
+    /**
+     * Read object data.
+     *
+     * @param string $dn    The object to retrieve.
+     * @param string $attrs Restrict to these attributes.
+     *
+     * @return array|PEAR_Error An array of attributes.
+     */
+    function _read($dn, $attrs = null)
+    {
+        if (!$this->_bound) {
+            $result = $this->_bind();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        if (isset($attrs)) {
+            $result = @ldap_read($this->_connection, $dn, '(objectclass=*)', $attrs);
+        } else {
+            $result = @ldap_read($this->_connection, $dn, '(objectclass=*)');
+        }
+        if (!$result && $this->_errno()) {
+            return PEAR::raiseError(sprintf(_("LDAP Error: No such object: %s: %s"),
+                                            $dn, $this->_error()));
+        }
+        $entry = $this->_firstEntry($result);
+        if (!$entry) {
+            ldap_free_result($result);
+            return PEAR::raiseError(sprintf(_("LDAP Error: Empty result for: %s."),
+                                            $dn));
+        }
+        $object = $this->_getAttributes($entry);
+        if (!$object  && $this->_errno()) {
+            return PEAR::raiseError(sprintf(_("LDAP Error: No such dn: %s: %s"),
+                                            $dn, $this->_error()));
+        }
+        ldap_free_result($result);
+        return $object;
+    }
+
+    /**
+     * Add a new object
+     *
+     * @param string $dn   The DN of the object to be added.
+     * @param array  $data The attributes of the object to be added.
+     *
+     * @return boolean  True if adding succeeded.
+     */
+    function _add($dn, $data)
+    {
+        if (!$this->_bound) {
+            $result = $this->_bind();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        return @ldap_add($this->_connection, $dn, $data);
+    }
+
+    /**
+     * Count the number of results.
+     *
+     * @param string $result The LDAP search result.
+     *
+     * @return int The number of records found.
+     */
+    function _count($result)
+    {
+        return @ldap_count_entries($this->_connection, $result);
+    }
+
+    /**
+     * Return the dn of an entry.
+     *
+     * @param resource $entry The LDAP entry.
+     *
+     * @return string  The DN of the entry.
+     */
+    function _getDn($entry)
+    {
+        return @ldap_get_dn($this->_connection, $entry);
+    }
+
+    /**
+     * Return the attributes of an entry.
+     *
+     * @param resource $entry The LDAP entry.
+     *
+     * @return array  The attributes of the entry.
+     */
+    function _getAttributes($entry)
+    {
+        return @ldap_get_attributes($this->_connection, $entry);
+    }
+
+    /**
+     * Return the first entry of a result.
+     *
+     * @param resource $result The LDAP search result.
+     *
+     * @return resource  The first entry of the result.
+     */
+    function _firstEntry($result)
+    {
+        return @ldap_first_entry($this->_connection, $result);
+    }
+
+    /**
+     * Return the next entry of a result.
+     *
+     * @param resource $entry The current LDAP entry.
+     *
+     * @return resource  The next entry of the result.
+     */
+    function _nextEntry($entry)
+    {
+        return @ldap_next_entry($this->_connection, $entry);
+    }
+
+    /**
+     * Return the entries of a result.
+     *
+     * @param resource $result The LDAP search result.
+     * @param int      $from   Only return results after this position.
+     * @param int      $to     Only return results until this position.
+     *
+     * @return array  The entries of the result.
+     */
+    function _getEntries($result, $from = -1, $to = -1)
+    {
+        if ($from >= 0 || $to >= 0) {
+            $result = array();
+
+            $i = 0;
+            for ($entry = $this->_firstEntry($result);
+                 $entry != false;
+                 $entry = $this->_nextEntry($entry)) {
+                if (!$entry  && $this->_errno()) {
+                    return false;
+                }
+                if ($i > $from && ($i <= $to || $to == -1)) {
+                    $attributes = $this->_getAttributes($entry);
+                    if (!$attributes  && $this->_errno()) {
+                        return false;
+                    }
+                    $result[] = $attributes;
+                }
+                $i++;
+            }
+            return $result;
+        }
+        return @ldap_get_entries($this->_connection, $result);
+    }
+
+    /**
+     * Sort the entries of a result.
+     *
+     * @param resource $result    The LDAP search result.
+     * @param string   $attribute The attribute used for sorting.
+     *
+     * @return boolean  True if sorting succeeded.
+     */
+    function _sort($result, $attribute)
+    {
+        return @ldap_sort($this->_connection, $result, $attribute);
+    }
+
+    /**
+     * Return the current LDAP error number.
+     *
+     * @return int  The current LDAP error number.
+     */
+    function _errno()
+    {
+        return @ldap_errno($this->_connection);
+    }
+
+    /**
+     * Return the current LDAP error description.
+     *
+     * @return string  The current LDAP error description.
+     */
+    function _error()
+    {
+        return @ldap_error($this->_connection);
+    }
+
+    /*
+     * ------------------------------------------------------------------
+     * The functions defined below do not call ldap_* functions directly.
+     * ------------------------------------------------------------------
+     */
+
+    /**
+     * Return the root of the UID values on this server.
+     *
+     * @return string The base UID on this server (base DN on ldap).
+     */
+    function getBaseUid()
+    {
+        return $this->_base_dn;
+    }
+
+    /**
+     * Return the DNs of a result.
+     *
+     * @param resource $result The LDAP search result.
+     * @param int      $from   Only return results after this position.
+     * @param int      $to     Only return results until this position.
+     *
+     * @return array  The DNs of the result.
+     */
+    function _getDns($result, $from = -1, $to = -1)
+    {
+        $dns   = array();
+        $entry = $this->_firstEntry($result);
+
+        $i = 0;
+        for ($entry = $this->_firstEntry($result);
+             $entry != false;
+             $entry = $this->_nextEntry($entry)) {
+            if ($i > $from && ($i <= $to || $to == -1)) {
+                $dn = $this->_getDn($entry);
+                if (!$dn  && $this->_errno()) {
+                    return false;
+                }
+                $dns[] = $dn;
+            }
+            $i++;
+        }
+        if ($this->_errno()) {
+            return false;
+        }
+        return $dns;
+    }
+
+    /**
+     * Identify the DN of the first result entry.
+     *
+     * @param array $result   The LDAP search result.
+     * @param int   $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return string|PEAR_Error The DN.
+     */
+    function _dnFromResult($result, $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        switch ($restrict) {
+        case KOLAB_SERVER_RESULT_STRICT:
+            $count = $this->_count($result);
+            if (!$count) {
+                return false;
+            } else if ($count > 1) {
+                return PEAR::raiseError(sprintf(_("Found %s results when expecting only one!"),
+                                                $count));
+            }
+        case KOLAB_SERVER_RESULT_SINGLE:
+            $entry = $this->_firstEntry($result);
+            if (!$entry  && $this->_errno()) {
+                return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"),
+                                                $this->_error()));
+            }
+            if (!$entry) {
+                return false;
+            }
+            $dn = $this->_getDn($entry);
+            if (!$dn  && $this->_errno()) {
+                return PEAR::raiseError(sprintf(_("Retrieving DN failed. Error was: %s"),
+                                                $this->_error()));
+            }
+            return $dn;
+        case KOLAB_SERVER_RESULT_MANY:
+            $entries = $this->_getDns($result);
+            if (!$entries  && $this->_errno()) {
+                return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"),
+                                                $this->_error()));
+            }
+            if (!$entries) {
+                return false;
+            }
+            return $entries;
+        }
+        return false;
+    }
+
+    /**
+     * Get the attributes of the first result entry.
+     *
+     * @param array $result   The LDAP search result.
+     * @param array $attrs    The attributes to retrieve.
+     * @param int   $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The attributes or false if there were
+     *                          no results.
+     */
+    function _attrsFromResult($result, $attrs,
+                              $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        $entries = array();
+
+        switch ($restrict) {
+        case KOLAB_SERVER_RESULT_STRICT:
+            $count = $this->_count($result);
+            if (!$count) {
+                return false;
+            } else if ($count > 1) {
+                return PEAR::raiseError(sprintf(_("Found %s results when expecting only one!"),
+                                                $count));
+            }
+        case KOLAB_SERVER_RESULT_SINGLE:
+            $first = $this->_firstEntry($result);
+            if (!$first  && $this->_errno()) {
+                return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"),
+                                                $this->_error()));
+            }
+            if (!$first) {
+                return false;
+            }
+            $entry = $this->_getAttributes($first);
+            if (!$entry  && $this->_errno()) {
+                return PEAR::raiseError(sprintf(_("Retrieving attributes failed. Error was: %s"),
+                                                $this->_error()));
+            }
+
+            $result = array();
+            foreach ($attrs as $attr) {
+                if ($entry[$attr]['count'] > 0) {
+                    unset($entry[$attr]['count']);
+                    $result[$attr] = $entry[$attr];
+                }
+            }
+            return $result;
+        case KOLAB_SERVER_RESULT_MANY:
+            $entries = $this->_getEntries($result);
+            if (!$entries  && $this->_errno()) {
+                return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"),
+                                                $this->_error()));
+            }
+            if (!$entries) {
+                return false;
+            }
+            unset($entries['count']);
+            $result = array();
+
+            $i = 0;
+            foreach ($entries as $entry) {
+                $result[$i] = array();
+                foreach ($attrs as $attr) {
+                    if (isset($entry[$attr])) {
+                        if ($entry[$attr]['count'] > 0) {
+                            unset($entry[$attr]['count']);
+                            $result[$i][$attr] = $entry[$attr];
+                        }
+                    }
+                }
+                $i++;
+            }
+            return $result;
+        }
+        return false;
+    }
+
+    /**
+     * Determine the type of a Kolab object.
+     *
+     * @param string $dn The DN of the object to examine.
+     *
+     * @return int The corresponding Kolab object type.
+     */
+    function _determineType($dn)
+    {
+        $oc = $this->_getObjectClasses($dn);
+        if (is_a($oc, 'PEAR_Error')) {
+            return $oc;
+        }
+
+        // Not a user type?
+        if (!in_array('kolabinetorgperson', $oc)) {
+            // Is it a group?
+            if (in_array('kolabgroupofnames', $oc)) {
+                return KOLAB_OBJECT_GROUP;
+            }
+            // Is it a shared Folder?
+            if (in_array('kolabsharedfolder', $oc)) {
+                return KOLAB_OBJECT_SHAREDFOLDER;
+            }
+            return PEAR::raiseError(sprintf(_("Unkown Kolab object type for DN %s."),
+                                            $dn));
+        }
+
+        $groups = $this->getGroups($dn);
+        if (is_a($groups, 'PEAR_Error')) {
+            return $groups;
+        }
+        if (!empty($groups)) {
+            if (in_array('cn=admin,cn=internal,' . $this->_base_dn, $groups)) {
+                return KOLAB_OBJECT_ADMINISTRATOR;
+            }
+            if (in_array('cn=maintainer,cn=internal,' . $this->_base_dn,
+                         $groups)) {
+                return KOLAB_OBJECT_MAINTAINER;
+            }
+            if (in_array('cn=domain-maintainer,cn=internal,' . $this->_base_dn,
+                         $groups)) {
+                return KOLAB_OBJECT_DOMAINMAINTAINER;
+            }
+        }
+
+        if (strpos($dn, 'cn=external') !== false) {
+            return KOLAB_OBJECT_ADDRESS;
+        }
+
+        return KOLAB_OBJECT_USER;
+    }
+
+    /**
+     * Get the LDAP object classes for the given DN.
+     *
+     * @param string $dn DN of the object.
+     *
+     * @return array|PEAR_Error An array of object classes.
+     */
+    function _getObjectClasses($dn)
+    {
+        $object = $this->_read($dn, array('objectClass'));
+        if (is_a($object, 'PEAR_Error')) {
+            return $object;
+        }
+        if (!isset($object['objectClass'])) {
+            return PEAR::raiseError('The result has no object classes!');
+        }
+        unset($object['count']);
+        unset($object['objectClass']['count']);
+        $result = array_map('strtolower', $object['objectClass']);
+        return $result;
+    }
+
+    /**
+     * Identify the DN for the first object found using a filter.
+     *
+     * @param string $filter   The LDAP filter to use.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The DN or false if there was no result.
+     */
+    function _dnForFilter($filter,
+                          $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        $result = $this->_search($filter, array());
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        if (!$this->_count($result)) {
+            return false;
+        }
+        return $this->_dnFromResult($result, $restrict);
+    }
+
+    /**
+     * Identify attributes for the first object found using a filter.
+     *
+     * @param string $filter   The LDAP filter to use.
+     * @param array  $attrs    The attributes to retrieve.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The DN or false if there was no result.
+     */
+    function _attrsForFilter($filter, $attrs,
+                             $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        $result = $this->_search($filter, $attrs);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        return $this->_attrsFromResult($result, $attrs, $restrict);
+    }
+
+    /**
+     * Identify the primary mail attribute for the first object found
+     * with the given ID or mail.
+     *
+     * @param string $id Search for objects with this ID/mail.
+     *
+     * @return mixed|PEAR_Error The mail address or false if there was
+     *                          no result.
+     */
+    function mailForIdOrMail($id)
+    {
+        $filter = '(&(objectClass=kolabInetOrgPerson)(|(uid='.
+            Horde_LDAP::quote($id) . ')(mail=' .
+            Horde_LDAP::quote($id) . ')))';
+        $result = $this->_attrsForFilter($filter, array('mail'),
+                                         KOLAB_SERVER_RESULT_STRICT);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        return $result['mail'][0];
+    }
+
+    /**
+     * Identify the UID for the first object found with the given ID
+     * or mail.
+     *
+     * @param string $id Search for objects with this ID/mail.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForIdOrMail($id)
+    {
+        $filter = '(&(objectClass=kolabInetOrgPerson)(|(uid='.
+            Horde_LDAP::quote($id) . ')(mail=' .
+            Horde_LDAP::quote($id) . ')))';
+        return $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_STRICT);
+    }
+
+    /**
+     * Returns a list of allowed email addresses for the given user.
+     *
+     * @param string $id The users primary mail address or ID.
+     *
+     * @return array|PEAR_Error An array of allowed mail addresses
+     */
+    function addrsForIdOrMail($id)
+    {
+        $filter = '(&(objectClass=kolabInetOrgPerson)(|(mail='
+            . Horde_LDAP::quote($id) . ')(uid='
+            . Horde_LDAP::quote($id) . ')))';
+        $result = $this->_attrsForFilter($filter, array('mail', 'alias'),
+                                         KOLAB_SERVER_RESULT_STRICT);
+        if (empty($result) || is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $addrs = array_merge((array) $result['mail'], (array) $result['alias']);
+        $mail  = $result['mail'][0];
+
+        $filter = '(&(objectClass=kolabInetOrgPerson)(kolabDelegate='
+            . Horde_LDAP::quote($mail) . '))';
+        $result = $this->_attrsForFilter($filter, array('mail', 'alias'),
+                                         KOLAB_SERVER_RESULT_MANY);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        if (!empty($result)) {
+            foreach ($result as $adr) {
+                if (isset($adr['mail'])) {
+                    $addrs = array_merge((array) $addrs, (array) $adr['mail']);
+                }
+                if (isset($adr['alias'])) {
+                    $addrs = array_merge((array) $addrs, (array) $adr['alias']);
+                }
+            }
+        }
+
+        return $addrs;
+    }
+
+    /**
+     * Return the UID for a given primary mail, ID, or alias.
+     *
+     * @param string $mail A valid mail address for the user.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForMailAddress($mail)
+    {
+        $filter = '(&(objectClass=kolabInetOrgPerson)(|(uid='.
+            Horde_LDAP::quote($mail) . ')(mail=' .
+            Horde_LDAP::quote($mail) . ')(alias=' .
+            Horde_LDAP::quote($mail) . ')))';
+        return $this->_dnForFilter($filter);
+    }
+
+    /**
+     * Identify the UID for the first object found using a specified
+     * attribute value.
+     *
+     * @param string $attr     The name of the attribute used for searching.
+     * @param string $value    The desired value of the attribute.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The UID or false if there was no result.
+     */
+    function uidForAttr($attr, $value,
+                       $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        $filter = '(&(objectClass=kolabInetOrgPerson)(' . $attr .
+            '=' . Horde_LDAP::quote($value) . '))';
+        return $this->_dnForFilter($filter, $restrict);
+    }
+
+    /**
+     * Identify the GID for the first group found using a specified
+     * attribute value.
+     *
+     * @param string $attr     The name of the attribute used for searching.
+     * @param string $value    The desired value of the attribute.
+     * @param int    $restrict A KOLAB_SERVER_RESULT_* result restriction.
+     *
+     * @return mixed|PEAR_Error The GID or false if there was no result.
+     */
+    function gidForAttr($attr, $value,
+                       $restrict = KOLAB_SERVER_RESULT_SINGLE)
+    {
+        $filter = '(&(objectClass=kolabGroupOfNames)(' . $attr .
+            '=' . Horde_LDAP::quote($value) . '))';
+        return $this->_dnForFilter($filter, $restrict);
+    }
+
+    /**
+     * Is the given UID member of the group with the given mail address?
+     *
+     * @param string $uid  UID of the user.
+     * @param string $mail Search the group with this mail address.
+     *
+     * @return boolen|PEAR_Error True in case the user is in the
+     *                           group, false otherwise.
+     */
+    function memberOfGroupAddress($uid, $mail)
+    {
+        $filter = '(&(objectClass=kolabGroupOfNames)(mail='
+            . Horde_LDAP::quote($mail) . ')(member='
+            . Horde_LDAP::quote($uid) . '))';
+        $result = $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_STRICT);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        if (empty($result)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get the groups for this object.
+     *
+     * @param string $uid The UID of the object to fetch.
+     *
+     * @return array|PEAR_Error An array of group ids.
+     */
+    function getGroups($uid)
+    {
+        $filter = '(&(objectClass=kolabGroupOfNames)(member='
+            . Horde_LDAP::quote($uid) . '))';
+        $result = $this->_dnForFilter($filter, KOLAB_SERVER_RESULT_MANY);
+        if (empty($result)) {
+            return array();
+        }
+        return $result;
+    }
+
+    /**
+     * List all objects of a specific type
+     *
+     * @param string $type   The type of the objects to be listed
+     * @param array  $params Additional parameters.
+     *
+     * @return array|PEAR_Error An array of Kolab objects.
+     */
+    function _listObjects($type, $params = null)
+    {
+        if (empty($params['base_dn'])) {
+            $base = $this->_base_dn;
+        } else {
+            $base = $params['base_dn'];
+        }
+
+        $result = Horde_Kolab_Server_Object::loadClass($type);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        $vars   = get_class_vars($type);
+        $filter = $vars['filter'];
+        $sort   = $vars['sort_by'];
+
+        if (isset($params['sort'])) {
+            $sort = $params['sort'];
+        }
+
+        $result = $this->_search($filter, null, $base);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+        if (empty($result)) {
+            return array();
+        }
+
+        if ($sort) {
+            $this->_sort($result, $sort);
+        }
+
+        if (isset($params['from'])) {
+            $from = $params['from'];
+        } else {
+            $from = -1;
+        }
+
+        if (isset($params['to'])) {
+            $sort = $params['to'];
+        } else {
+            $to = -1;
+        }
+
+        $entries = $this->_getDns($result, $from, $to);
+        if (!$entries  && $this->_errno()) {
+            return PEAR::raiseError(sprintf(_("Search failed. Error was: %s"),
+                                            $this->_error()));
+        }
+        if (!$entries) {
+            return false;
+        }
+
+        if (!empty($vars['required_group'])) {
+            $required_group = $this->fetch($vars['required_group'],
+                                           KOLAB_OBJECT_GROUP);
+        }
+
+        $objects = array();
+        foreach ($entries as $dn) {
+            if (!empty($vars['required_group']) && $required_group->isMember($dn)) {
+                continue;
+            }
+            $result = $this->fetch($dn, $type);
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            $objects[] = $result;
+        }
+        return $objects;
+    }
+
+    /**
+     * Generates a UID for the given information.
+     *
+     * @param string $type The type of the object to create.
+     * @param string $id   The id of the object.
+     * @param array  $info Any additional information about the object to create.
+     *
+     * @return string|PEAR_Error The DN.
+     */
+    function _generateUid($type, $id, $info)
+    {
+        switch ($type) {
+        case KOLAB_OBJECT_USER:
+            if (!isset($info['user_type']) || $info['user_type'] == 0) {
+                return sprintf('cn=%s,%s', $id, $this->_base_dn);
+            } else if ($info['user_type'] == KOLAB_UT_INTERNAL) {
+                return sprintf('cn=%s,cn=internal,%s', $id, $this->_base_dn);
+            } else if ($info['user_type'] == KOLAB_UT_GROUP) {
+                return sprintf('cn=%s,cn=groups,%s', $id, $this->_base_dn);
+            } else if ($info['user_type'] == KOLAB_UT_RESOURCE) {
+                return sprintf('cn=%s,cn=resources,%s', $id, $this->_base_dn);
+            } else {
+                return sprintf('cn=%s,%s', $id, $this->_base_dn);
+            }
+        case KOLAB_OBJECT_ADDRESS:
+            return sprintf('cn=%s,cn=external,%s', $id, $this->_base_dn);
+        case KOLAB_OBJECT_SHAREDFOLDER:
+        case KOLAB_OBJECT_ADMINISTRATOR:
+        case KOLAB_OBJECT_MAINTAINER:
+        case KOLAB_OBJECT_DOMAINMAINTAINER:
+            return sprintf('cn=%s,%s', $id, $this->_base_dn);
+        case KOLAB_OBJECT_GROUP:
+        case KOLAB_OBJECT_DISTLIST:
+            if (!isset($info['visible']) || !empty($info['visible'])) {
+                return sprintf('cn=%s,%s', $id, $this->_base_dn);
+            } else {
+                return sprintf('cn=%s,cn=internal,%s', $id, $this->_base_dn);
+            }
+        default:
+            return PEAR::raiseError(_("Not implemented!"));
+        }
+    }
+
+    /**
+     * Save an object.
+     *
+     * @param string $dn   The DN of the object.
+     * @param array  $data The data for the object.
+     *
+     * @return boolean|PEAR_Error True if successfull.
+     */
+    function save($dn, $data)
+    {
+        $result = $this->_add($dn, $data);
+        if (!$result  && $this->_errno()) {
+            return PEAR::raiseError(sprintf(_("Failed saving object. Error was: %s"),
+                                            $this->_error()));
+        }
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Server/test.php b/framework/Kolab_Server/lib/Horde/Kolab/Server/test.php
new file mode 100644 (file)
index 0000000..65c7820
--- /dev/null
@@ -0,0 +1,623 @@
+<?php
+/**
+ * A driver for simulating a Kolab user database stored in LDAP.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/test.php,v 1.8 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/** Require the LDAP based class as our base class */
+require_once 'Horde/Kolab/Server/ldap.php';
+
+/**
+ * This class provides a class for testing the Kolab Server DB.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Server/test.php,v 1.8 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_test extends Horde_Kolab_Server_ldap {
+
+    /**
+     * Array holding the current result set.
+     *
+     * @var array
+     */
+    var $_result;
+
+    /**
+     * Buffer for error numbers.
+     *
+     * @var int
+     */
+    var $_errno = 0;
+
+    /**
+     * Buffer for error descriptions.
+     *
+     * @var int
+     */
+    var $_error = '';
+
+    /**
+     * Attribute used for sorting.
+     *
+     * @var string
+     */
+    var $_sort_by;
+
+    /**
+     * A result cache for iterating over the result.
+     *
+     * @var array
+     */
+    var $_current_result;
+
+    /**
+     * An index into the current result for iterating.
+     *
+     * @var int
+     */
+    var $_current_index;
+
+    /**
+     * Construct a new Horde_Kolab_Server object.
+     *
+     * @param array $params Parameter array.
+     */
+    function Horde_Kolab_Server_test($params = array())
+    {
+        if (isset($params['data'])) {
+            $GLOBALS['KOLAB_SERVER_TEST_DATA'] = $params['data'];
+        } else {
+            if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'])) {
+                $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array();
+            }
+        }
+        Horde_Kolab_Server::Horde_Kolab_Server($params);
+    }
+
+    /**
+     * Binds the LDAP connection with a specific user and pass.
+     *
+     * @param string $dn DN to bind with
+     * @param string $pw Password associated to this DN.
+     *
+     * @return boolean|PEAR_Error  Whether or not the binding succeeded.
+     */
+    function _bind($dn = false, $pw = '')
+    {
+        if (!$dn) {
+            if (isset($this->_params['uid'])) {
+                $dn = $this->_params['uid'];
+            } else {
+                $dn = '';
+            }
+        }
+        if (!$pw) {
+            if (isset($this->_params['pass'])) {
+                $pw = $this->_params['pass'];
+            }
+        }
+
+        if (!empty($dn)) {
+            if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn])) {
+                return PEAR::raiseError('User does not exist!');
+            }
+
+            $this->_bound = true;
+
+            $data = $this->_read($dn, $attrs = array('userPassword'));
+            if (is_a($data, 'PEAR_Error')) {
+                $this->_bound = false;
+                return $data;
+            }
+            if (!isset($data['userPassword'])) {
+                $this->_bound = false;
+                return PEAR::raiseError('User has no password entry!');
+            }
+            $this->_bound = $data['userPassword'][0] == $pw;
+            if (!$this->_bound) {
+                return PEAR::raiseError('Incorrect password!');
+            }
+        } else if (!empty($this->_params['no_anonymous_bind'])) {
+            $this->_bound = false;
+            return PEAR::raiseError('Anonymous bind is not allowed!');
+        } else {
+            $this->_bound = true;
+        }
+        return $this->_bound;
+    }
+
+    /**
+     * Disconnect from LDAP.
+     *
+     * @return NULL
+     */
+    function unbind()
+    {
+        $this->_bound = false;
+    }
+
+    /**
+     * Parse LDAP filter.
+     * Partially derived from Net_LDAP_Filter.
+     *
+     * @param string $filter The filter string.
+     *
+     * @return array|PEAR_Error An array of the parsed filter.
+     */
+    function _parse($filter)
+    {
+        $result = array();
+        if (preg_match('/^\((.+?)\)$/', $filter, $matches)) {
+            if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
+                $result['op']  = substr($matches[1], 0, 1);
+                $result['sub'] = $this->_parseSub(substr($matches[1], 1));
+                return $result;
+            } else {
+                if (stristr($matches[1], ')(')) {
+                    return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
+                } else {
+                    $filter_parts = preg_split('/(?<!\\\\)(=|=~|>|<|>=|<=)/',
+                                               $matches[1], 2,
+                                               PREG_SPLIT_DELIM_CAPTURE);
+                    if (count($filter_parts) != 3) {
+                        return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
+                    } else {
+                        $result['att'] = $filter_parts[0];
+                        $result['log'] = $filter_parts[1];
+                        $result['val'] = $filter_parts[2];
+                        return $result;
+                    }
+                }
+            }
+        } else {
+            return PEAR::raiseError(sprintf("Filter parsing error: %s - filter components must be enclosed in round brackets",
+                                            $filter));
+        }
+    }
+
+    /**
+     * Parse a LDAP subfilter.
+     *
+     * @param string $filter The subfilter string.
+     *
+     * @return array|PEAR_Error An array of the parsed subfilter.
+     */
+    function _parseSub($filter)
+    {
+        $result  = array();
+        $level   = 0;
+        $collect = '';
+        while (preg_match('/^(\(.+?\))(.*)/', $filter, $matches)) {
+            if (in_array(substr($matches[1], 0, 2), array('(!', '(|', '(&'))) {
+                $level++;
+            }
+            if ($level) {
+                $collect .= $matches[1];
+                if (substr($matches[2], 0, 1) == ')') {
+                    $collect   .= ')';
+                    $matches[2] = substr($matches[2], 1);
+                    $level--;
+                    if (!$level) {
+                        $result[] = $this->_parse($collect);
+                    }
+                }
+            } else {
+                $result[] = $this->_parse($matches[1]);
+            }
+            $filter = $matches[2];
+        }
+        return $result;
+    }
+
+    /**
+     * Search for an object.
+     *
+     * @param string $filter     Filter criteria.
+     * @param array  $attributes Restrict the search result to
+     *                           these attributes.
+     * @param string $base       DN of the search base.
+     *
+     * @return array|PEAR_Error A LDAP serach result.
+     */
+    function _search($filter, $attributes = null, $base = null)
+    {
+        if (!$this->_bound) {
+            $result = $this->_bind();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $filter = $this->_parse($filter);
+        if (is_a($filter, 'PEAR_Error')) {
+            return $filter;
+        }
+        $result = $this->_doSearch($filter, $attributes);
+        if (empty($result)) {
+            return null;
+        }
+        if ($base) {
+            $subtree = array();
+            foreach ($result as $entry) {
+                if (strpos($entry['dn'], $base)) {
+                    $subtree[] = $entry;
+                }
+            }
+            $result = $subtree;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Perform the search.
+     *
+     * @param array $filter     Filter criteria-
+     * @param array $attributes Restrict the search result to
+     *                          these attributes.
+     *
+     * @return array|PEAR_Error A LDAP serach result.
+     */
+    function _doSearch($filter, $attributes = null)
+    {
+        if (isset($filter['log'])) {
+            $result = array();
+            foreach ($GLOBALS['KOLAB_SERVER_TEST_DATA'] as $element) {
+                if (isset($element['data'][$filter['att']])) {
+                    switch ($filter['log']) {
+                    case '=':
+                        $value = $element['data'][$filter['att']];
+                        if (($filter['val'] == '*' && !empty($value))
+                            || $value == $filter['val']
+                            || (is_array($value)
+                                && in_array($filter['val'], $value))) {
+                            if (empty($attributes)) {
+                                $result[] = $element;
+                            } else {
+                                $selection = $element;
+                                foreach ($element['data'] as $attr => $value) {
+                                    if (!in_array($attr, $attributes)) {
+                                        unset($selection['data'][$attr]);
+                                    }
+                                }
+                                $result[] = $selection;
+                            }
+                        }
+                        break;
+                    default:
+                        return PEAR::raiseError(_("Not implemented!"));
+                    }
+                }
+            }
+            return $result;
+        } else {
+            $subresult   = array();
+            $filtercount = count($filter['sub']);
+            foreach ($filter['sub'] as $subfilter) {
+                $subresult = array_merge($subresult,
+                                         $this->_doSearch($subfilter,
+                                                          $attributes));
+            }
+            $result = array();
+            $dns    = array();
+            foreach ($subresult as $element) {
+                $dns[] = $element['dn'];
+
+                $result[$element['dn']] = $element;
+            }
+            switch ($filter['op']) {
+            case '&':
+                $count     = array_count_values($dns);
+                $selection = array();
+                foreach ($count as $dn => $value) {
+                    if ($value == $filtercount) {
+                        $selection[] = $result[$dn];
+                    }
+                }
+                return $selection;
+            case '|':
+                return array_values($result);
+            case '!':
+                $dns = array();
+                foreach ($result as $entry) {
+                    if (!in_array($entry['dn'], $dns) ) {
+                        $dns[] = $entry['dn'];
+                    }
+                }
+                $all_dns = array_keys($GLOBALS['KOLAB_SERVER_TEST_DATA']);
+                $diff    = array_diff($all_dns, $dns);
+
+                $result = array();
+                foreach ($diff as $dn) {
+                    if (empty($attributes)) {
+                        $result[] = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn];
+                    } else {
+                        $selection = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn];
+                        foreach ($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data']
+                                 as $attr => $value) {
+                            if (!in_array($attr, $attributes)) {
+                                unset($selection['data'][$attr]);
+                            }
+                        }
+                        $result[] = $selection;
+                    }
+                }
+                return $result;
+            default:
+                return PEAR::raiseError(_("Not implemented!"));
+            }
+        }
+    }
+
+    /**
+     * Read object data.
+     *
+     * @param string $dn    The object to retrieve.
+     * @param string $attrs Restrict to these attributes
+     *
+     * @return array|PEAR_Error An array of attributes.
+     */
+    function _read($dn, $attrs = null)
+    {
+        if (!$this->_bound) {
+            $result = $this->_bind();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        if (!isset($GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn])) {
+            return PEAR::raiseError(sprintf("LDAP Error: No such object: %s: No such object",
+                                            $dn));
+        }
+        if (empty($attrs)) {
+            return $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data'];
+        } else {
+            $result = array();
+            $data   = $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn]['data'];
+
+            foreach ($attrs as $attr) {
+                if (isset($data[$attr])) {
+                    $result[$attr] = $data[$attr];
+                    array_push($result, $attr);
+                }
+            }
+            $result['count'] = 1;
+            return $result;
+        }
+    }
+
+    /**
+     * Add a new object
+     *
+     * @param string $dn   The DN of the object to be added.
+     * @param array  $data The attributes of the object to be added.
+     *
+     * @return boolean  True if adding succeeded.
+     */
+    function _add($dn, $data)
+    {
+        if (!$this->_bound) {
+            $result = $this->_bind();
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+        }
+
+        $ldap_data = array();
+        foreach ($data as $key => $val) {
+            if (!is_array($val)) {
+                $val = array($val);
+            }
+            $ldap_data[$key] = array_merge(array('count' => count($val)), $val);
+        }
+
+        $GLOBALS['KOLAB_SERVER_TEST_DATA'][$dn] = array(
+            'dn' => $dn,
+            'data' => $ldap_data
+        );
+    }
+
+    /**
+     * Count the number of results.
+     *
+     * @param array $result The LDAP search result.
+     *
+     * @return int The number of records found.
+     */
+    function _count($result)
+    {
+        if (is_array($result)) {
+            return count($result);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Return the dn of an entry.
+     *
+     * @param array $entry The LDAP entry.
+     *
+     * @return string  The DN of the entry.
+     */
+    function _getDn($entry)
+    {
+        if (is_array($entry) && isset($entry['dn'])) {
+            if (isset($entry['count'])) {
+                return $entry['dn'][0];
+            } else {
+                return $entry['dn'];
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return the attributes of an entry.
+     *
+     * @param array $entry The LDAP entry.
+     *
+     * @return array  The attributes of the entry.
+     */
+    function _getAttributes($entry)
+    {
+        if (is_array($entry)) {
+            return $entry;
+        }
+        return false;
+    }
+
+    /**
+     * Return the current entry of a result.
+     *
+     * @return mixe  The current entry of the result or false.
+     */
+    function _fetchEntry()
+    {
+        if (is_array($this->_current_result)
+            && $this->_current_index < count($this->_current_result)) {
+
+            $data = array_keys($this->_current_result[$this->_current_index]['data']);
+
+            $data['count']       = 1;
+            $data['dn']          = array($this->_current_result[$this->_current_index]['dn']);
+            $data['dn']['count'] = 1;
+
+            foreach ($this->_current_result[$this->_current_index]['data']
+                     as $attr => $value) {
+                if (!is_array($value)) {
+                    $value = array($value);
+                }
+                $data[$attr]          = $value;
+                $data[$attr]['count'] = count($value);
+            }
+            $this->_current_index++;
+            return $data;
+        }
+        return false;
+    }
+
+    /**
+     * Return the first entry of a result.
+     *
+     * @param array $result The LDAP search result.
+     *
+     * @return mixed The first entry of the result or false.
+     */
+    function _firstEntry($result)
+    {
+        $this->_current_result = $result;
+        $this->_current_index  = 0;
+        return $this->_fetchEntry();
+    }
+
+    /**
+     * Return the next entry of a result.
+     *
+     * @param resource $entry The current LDAP entry.
+     *
+     * @return resource The next entry of the result.
+     */
+    function _nextEntry($entry)
+    {
+        return $this->_fetchEntry();
+    }
+
+    /**
+     * Return the entries of a result.
+     *
+     * @param array $result The LDAP search result.
+     *
+     * @return mixed The entries of the result or false.
+     */
+    function _getEntries($result)
+    {
+        if (is_array($result)) {
+            $data          = array();
+            $data['count'] = count($result);
+            foreach ($result as $entry) {
+                $t       = $entry['data'];
+                $t['dn'] = $entry['dn'];
+                $data[]  = $t;
+            }
+            return $data;
+        }
+        return false;
+    }
+
+    /**
+     * Sort the entries of a result.
+     *
+     * @param resource &$result   The LDAP search result.
+     * @param string   $attribute The attribute used for sorting.
+     *
+     * @return boolean  True if sorting succeeded.
+     */
+    function _sort(&$result, $attribute)
+    {
+        if (empty($result)) {
+            return $result;
+        }
+
+        $this->_sort_by = $attribute;
+        usort($result, array($this, '_resultSort'));
+        return false;
+    }
+
+    /**
+     * Sort two entries.
+     *
+     * @param array $a First entry.
+     * @param array $b Second entry.
+     *
+     * @return int  Comparison result.
+     */
+    function _resultSort($a, $b)
+    {
+        $x = isset($a['data'][$this->_sort_by][0])?$a['data'][$this->_sort_by][0]:'';
+        $y = isset($b['data'][$this->_sort_by][0])?$b['data'][$this->_sort_by][0]:'';
+        return strcasecmp($x, $y);
+    }
+
+
+    /**
+     * Return the current LDAP error number.
+     *
+     * @return int  The current LDAP error number.
+     */
+    function _errno()
+    {
+        return $this->_errno;
+    }
+
+    /**
+     * Return the current LDAP error description.
+     *
+     * @return string  The current LDAP error description.
+     */
+    function _error()
+    {
+        return $this->_error;
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Session.php b/framework/Kolab_Server/lib/Horde/Kolab/Session.php
new file mode 100644 (file)
index 0000000..d724372
--- /dev/null
@@ -0,0 +1,384 @@
+<?php
+/**
+ * The Horde_Kolab_Session class holds additional user details for the current
+ * session.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Session.php,v 1.11 2009/02/07 14:03:32 wrobel Exp $
+ *
+ * PHP version 4
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/** We need the Auth library */
+require_once 'Horde/Auth.php';
+
+/**
+ * The Horde_Kolab_Session class holds additional user details for the current
+ * session.
+ *
+ * The core user credentials (login, pass) are kept within the Auth module and
+ * can be retrieved using <code>Auth::getAuth()</code> respectively
+ * <code>Auth::getCredential('password')</code>. Any additional Kolab user data
+ * relevant for the user session should be accessed via the Horde_Kolab_Session
+ * class.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Session.php,v 1.11 2009/02/07 14:03:32 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Session {
+
+    /**
+     * User ID.
+     *
+     * @var string
+     */
+    var $user_id;
+
+    /**
+     * User UID.
+     *
+     * @var string
+     */
+    var $user_uid;
+
+    /**
+     * Primary user mail address.
+     *
+     * @var string
+     */
+    var $user_mail;
+
+    /**
+     * Full name.
+     *
+     * @var string
+     */
+    var $user_name = '';
+
+    /**
+     * True if the Kolab_Server login was successfull.
+     *
+     * @var boolean|PEAR_Error
+     */
+    var $auth;
+
+    /**
+     * The connection parameters for the IMAP server.
+     *
+     * @var array|PEAR_Error
+     */
+    var $_imap_params;
+
+    /**
+     * Our IMAP connection.
+     *
+     * @var Horde_Kolab_IMAP
+     */
+    var $_imap;
+
+    /**
+     * The free/busy server for the current user.
+     *
+     * @var array|PEAR_Error
+     */
+    var $freebusy_server;
+
+    /**
+     * Constructor.
+     *
+     * @param string $user        The session will be setup for the user with
+     *                            this ID.
+     * @param array  $credentials An array of login credentials. For Kolab,
+     *                            this must contain a "password" entry.
+     */
+    function Horde_Kolab_Session($user = null, $credentials = null)
+    {
+        global $conf;
+
+        if (empty($user)) {
+            $user = Auth::getAuth();
+            if (empty($user)) {
+                $user = 'anonymous';
+            } else if (!strpos($user, '@')) {
+                $user = $user . '@' . (!empty($_SERVER['SERVER_NAME']) ?
+                                       $_SERVER['SERVER_NAME'] : 'localhost');
+            }
+        }
+
+        $this->user_id      = $user;
+        $this->_imap_params = array();
+
+        $user_object = null;
+
+        if ($user != 'anonymous') {
+            $server = $this->getServer($user, $credentials);
+            if (is_a($server, 'PEAR_Error')) {
+                $this->auth = $server;
+            } else {
+                $this->user_uid = $server->uid;
+                $user_object    = $server->fetch();
+
+                if (is_a($user_object, 'PEAR_Error')) {
+                    $this->auth = $user_object;
+                } else {
+                    if (empty($conf['kolab']['imap']['allow_special_users'])
+                        && !is_a($user_object, 'Horde_Kolab_Server_Object_user')) {
+                        $this->auth = PEAR::raiseError(_('Access to special Kolab users is denied.'));
+                    } else if (isset($conf['kolab']['server']['deny_group'])) {
+                        $dn = $server->gidForMail($conf['kolab']['server']['deny_group']);
+                        if (is_a($dn, 'PEAR_Error')) {
+                            $this->auth = $dn;
+                        } else if (empty($dn)) {
+                            Horde::logMessage('The Kolab configuratin setting $conf[\'kolab\'][\'server\'][\'deny_group\'] holds a non-existing group!',
+                                              __FILE__, __LINE__, PEAR_LOG_WARNING);
+                            $this->auth = true;
+                        } else if (in_array($dn, $user_object->getGroups())) {
+                            $this->auth = PEAR::raiseError(_('You are member of a group that may not login on this server.'));
+                        } else {
+                            $this->auth = true;
+                        }
+                    } else if (isset($conf['kolab']['server']['allow_group'])) {
+                        $dn = $server->gidForMail($conf['kolab']['server']['allow_group']);
+                        if (is_a($dn, 'PEAR_Error')) {
+                            $this->auth = $dn;
+                        } else if (empty($dn)) {
+                            Horde::logMessage('The Kolab configuratin setting $conf[\'kolab\'][\'server\'][\'allow_group\'] holds a non-existing group!',
+                                              __FILE__, __LINE__, PEAR_LOG_WARNING);
+                            $this->auth = true;
+                        } else if (!in_array($dn, $user_object->getGroups())) {
+                            $this->auth = PEAR::raiseError(_('You are no member of a group that may login on this server.'));
+                        } else {
+                            $this->auth = true;
+                        }
+                    } else {
+                        /**
+                         * At this point we can be certain the user is an
+                         * authenticated Kolab user.
+                         */
+                        $this->auth = true;
+                    }
+
+                    if (empty($this->auth) || is_a($this->auth, 'PEAR_Error')) {
+                        return;
+                    }
+
+                    $result = $user_object->get(KOLAB_ATTR_MAIL);
+                    if (!empty($result) && !is_a($result, 'PEAR_Error')) {
+                        $this->user_mail = $result;
+                    }
+
+                    $result = $user_object->get(KOLAB_ATTR_SID);
+                    if (!empty($result) && !is_a($result, 'PEAR_Error')) {
+                        $this->user_id = $result;
+                    }
+
+                    $result = $user_object->get(KOLAB_ATTR_FNLN);
+                    if (!empty($result) && !is_a($result, 'PEAR_Error')) {
+                        $this->user_name = $result;
+                    }
+
+                    $result = $user_object->getServer('imap');
+                    if (!empty($result) && !is_a($result, 'PEAR_Error')) {
+                        $server = explode(':', $result, 2);
+                        if (!empty($server[0])) {
+                            $this->_imap_params['hostspec'] = $server[0];
+                        }
+                        if (!empty($server[1])) {
+                            $this->_imap_params['port'] = $server[1];
+                        }
+                    }
+
+                    $result = $user_object->getServer('freebusy');
+                    if (!empty($result) && !is_a($result, 'PEAR_Error')) {
+                        $this->freebusy_server = $result;
+                    }
+                }
+            }
+        }
+
+        if (empty($this->user_mail)) {
+            $this->user_mail = $user;
+        }
+
+        if (!isset($this->_imap_params['hostspec'])) {
+            if (isset($conf['kolab']['imap']['server'])) {
+                $this->_imap_params['hostspec'] = $conf['kolab']['imap']['server'];
+            } else {
+                $this->_imap_params['hostspec'] = 'localhost';
+            }
+        }
+
+        if (!isset($this->_imap_params['port'])) {
+            if (isset($conf['kolab']['imap']['port'])) {
+                $this->_imap_params['port'] = $conf['kolab']['imap']['port'];
+            } else {
+                $this->_imap_params['port'] = 143;
+            }
+        }
+
+        $this->_imap_params['protocol'] = 'imap/notls/novalidate-cert';
+    }
+
+    /**
+     * Returns the properties that need to be serialized.
+     *
+     * @return array  List of serializable properties.
+     */
+    function __sleep()
+    {
+        $properties = get_object_vars($this);
+        unset($properties['_imap']);
+        $properties = array_keys($properties);
+        return $properties;
+    }
+
+    /**
+     * Get the Kolab Server connection.
+     *
+     * @param string $user        The session will be setup for the user with
+     *                            this ID.
+     * @param array  $credentials An array of login credentials. For Kolab,
+     *                            this must contain a "password" entry.
+     *
+     * @return Horde_Kolab_Server|PEAR_Error The Kolab Server connection.
+     */
+    function &getServer($user = null, $credentials = null)
+    {
+        /** We need the Kolab Server access. */
+        require_once 'Horde/Kolab/Server.php';
+
+        $params = array();
+        if ($this->user_uid) {
+            $params['uid']  = $this->user_uid;
+            $params['pass'] = Auth::getCredential('password');
+        } else if (isset($user)) {
+            $params['user'] = $user;
+            if (isset($credentials['password'])) {
+                $params['pass'] = $credentials['password'];
+            } else {
+                $params['pass'] = Auth::getCredential('password');
+            }
+        }
+        return Horde_Kolab_Server::singleton($params);
+    }
+
+    /**
+     * Get the IMAP connection parameters.
+     *
+     * @return array|PEAR_Error The IMAP connection parameters.
+     */
+    function &getImapParams()
+    {
+        return $this->_imap_params;
+    }
+
+    /**
+     * Create an IMAP connection.
+     *
+     * @return Horde_Kolab_IMAP|PEAR_Error The IMAP connection.
+     */
+    function &getImap()
+    {
+        if (!isset($this->_imap)) {
+
+            $params = $this->getImapParams();
+            if (is_a($params, 'PEAR_Error')) {
+                return $params;
+            }
+
+            /** We need the Kolab IMAP library now. */
+            require_once 'Horde/Kolab/IMAP.php';
+
+            $imap = &Horde_Kolab_IMAP::singleton($params['hostspec'],
+                                                 $params['port'], true, false);
+            if (is_a($imap, 'PEAR_Error')) {
+                return $imap;
+            }
+
+            $result = $imap->connect(Auth::getAuth(),
+                                     Auth::getCredential('password'));
+            if (is_a($result, 'PEAR_Error')) {
+                return $result;
+            }
+            $this->_imap = &$imap;
+        }
+        return $this->_imap;
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Horde_Kolab_Session instance.
+     *
+     * It will only create a new instance if no Horde_Kolab_Session instance
+     * currently exists or if a user ID has been specified that does not match the
+     * user ID/user mail of the current session.
+     *
+     * This method must be invoked as:
+     *   <code>$var = &Horde_Kolab_Session::singleton();</code>
+     *
+     * @param string $user        The session will be setup for the user with
+     *                            this ID.
+     * @param array  $credentials An array of login credentials. For Kolab,
+     *                            this must contain a "password" entry.
+     *
+     * @static
+     *
+     * @return Horde_Kolab_Session  The concrete Session reference.
+     */
+    function &singleton($user = null, $credentials = null, $destruct = false)
+    {
+        static $session;
+
+        if (!isset($session)) {
+            /**
+             * Horde_Kolab_Server currently has no caching so we mainly
+             * cache some user information here as reading this data
+             * may be expensive when running in a multi-host
+             * environment.
+             */
+            require_once 'Horde/SessionObjects.php';
+            $hs      = &Horde_SessionObjects::singleton();
+            $session = $hs->query('kolab_session');
+        }
+
+        if (empty($user)) {
+            $user = Auth::getAuth();
+        }
+
+        if ($destruct || empty($session)
+            || ($user != $session->user_mail && $user != $session->user_id)) {
+            $session = new Horde_Kolab_Session($user, $credentials);
+        }
+
+        register_shutdown_function(array(&$session, 'shutdown'));
+
+        return $session;
+    }
+
+    /**
+     * Stores the object in the session cache.
+     *
+     * @return NULL
+     */
+    function shutdown()
+    {
+        require_once 'Horde/SessionObjects.php';
+        $session = &Horde_SessionObjects::singleton();
+        $session->overwrite('kolab_session', $this, false);
+    }
+
+}
diff --git a/framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php b/framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php
new file mode 100644 (file)
index 0000000..d45ddd3
--- /dev/null
@@ -0,0 +1,746 @@
+<?php
+/**
+ * Base for PHPUnit scenarios.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php,v 1.3 2009/01/06 17:49:26 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Test
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Share
+ */
+
+/**
+ *  We need the unit test framework
+ */
+require_once 'PHPUnit/Framework.php';
+require_once 'PHPUnit/Extensions/Story/TestCase.php';
+
+/**
+ *  We need the classes to be tested
+ */
+require_once 'Horde.php';
+require_once 'Horde/Kolab/Server.php';
+
+/**
+ * Base for PHPUnit scenarios.
+ *
+ * $Horde: framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php,v 1.3 2009/01/06 17:49:26 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Test
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Share
+ */
+class Horde_Kolab_Test_Server extends PHPUnit_Extensions_Story_TestCase
+{
+    /**
+     * Handle a "given" step.
+     *
+     * @param array  &$world    Joined "world" of variables.
+     * @param string $action    The description of the step.
+     * @param array  $arguments Additional arguments to the step.
+     *
+     * @return mixed The outcome of the step.
+     */
+    public function runGiven(&$world, $action, $arguments)
+    {
+        switch($action) {
+        case 'an empty Kolab server':
+            $world['server'] = &$this->prepareEmptyKolabServer();
+            break;
+        case 'a basic Kolab server':
+            $world['server'] = &$this->prepareBasicKolabServer();
+            break;
+        case 'the Kolab auth driver has been selected':
+            $world['auth'] = &$this->prepareKolabAuthDriver();
+            break;
+        default:
+            return $this->notImplemented($action);
+        }
+    }
+
+    /**
+     * Handle a "when" step.
+     *
+     * @param array  &$world    Joined "world" of variables.
+     * @param string $action    The description of the step.
+     * @param array  $arguments Additional arguments to the step.
+     *
+     * @return mixed The outcome of the step.
+     */
+    public function runWhen(&$world, $action, $arguments)
+    {
+        switch($action) {
+        case 'adding a Kolab server object':
+            $world['result']['add'] = $world['server']->add($arguments[0]);
+            break;
+        case 'logging in as a user with a password':
+            $world['login'] = $world['auth']->authenticate($arguments[0],
+                                                           array('password' => $arguments[1]));
+            break;
+        case 'adding an object list':
+            foreach ($arguments[0] as $object) {
+                $result = $world['server']->add($object);
+                if (is_a($result, 'PEAR_Error')) {
+                    $world['result']['add'] = $result;
+                    return;
+                }
+            }
+            $world['result']['add'] = true;
+            break;
+        case 'adding a user without first name':
+            $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutGivenName());
+            break;
+        case 'adding a user without last name':
+            $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutLastName());
+            break;
+        case 'adding a user without password':
+            $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutPassword());
+            break;
+        case 'adding a user without primary mail':
+            $world['result']['add'] = $world['server']->add($this->provideInvalidUserWithoutMail());
+            break;
+        case 'adding a distribution list':
+            $world['result']['add'] = $world['server']->add($this->provideDistributionList());
+            break;
+        case 'listing all users':
+            $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_USER);
+            break;
+        case 'listing all groups':
+            $world['list'] = $world['server']->listObjects(KOLAB_OBJECT_GROUP);
+            break;
+        case 'listing all objects of type':
+            $world['list'] = $world['server']->listObjects($arguments[0]);
+            break;
+        case 'retrieving a hash list with all objects of type':
+            $world['list'] = $world['server']->listHash($arguments[0]);
+            break;
+        default:
+            return $this->notImplemented($action);
+        }
+    }
+
+    /**
+     * Handle a "then" step.
+     *
+     * @param array  &$world    Joined "world" of variables.
+     * @param string $action    The description of the step.
+     * @param array  $arguments Additional arguments to the step.
+     *
+     * @return mixed The outcome of the step.
+     */
+    public function runThen(&$world, $action, $arguments)
+    {
+        switch($action) {
+        case 'the result should be an object of type':
+            if (!isset($world['result'])) {
+                $this->fail('Did not receive a result!');
+            }
+            foreach ($world['result'] as $result) { 
+                if ($result instanceOf PEAR_Error) {
+                    $this->assertEquals('', $result->getMessage());
+                } else {
+                    $this->assertEquals($arguments[0], get_class($result));
+                }
+            }
+            break;
+        case 'the result indicates success.':
+            if (!isset($world['result'])) {
+                $this->fail('Did not receive a result!');
+            }
+            foreach ($world['result'] as $result) { 
+                if ($result instanceOf PEAR_Error) {
+                    $this->assertEquals('', $result->getMessage());
+                } else {
+                    $this->assertTrue($result);
+                }
+            }
+            break;
+        case 'the result should indicate an error with':
+            if (!isset($world['result'])) {
+                $this->fail('Did not receive a result!');
+            }
+            foreach ($world['result'] as $result) { 
+                if ($result instanceOf PEAR_Error) {
+                    $this->assertEquals($arguments[0], $result->getMessage());
+                } else {
+                    $this->assertEquals($arguments[0], 'Action succeeded without an error.');
+                }
+            }
+            break;
+        case 'the list has a number of entries equal to':
+            if ($world['list'] instanceOf PEAR_Error) {
+                $this->assertEquals('', $world['list']->getMessage());
+            } else {
+                $this->assertEquals($arguments[0], count($world['list']));
+            }
+            break;
+        case 'the list is an empty array':
+            if ($world['list'] instanceOf PEAR_Error) {
+                $this->assertEquals('', $world['list']->getMessage());
+            } else {
+                $this->assertEquals(array(), $world['list']);
+            }
+            break;
+        case 'the list is an empty array':
+            if ($world['list'] instanceOf PEAR_Error) {
+                $this->assertEquals('', $world['list']->getMessage());
+            } else {
+                $this->assertEquals(array(), $world['list']);
+            }
+            break;
+        case 'the provided list and the result list match with regard to these attributes':
+            if ($world['list'] instanceOf PEAR_Error) {
+                $this->assertEquals('', $world['list']->getMessage());
+            } else {
+                $provided_vals = array();
+                foreach ($arguments[2] as $provided_element) {
+                    if (isset($provided_element[$arguments[0]])) {
+                        $provided_vals[] = $provided_element[$arguments[0]];
+                    } else {
+                        $this->fail(sprintf('The provided element %s does have no value for %s.',
+                                            print_r($provided_element, true),
+                                            print_r($arguments[0])));
+                    }
+                }
+                $result_vals = array();
+                foreach ($world['list'] as $result_element) {
+                    if (isset($result_element[$arguments[1]])) {
+                        $result_vals[] = $result_element[$arguments[1]];
+                    } else {
+                        $this->fail(sprintf('The result element %s does have no value for %s.',
+                                            print_r($result_element, true),
+                                            print_r($arguments[1])));
+                    }
+                }
+                $this->assertEquals(array(),
+                                    array_diff($provided_vals, $result_vals));
+            }
+            break;
+        case 'each element in the result list has an attribute':
+            if ($world['list'] instanceOf PEAR_Error) {
+                $this->assertEquals('', $world['list']->getMessage());
+            } else {
+                $result_vals = array();
+                foreach ($world['list'] as $result_element) {
+                    if (!isset($result_element[$arguments[0]])) {
+                        $this->fail(sprintf('The result element %s does have no value for %s.',
+                                            print_r($result_element, true),
+                                            print_r($arguments[0])));
+                    }
+                }
+            }
+            break;
+        case 'each element in the result list has an attribute set to a given value':
+            if ($world['list'] instanceOf PEAR_Error) {
+                $this->assertEquals('', $world['list']->getMessage());
+            } else {
+                $result_vals = array();
+                foreach ($world['list'] as $result_element) {
+                    if (!isset($result_element[$arguments[0]])) {
+                        $this->fail(sprintf('The result element %s does have no value for %s.',
+                                            print_r($result_element, true),
+                                            print_r($arguments[0], true)));
+                    }
+                    if ($result_element[$arguments[0]] != $arguments[1]) {
+                        $this->fail(sprintf('The result element %s has an unexpected value %s for %s.',
+                                            print_r($result_element, true),
+                                            print_r($result_element[$arguments[0]], true),
+                                            print_r($arguments[0], true)));
+                    }
+                }
+            }
+            break;
+        case 'the login was successful':
+            $this->assertNoError($world['login']);
+            $this->assertTrue($world['login']);
+            break;
+        case 'the list contains a number of elements equal to':
+            $this->assertEquals($arguments[0], count($world['list']));
+            break;
+        default:
+            return $this->notImplemented($action);
+        }
+    }
+
+    
+    /**
+     * Prepare an empty Kolab server.
+     *
+     * @return Horde_Kolab_Server The empty server.
+     */
+    public function &prepareEmptyKolabServer()
+    {
+        global $conf;
+
+        include_once 'Horde/Kolab/Server.php';
+
+        $GLOBALS['KOLAB_SERVER_TEST_DATA'] = array();
+
+        /** Prepare a Kolab test server */
+        $conf['kolab']['server']['driver'] = 'test';
+
+        $server = Horde_Kolab_Server::singleton();
+
+        /** Ensure we don't use a connection from older tests */
+        $server->unbind();
+
+        /** Set base DN */
+        $server->_base_dn = 'dc=example,dc=org';
+
+        /** Clean the server data */
+
+        return $server;
+    }
+
+    /**
+     * Prepare a Kolab server with some basic entries.
+     *
+     * @return Horde_Kolab_Server The empty server.
+     */
+    public function &prepareBasicServer()
+    {
+        $server = $this->prepareEmptyKolabServer();
+        $this->prepareUsers($server);
+        return $server;
+    }
+
+    /**
+     * Fill a Kolab Server with test users.
+     *
+     * @param Kolab_Server &$server The server to populate.
+     *
+     * @return Horde_Kolab_Server The empty server.
+     */
+    public function prepareUsers(&$server)
+    {
+        $result = $server->add($this->provideBasicUserOne());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicUserTwo());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicAddress());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicAdmin());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicDomainMaintainer());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicGroupOne());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicGroupTwo());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicMaintainer());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicSharedFolder());
+        $this->assertNoError($result);
+    }
+
+    /**
+     * Prepare a Kolab Auth Driver.
+     *
+     * @return Auth The auth driver.
+     */
+    public function &prepareKolabAuthDriver()
+    {
+        include_once 'Horde/Auth.php';
+
+        $auth = Auth::singleton('kolab');
+        return $auth;
+    }
+
+    /**
+     * Return a test user.
+     *
+     * @return array The test user.
+     */
+    public function provideBasicUserOne()
+    {
+        return array('givenName' => 'Gunnar',
+                      'sn' => 'Wrobel',
+                      'type' => KOLAB_OBJECT_USER,
+                      'mail' => 'wrobel@example.org',
+                      'uid' => 'wrobel',
+                      'userPassword' => 'none',
+                      'kolabHomeServer' => 'home.example.org',
+                      'kolabImapServer' => 'imap.example.org',
+                      'kolabFreeBusyServer' => 'https://fb.example.org/freebusy',
+                      KOLAB_ATTR_IPOLICY => array('ACT_REJECT_IF_CONFLICTS'),
+                      'alias' => array('gunnar@example.org',
+                                       'g.wrobel@example.org'),
+                );
+    }
+
+    /**
+     * Return a test user.
+     *
+     * @return array The test user.
+     */
+    public function provideBasicUserTwo()
+    {
+        return array('givenName' => 'Test',
+                     'sn' => 'Test',
+                     'type' => KOLAB_OBJECT_USER,
+                     'mail' => 'test@example.org',
+                     'uid' => 'test',
+                     'userPassword' => 'test',
+                     'kolabHomeServer' => 'home.example.org',
+                     'kolabImapServer' => 'home.example.org',
+                     'kolabFreeBusyServer' => 'https://fb.example.org/freebusy',
+                     'alias' => array('t.test@example.org'),
+                     KOLAB_ATTR_KOLABDELEGATE => 'wrobel@example.org',);
+    }
+
+    /**
+     * Return a test address.
+     *
+     * @return array The test address.
+     */
+    public function provideBasicAddress()
+    {
+        return array('givenName' => 'Test',
+                     'sn' => 'Address',
+                     'type' => KOLAB_OBJECT_ADDRESS,
+                     'mail' => 'address@example.org');
+    }
+
+    /**
+     * Return a test administrator.
+     *
+     * @return array The test administrator.
+     */
+    public function provideBasicAdmin()
+    {
+        return array('sn' => 'Administrator',
+                     'givenName' => 'The',
+                     'uid' => 'admin',
+                     'type' => KOLAB_OBJECT_ADMINISTRATOR,
+                     'userPassword' => 'none');
+    }
+
+    /**
+     * Return a test maintainer.
+     *
+     * @return array The test maintainer.
+     */
+    public function provideBasicMaintainer()
+    {
+        return array('sn' => 'Tainer',
+                     'givenName' => 'Main',
+                     'uid' => 'maintainer',
+                     'type' => KOLAB_OBJECT_MAINTAINER,
+                     'userPassword' => 'none',
+        );
+    }
+
+    /**
+     * Return a test domain maintainer.
+     *
+     * @return array The test domain maintainer.
+     */
+    public function provideBasicDomainMaintainer()
+    {
+        return array('sn' => 'Maintainer',
+                     'givenName' => 'Domain',
+                     'uid' => 'domainmaintainer',
+                     'type' => KOLAB_OBJECT_DOMAINMAINTAINER,
+                     'userPassword' => 'none',
+                     'domain' => array('example.com'),
+        );
+    }
+
+    /**
+     * Return a test shared folder.
+     *
+     * @return array The test shared folder.
+     */
+    public function provideBasicSharedFolder()
+    {
+        return array('cn' => 'shared@example.org',
+                     'kolabHomeServer' => 'example.org',
+                     'type' => KOLAB_OBJECT_SHAREDFOLDER);
+    }
+
+    /**
+     * Return a test group.
+     *
+     * @return array The test group.
+     */
+    public function provideBasicGroupOne()
+    {
+        return array('mail' => 'empty.group@example.org',
+                     'type' => KOLAB_OBJECT_GROUP);
+    }
+
+    /**
+     * Return a test group.
+     *
+     * @return array The test group.
+     */
+    public function provideBasicGroupTwo()
+    {
+        return array('mail' => 'group@example.org',
+                     'type' => KOLAB_OBJECT_GROUP,
+                     'member' => array('cn=Test Test,dc=example,dc=org',
+                                       'cn=Gunnar Wrobel,dc=example,dc=org'));
+    }
+
+    public function provideDistributionList()
+    {
+        return array('mail' => 'distlist@example.org',
+                     'type' => KOLAB_OBJECT_DISTLIST,
+                     'member' => array('cn=Test Test,dc=example,dc=org',
+                                       'cn=Gunnar Wrobel,dc=example,dc=org'));
+    }
+
+    public function provideInvalidUserWithoutPassword()
+    {
+        return array('givenName' => 'Test',
+                     'sn' => 'Test',
+                     'type' => KOLAB_OBJECT_USER,
+                     'mail' => 'test@example.org');
+    }
+
+    public function provideInvalidUserWithoutGivenName()
+    {
+        return array('sn' => 'Test',
+                     'userPassword' => 'none',
+                     'type' => KOLAB_OBJECT_USER,
+                     'mail' => 'test@example.org');
+    }
+
+    public function provideInvalidUserWithoutLastName()
+    {
+        return array('givenName' => 'Test',
+                     'userPassword' => 'none',
+                     'type' => KOLAB_OBJECT_USER,
+                     'mail' => 'test@example.org');
+    }
+
+    public function provideInvalidUserWithoutMail()
+    {
+        return array('givenName' => 'Test',
+                     'sn' => 'Test',
+                     'userPassword' => 'none',
+                     'type' => KOLAB_OBJECT_USER);
+    }
+
+    public function provideInvalidUsers()
+    {
+        return array(
+            array(
+                $this->provideInvalidUserWithoutPassword(),
+                'Adding object failed: The value for "userPassword" is missing!'
+            ),
+            array(
+                $this->provideInvalidUserWithoutGivenName(),
+                'Adding object failed: Either the last name or the given name is missing!'
+            ),
+            array(
+                $this->provideInvalidUserWithoutLastName(),
+                'Adding object failed: Either the last name or the given name is missing!'
+            ),
+            array(
+                $this->provideInvalidUserWithoutMail(),
+                'Adding object failed: The value for "mail" is missing!'
+            ),
+        );
+    }
+
+    /** FIXME: Prefix the stuff bewlow with provide...() */
+
+    public function validUsers()
+    {
+        return array(
+            array(
+                $this->provideBasicUserOne(),
+            ),
+            array(
+                $this->provideBasicUserTwo(),
+            ),
+        );
+    }
+
+    public function validAddresses()
+    {
+        return array(
+            array(
+                $this->provideBasicAddress(),
+            ),
+        );
+    }
+
+    public function validAdmins()
+    {
+        return array(
+            array(
+                $this->provideBasicAdmin(),
+            ),
+        );
+    }
+
+    public function validMaintainers()
+    {
+        return array(
+            array(
+                $this->provideBasicMaintainer(),
+            )
+        );
+    }
+
+    public function validDomainMaintainers()
+    {
+        return array(
+            array(
+                $this->provideBasicDomainMaintainer(),
+            )
+        );
+    }
+
+    public function validGroups()
+    {
+        return array(
+            array(
+                $this->validGroupWithoutMembers(),
+            ),
+            array(
+                array('mail' => 'group@example.org',
+                      'type' => KOLAB_OBJECT_GROUP,
+                      'member' => array('cn=Test Test,dc=example,dc=org',
+                                        'cn=Gunnar Wrobel,dc=example,dc=org')
+                ),
+            ),
+            array(
+                array('mail' => 'group2@example.org',
+                      'type' => KOLAB_OBJECT_GROUP,
+                      'member' => array('cn=Gunnar Wrobel,dc=example,dc=org')
+                ),
+            ),
+        );
+    }
+
+    public function validSharedFolders()
+    {
+        return array(
+            array('cn' => 'Shared',
+                  'type' => KOLAB_OBJECT_SHAREDFOLDER
+            ),
+        );
+    }
+
+    public function validGroupWithoutMembers()
+    {
+        return array('mail' => 'empty.group@example.org',
+                     'type' => KOLAB_OBJECT_GROUP,
+        );
+    }
+
+    public function userLists()
+    {
+        return array(
+        );
+    }
+
+    public function groupLists()
+    {
+        return array(
+            array(
+                array(
+                    array('type' => KOLAB_OBJECT_GROUP,
+                          'mail' => 'empty.group@example.org',
+                    ),
+                )
+            ),
+            array(
+                array(
+                    array('mail' => 'empty.group@example.org',
+                          'type' => KOLAB_OBJECT_GROUP,
+                    ),
+                ),
+                array(
+                    array('mail' => 'group@example.org',
+                          'type' => KOLAB_OBJECT_GROUP,
+                          'member' => array('cn=Test Test,dc=example,dc=org',
+                                            'cn=Gunnar Wrobel,dc=example,dc=org')
+                    ),
+                ),
+                array(
+                    array('mail' => 'group2@example.org',
+                          'type' => KOLAB_OBJECT_GROUP,
+                          'member' => array('cn=Gunnar Wrobel,dc=example,dc=org')
+                    ),
+                ),
+            )
+        );
+    }
+
+    public function userListByLetter()
+    {
+        return array(
+        );
+    }
+
+    public function userListByAttribute()
+    {
+        return array(
+        );
+    }
+
+    public function userAdd()
+    {
+        return array(
+        );
+    }
+
+    public function invalidMails()
+    {
+        return array(
+        );
+    }
+
+    public function largeList()
+    {
+        return array(
+        );
+    }
+
+    /**
+     * Ensure that the variable contains no PEAR_Error and fail if it does.
+     *
+     * @param mixed $var The variable to check.
+     *
+     * @return NULL.
+     */
+    public function assertNoError($var)
+    {
+        if (is_a($var, 'PEAR_Error')) {
+            $this->assertEquals('', $var->getMessage());
+        }
+    }
+
+    /**
+     * Ensure that the variable contains a PEAR_Error and fail if it does
+     * not. Optionally compare the error message with the provided message and
+     * fail if both do not match.
+     *
+     * @param mixed  $var The variable to check.
+     * @param string $msg The expected error message.
+     *
+     * @return NULL.
+     */
+    public function assertError($var, $msg = null)
+    {
+        $this->assertEquals('PEAR_Error', get_class($var));
+        if (isset($msg)) {
+            $this->assertEquals($msg, $var->getMessage());
+        }
+    }
+}
diff --git a/framework/Kolab_Server/package.xml b/framework/Kolab_Server/package.xml
new file mode 100644 (file)
index 0000000..4ce4ea7
--- /dev/null
@@ -0,0 +1,255 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Kolab_Server</name>
+ <channel>pear.horde.org</channel>
+ <summary>A package for manipulating the Kolab user database.</summary>
+ <description>This package allows to read/write entries in the Kolab user
+ database stored in LDAP.
+ </description>
+ <lead>
+  <name>Gunnar Wrobel</name>
+  <user>wrobel</user>
+  <email>p@rdus.de</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Thomas Jarosch</name>
+  <user>jarosch</user>
+  <email>thomas.jarosch@intra2net.com</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Jan Schneider</name>
+  <user>jan</user>
+  <email>jan@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-12-16</date>
+ <version>
+  <release>0.4.0</release>
+  <api>0.2.0</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>
+  * Fixed objectClass evaluation to respect case-insensitivity (Bug: #7694)
+  * kolab/issue2207 (Make it possible to enable and disable users to be able to
+    use the webclient)
+  * Added fullname attribute as support for kolab/issue2546 (Horde
+    should use name and email from ldap as defaults)
+  * Fixed initialization of parameters retrieved from LDAP.
+ </notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Kolab">
+      <file name="IMAP.php" role="php" />
+      <dir name="IMAP">
+       <file name="cclient.php" role="php" />
+       <file name="pear.php" role="php" />
+       <file name="test.php" role="php" />
+      </dir> <!-- /lib/Horde/Kolab/IMAP -->
+      <file name="Server.php" role="php" />
+      <file name="Session.php" role="php" />
+      <dir name="Server">
+       <file name="ldap.php" role="php" />
+       <file name="Object.php" role="php" />
+       <file name="test.php" role="php" />
+       <dir name="Object">
+        <file name="address.php" role="php" />
+        <file name="administrator.php" role="php" />
+        <file name="adminrole.php" role="php" />
+        <file name="distlist.php" role="php" />
+        <file name="domainmaintainer.php" role="php" />
+        <file name="group.php" role="php" />
+        <file name="maintainer.php" role="php" />
+        <file name="server.php" role="php" />
+        <file name="sharedfolder.php" role="php" />
+        <file name="user.php" role="php" />
+       </dir> <!-- /lib/Horde/Kolab/Server/Object -->
+      </dir> <!-- /lib/Horde/Kolab/Server -->
+      <dir name="Test">
+       <file name="Server.php" role="php" />
+      </dir> <!-- /lib/Horde/Kolab/Test -->
+     </dir> <!-- /lib/Horde/Kolab -->
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+   <dir name="test">
+    <dir name="Horde">
+     <dir name="Kolab">
+      <dir name="Server">
+       <file name="AddingObjectsTest.php" role="test" />
+       <file name="AdminTest.php" role="test" />
+       <file name="AllTests.php" role="test" />
+       <file name="DistListHandlingTest.php" role="test" />
+       <file name="GroupHandlingTest.php" role="test" />
+       <file name="GroupTest.php" role="test" />
+       <file name="ldapTest.php" role="test" />
+       <file name="ObjectTest.php" role="test" />
+       <file name="ServerTest.php" role="test" />
+       <file name="SessionTest.php" role="test" />
+       <file name="testTest.php" role="test" />
+       <file name="UserHandlingTest.php" role="test" />
+       <file name="UserTest.php" role="test" />
+      </dir> <!-- /test/Horde/Kolab/Server -->
+     </dir> <!-- /test/Horde/Kolab -->
+    </dir> <!-- /test/Horde -->
+   </dir> <!-- /test -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>4.3.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.4.0b1</min>
+   </pearinstaller>
+   <package>
+    <name>Auth</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </required>
+  <optional>
+   <package>
+    <name>Horde_LDAP</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_SessionObjects</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>PHPUnit</name>
+    <channel>pear.phpunit.de</channel>
+   </package>
+   <extension>
+    <name>ldap</name>
+   </extension>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Kolab/IMAP.php" as="Horde/Kolab/IMAP.php" />
+   <install name="lib/Horde/Kolab/IMAP/cclient.php" as="Horde/Kolab/IMAP/cclient.php" />
+   <install name="lib/Horde/Kolab/IMAP/pear.php" as="Horde/Kolab/IMAP/pear.php" />
+   <install name="lib/Horde/Kolab/IMAP/test.php" as="Horde/Kolab/IMAP/test.php" />
+   <install name="lib/Horde/Kolab/Server.php" as="Horde/Kolab/Server.php" />
+   <install name="lib/Horde/Kolab/Session.php" as="Horde/Kolab/Session.php" />
+   <install name="lib/Horde/Kolab/Test/Server.php" as="Horde/Kolab/Test/Server.php" />
+   <install name="lib/Horde/Kolab/Server/ldap.php" as="Horde/Kolab/Server/ldap.php" />
+   <install name="lib/Horde/Kolab/Server/test.php" as="Horde/Kolab/Server/test.php" />
+   <install name="lib/Horde/Kolab/Server/Object.php" as="Horde/Kolab/Server/Object.php" />
+   <install name="lib/Horde/Kolab/Server/Object/address.php" as="Horde/Kolab/Server/Object/address.php" />
+   <install name="lib/Horde/Kolab/Server/Object/administrator.php" as="Horde/Kolab/Server/Object/administrator.php" />
+   <install name="lib/Horde/Kolab/Server/Object/adminrole.php" as="Horde/Kolab/Server/Object/adminrole.php" />
+   <install name="lib/Horde/Kolab/Server/Object/distlist.php" as="Horde/Kolab/Server/Object/distlist.php" />
+   <install name="lib/Horde/Kolab/Server/Object/domainmaintainer.php" as="Horde/Kolab/Server/Object/domainmaintainer.php" />
+   <install name="lib/Horde/Kolab/Server/Object/group.php" as="Horde/Kolab/Server/Object/group.php" />
+   <install name="lib/Horde/Kolab/Server/Object/maintainer.php" as="Horde/Kolab/Server/Object/maintainer.php" />
+   <install name="lib/Horde/Kolab/Server/Object/server.php" as="Horde/Kolab/Server/Object/server.php" />
+   <install name="lib/Horde/Kolab/Server/Object/sharedfolder.php" as="Horde/Kolab/Server/Object/sharedfolder.php" />
+   <install name="lib/Horde/Kolab/Server/Object/user.php" as="Horde/Kolab/Server/Object/user.php" />
+   <install name="test/Horde/Kolab/Server/AddingObjectsTest.php" as="Horde/Kolab/Server/AddingObjectsTest.php" />
+   <install name="test/Horde/Kolab/Server/AdminTest.php" as="Horde/Kolab/Server/AdminTest.php" />
+   <install name="test/Horde/Kolab/Server/AllTests.php" as="Horde/Kolab/Server/AllTests.php" />
+   <install name="test/Horde/Kolab/Server/DistListHandlingTest.php" as="Horde/Kolab/Server/DistListHandlingTest.php" />
+   <install name="test/Horde/Kolab/Server/GroupHandlingTest.php" as="Horde/Kolab/Server/GroupHandlingTest.php" />
+   <install name="test/Horde/Kolab/Server/GroupTest.php" as="Horde/Kolab/Server/GroupTest.php" />
+   <install name="test/Horde/Kolab/Server/ldapTest.php" as="Horde/Kolab/Server/ldapTest.php" />
+   <install name="test/Horde/Kolab/Server/ObjectTest.php" as="Horde/Kolab/Server/ObjectTest.php" />
+   <install name="test/Horde/Kolab/Server/ServerTest.php" as="Horde/Kolab/Server/ServerTest.php" />
+   <install name="test/Horde/Kolab/Server/SessionTest.php" as="Horde/Kolab/Server/SessionTest.php" />
+   <install name="test/Horde/Kolab/Server/testTest.php" as="Horde/Kolab/Server/testTest.php" />
+   <install name="test/Horde/Kolab/Server/UserHandlingTest.php" as="Horde/Kolab/Server/UserHandlingTest.php" />
+   <install name="test/Horde/Kolab/Server/UserTest.php" as="Horde/Kolab/Server/UserTest.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <date>2008-12-16</date>
+   <version>
+    <release>0.3.0</release>
+    <api>0.2.0</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>
+    * Fixed the fallback for a missing freebusy_server value.
+    * Fixed identification of external addresses.
+   </notes>
+  </release>
+  <release>
+   <date>2008-10-29</date>
+   <version>
+    <release>0.2.0</release>
+    <api>0.2.0</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>
+     * Fixed retrieval of the users IMAP home server.
+     * Allowed retrieving a DN for an id matching either mail, uid or alias.
+       (Kolab issue 2587, https://www.intevation.de/roundup/kolab/issue2587)
+     * Moved Kolab session handler from Kolab_Storage to Kolab_Server.
+     * Enabled retrieval of the users free/busy server. (Enhancement: #6699)
+     * Added capability to list objects.
+     * Added write capabilities to the package.
+     * Moved the IMAP drivers from Kolab_Storage to Kolab_Server as the
+       IMAP connection must be handled by the Kolab session.
+     * Added a test class for simplified PHPUnit testing.
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.1.1</release>
+    <api>0.1.0</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>
+     * Renamed package to Kolab_Server.
+     * Removed an unnecessary translation.
+     * Added dnForMailOrAlias function to Horde_Kolab_Server.
+     * Fixed experimental KOLAB_ATTR_IMAPHOST attribute.
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.1.0</release>
+    <api>0.1.0</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2008-07-29</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>
+     * Initial release.
+   </notes>
+  </release>
+ </changelog>
+</package>
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php
new file mode 100644 (file)
index 0000000..2a9f60b
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Adding objects to the server.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * Adding objects to the server.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AddingObjectsTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_AddingObjectsTest extends Horde_Kolab_Test_Server {
+    /**
+     * Test adding valid users.
+     *
+     * @param array $user The user to add.
+     *
+     * @scenario
+     * @dataProvider validUsers
+     *
+     * @return NULL
+     */
+    public function addingValidUser($user)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a Kolab server object', $user)
+            ->then('the result should be an object of type', KOLAB_OBJECT_USER);
+    }
+
+    /**
+     * Test adding invalid users.
+     *
+     * @param array  $user  The user to add.
+     * @param string $error The error to expect.
+     *
+     * @scenario
+     * @dataProvider provideInvalidUsers
+     *
+     * @return NULL
+     */
+    public function addingInvalidUser($user, $error)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a Kolab server object', $user)
+            ->then('the result should indicate an error with', $error);
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php
new file mode 100644 (file)
index 0000000..cf730f2
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Test the admin object.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * Test the admin object.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AdminTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_AdminTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Set up testing.
+     *
+     * @return NULL
+     */
+    protected function setUp()
+    {
+        $this->ldap = $this->prepareEmptyKolabServer();
+    }
+
+    /**
+     * Add an administrator object.
+     *
+     * @return NULL
+     */
+    private function _addValidAdmin()
+    {
+        $admin  = $this->provideBasicAdmin();
+        $result = $this->ldap->add($admin);
+        $this->assertNoError($result);
+    }
+
+    /**
+     * Test ID generation for an admin.
+     *
+     * @return NULL
+     */
+    public function testGenerateId()
+    {
+        $admin = $this->provideBasicAdmin();
+        $this->assertNoError($admin);
+        $uid = $this->ldap->generateUid(KOLAB_OBJECT_ADMINISTRATOR, $admin);
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=The Administrator,dc=example,dc=org', $uid);
+    }
+
+    /**
+     * Test fetching an admin.
+     *
+     * @return NULL
+     */
+    public function testFetchAdmin()
+    {
+        $this->_addValidAdmin();
+
+        $this->assertEquals(2, count($GLOBALS['KOLAB_SERVER_TEST_DATA']));
+        $this->assertContains('cn=admin,cn=internal,dc=example,dc=org',
+                              array_keys($GLOBALS['KOLAB_SERVER_TEST_DATA']));
+
+        $administrators = $this->ldap->getGroups('cn=The Administrator,dc=example,dc=org');
+        $this->assertNoError($administrators);
+
+        $admin_group = $this->ldap->fetch('cn=admin,cn=internal,dc=example,dc=org');
+        $this->assertNoError($admin_group);
+        $this->assertTrue($admin_group->exists());
+
+        $admin = $this->ldap->fetch('cn=The Administrator,dc=example,dc=org');
+        $this->assertNoError($admin);
+        $this->assertEquals('Horde_Kolab_Server_Object_administrator',
+                            get_class($admin));
+    }
+
+    /**
+     * Test listing the admins.
+     *
+     * @return NULL
+     */
+    public function testToHash()
+    {
+        $this->_addValidAdmin();
+
+        $admin = $this->ldap->fetch('cn=The Administrator,dc=example,dc=org');
+        $this->assertNoError($admin);
+
+        $hash = $admin->toHash();
+        $this->assertNoError($hash);
+        $this->assertContains('uid', array_keys($hash));
+        $this->assertContains('lnfn', array_keys($hash));
+        $this->assertEquals('admin', $hash['uid']);
+    }
+
+    /**
+     * Test listing admins.
+     *
+     * @return NULL
+     */
+    public function testListingGroups()
+    {
+        $this->_addValidAdmin();
+
+        $entries = $this->ldap->_search('(&(cn=*)(objectClass=inetOrgPerson)(!(uid=manager))(sn=*))');
+        $this->assertNoError($entries);
+        $this->assertEquals(1, count($entries));
+
+        $list = $this->ldap->listObjects(KOLAB_OBJECT_ADMINISTRATOR);
+        $this->assertNoError($list);
+        $this->assertEquals(1, count($list));
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php b/framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php
new file mode 100644 (file)
index 0000000..2463bbb
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * All tests for the Horde_Kolab_Server:: package.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php,v 1.5 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ * Define the main method 
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Kolab_Server_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+/**
+ * Combine the tests for this package.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/AllTests.php,v 1.5 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_AllTests {
+
+    /**
+     * Main entry point for running the suite.
+     *
+     * @return NULL
+     */
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    /**
+     * Collect the unit tests of this directory into a new suite.
+     *
+     * @return PHPUnit_Framework_TestSuite The test suite.
+     */
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Kolab_Server');
+
+        $basedir    = dirname(__FILE__);
+        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+                $pathname = $file->getPathname();
+                require $pathname;
+
+                $class = str_replace(DIRECTORY_SEPARATOR, '_',
+                                     preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+                $suite->addTestSuite('Horde_Kolab_Server_' . $class);
+            }
+        }
+
+        return $suite;
+    }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Kolab_Server_AllTests::main') {
+    Horde_Kolab_Server_AllTests::main();
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php
new file mode 100644 (file)
index 0000000..6f3611c
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Handling distribution lists.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+require_once 'Horde/Kolab/Server.php';
+
+/**
+ * Handling distribution lists.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/DistListHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_DistListHandlingTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Test adding a distribution list.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function creatingDistributionList()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a distribution list')
+            ->then('the result should be an object of type',
+                   KOLAB_OBJECT_DISTLIST);
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php
new file mode 100644 (file)
index 0000000..ae959cb
--- /dev/null
@@ -0,0 +1,456 @@
+<?php
+/**
+ * Handling groups.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+require_once 'Horde/Kolab/Server.php';
+
+/**
+ * Handling groups.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/GroupHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_GroupHandlingTest extends Horde_Kolab_Test_Server {
+    /**
+     * Test listing groups if there are no groups.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function listingGroupsOnEmptyServer()
+    {
+        $this->given('an empty Kolab server')
+            ->when('retrieving a hash list with all objects of type',
+                   KOLAB_OBJECT_GROUP)
+            ->then('the list is an empty array');
+    }
+
+    /**
+     * Test listing groups after adding some groups.
+     *
+     * @param array $group_list The groups to add.
+     *
+     * @scenario
+     * @dataProvider groupLists
+     *
+     * @return NULL
+     */
+    public function listingGroups($group_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object list', $group_list)
+            ->and('retrieving a hash list with all objects of type',
+                  KOLAB_OBJECT_GROUP)
+            ->then('the result indicates success.')
+            ->and('the list has a number of entries equal to',
+                  count($group_list));
+    }
+
+    /**
+     * Test the list of groups for the group id.
+     *
+     * @param array $group_list The groups to add.
+     *
+     * @scenario
+     * @dataProvider groupLists
+     *
+     * @return NULL
+     */
+    public function listingGroupsHasAttributeId($group_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object list', $group_list)
+            ->and('retrieving a hash list with all objects of type',
+                  KOLAB_OBJECT_GROUP)
+            ->then('the provided list and the result list match with regard to these attributes',
+                   'mail', 'id', $group_list);
+    }
+
+    /**
+     * Test the list of groups for the group mail address.
+     *
+     * @param array $group_list The groups to add.
+     *
+     * @scenario
+     * @dataProvider groupLists
+     *
+     * @return NULL
+     */
+    public function listingGroupsHasAttributeMail($group_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object list', $group_list)
+            ->and('retrieving a hash list with all objects of type',
+                  KOLAB_OBJECT_GROUP)
+            ->then('the provided list and the result list match with regard to these attributes',
+                   'mail', 'mail', $group_list);
+    }
+
+    /**
+     * Test the list of groups for the group visibility.
+     *
+     * @param array $group_list The groups to add.
+     *
+     * @scenario
+     * @dataProvider groupLists
+     *
+     * @return NULL
+     */
+    public function listingGroupsHasAttributeVisibility($group_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object list', $group_list)
+            ->and('retrieving a hash list with all objects of type',
+                  KOLAB_OBJECT_GROUP)
+            ->then('each element in the result list has an attribute',
+                   'visible');
+    }
+
+    /**
+     * Test adding an invalid group.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function creatingGroupsWithoutMailAddressFails()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group without a mail address')
+            ->then('the result should indicate an error with',
+                   'Adding object failed: The value for "mail" is missing!');
+    }
+
+    /**
+     * Test adding a group without setting the visibility.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function creatingGroupWithoutVisibilityCreatesVisibleGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object', $this->validGroupWithoutMembers())
+            ->and('retrieving a hash list with all objects of type',
+                  KOLAB_OBJECT_GROUP)
+            ->then('each element in the result list has an attribute set to a given value',
+                   'visible', true);
+    }
+
+    /**
+     * Test modifying a group mail address.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function modifyingGroupMailAddressIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "test@example.org"')
+            ->and('modifying the mail address to "new@example.org"')
+            ->then('the result should indicate an error with',
+                   'The group cannot be modified: Changing the mail address from "test@example.org" to "new@example.org" is not allowed!');
+    }
+
+    /**
+     * Test modifying a group mail address.
+     *
+     * @scenario
+     */
+    public function conflictBetweenGroupMailAndUserMailIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "test@example.org"')
+            ->and('adding a user "Test Test" with the mail address "test@example.org"')
+            ->then('the result should indicate an error with',
+                   'The user cannot be added: Mail address "test@example.org" is already the mail address for the group "test@example.org"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenUserMailAndGroupMailIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the mail address "test@example.org"')
+            ->and('adding a group with the mail address "test@example.org"')
+            ->then('the result should indicate an error with',
+                   'The group cannot be added: Mail address "test@example.org" is already the mail address of the user "Test Test"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenGroupMailAndUserAliasIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "test@example.org"')
+            ->and('adding a user with the alias address "test@example.org"')
+            ->then('the result should indicate an error with',
+                   'The user cannot be added: Alias address "test@example.org" is already the mail address of the group "test@example.org"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenUserAliasAndGroupMailIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the alias address "test@example.org"')
+            ->and('adding a group with the mail address "test@example.org"')
+            ->then('the result should indicate an error with',
+                   'The group cannot be added: Mail address "test@example.org" is already the alias address of the user "Test Test"!');
+    }
+
+    /**
+     *  kolab/issue890 (Assigning multiple Distribution Lists to user during creation and modification)
+     *
+     * @scenario
+     */
+    public function showGroupsWhenFetchingTheUser()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org"')
+            ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"')
+            ->and('fetching the user "test@example.org"')
+            ->and('listing the groups of this user')
+            ->then('the list should contain "testgroup@example.org"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function allowAddingUserToGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "testgroup@example.org"')
+            ->and('adding a user "cn=Test Test" with the mail address "test@example.org"')
+            ->and('modifying group with the mail address "testgroup@example.org" to contain the member "cn=Test Test".')
+            ->and('fetching the groups "group@example.org"')
+            ->and('listing the members of this group')
+            ->then('the list should contain "test@example.org"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function allowAddingUserToGroupWhenCreatingUser()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "testgroup@example.org"')
+            ->and('adding a user "cn=Test Test" with the mail address "test@example.org" and member of "testgroup@example.org"')
+            ->and('fetching the groups "group@example.org"')
+            ->and('listing the members of this group')
+            ->then('the list should contain "test@example.org"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function allowRemovingUserFromGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org"')
+            ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"')
+            ->and('modifying group with the mail address "testgroup@example.org" to contain no members.')
+            ->and('fetching the groups "group@example.org"')
+            ->and('listing the members of this group')
+            ->then('the list is empty');
+    }
+
+    /**
+     * @scenario
+     */
+    public function deletingUserRemovesUserFromAllDistributionLists()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org"')
+            ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"')
+            ->and('adding a group with the mail address "testgroup2@example.org" and the member "cn=Test Test"')
+            ->and('deleting user "cn=Test Test"')
+            ->and('listing the members of group "testgroup@example.org"')
+            ->and('listing the members of group "testgroup2@example.org"')
+            ->then('the list of group "testgroup@example.org" is empty')
+            ->and('the list of group "testgroup2@example.org" is empty');
+    }
+
+    /**
+     * @scenario
+     */
+    public function modifyingUserIDDoesNotChangeGroupMembership()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org"')
+            ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"')
+            ->and('modifying user "cn=Test Test" to ID "cn=Test2 Test"')
+            ->and('listing the members of group "testgroup@example.org"')
+            ->then('the list of group "testgroup@example.org" contains "cn=Test2 Test"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function addingGroupInUndefinedDomainIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->and('the only served mail domain is "example.org"')
+            ->when('adding a group with the mail address "test@doesnotexist.org"')
+            ->then('the result should indicate an error with', 'The group cannot be added: Domain "doesnotexist.org" is not being handled by this server!');
+    }
+
+    /**
+     *   kolab/issue1368 (Webinterface allows to create email addresses with slash that cyrus cannot handle)
+     *
+     * @scenario
+     * @dataProvider invalidMails
+     */
+    public function disallowInvalidMailAddresses($address)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with an invalid mail address', $address)
+            ->then('the result should indicate an error with', "The group cannot be added: Address \"$address\" is not a valid mail address!");
+    }
+
+    /**
+     * @scenario
+     */
+    public function objectAttributeDescriptionsCanBeRetrieved()
+    {
+        $this->given('an empty Kolab server')
+            ->when('retrieving the supported attributes by the object type "group"')
+            ->then('the result is an array of Horde attribute descriptions')
+            ->and('contains the description of "members"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function removingGroupFailsIfGroupDoesNotExist()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "group@example.org"')
+            ->and('deleting the group with the mail address "group@example.org"')
+            ->then('the result should indicate an error with', 'The group cannot be deleted: Group "group@example.org" does not exist!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function removingGroupByMailSucceeds()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "test@example.org"')
+            ->and('deleting the group with mail address "test@example.org"')
+            ->then('the result indicates success')
+            ->and('listing all groups returns an empty list');
+    }
+
+    /**
+     *      kolab/issue1189 (IMAP login fails on some specific uids)
+     *
+     * @scenario
+     */
+    public function userUidsShouldNotResembleTheLocalPartOfMailAddresses()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a group with the mail address "test@example.org"')
+            ->and('adding a user with the uid "test"')
+            ->then('the result should indicate an error with', 'The user cannot be added: The uid "test" matches the local part of the mail address "test@example.org" assigned to group "test@example.org"!');
+    }
+
+    /**
+     * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.)
+     *
+     * @scenario
+     */
+    public function addedUserCanLoginIfInAllowedGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->and('Horde uses the Kolab auth driver')
+            ->and('only members of group "testgroup@example.org" are allowed')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"')
+            ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"')
+            ->and('trying to login to Horde with "test@example.org" and passowrd "test"')
+            ->then('the result indicates success')
+            ->and('the session shows "test@example.org" as the current user');
+    }
+
+    /**
+     * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.)
+     *
+     * @scenario
+     */
+    public function addedUserCannotLoginIfInNotInAllowedGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->and('Horde uses the Kolab auth driver')
+            ->and('only members of group "testgroup@example.org" are allowed')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"')
+            ->and('adding a group with the mail address "testgroup@example.org" and no members')
+            ->and('trying to login to Horde with "test@example.org" and passowrd "test"')
+            ->then('the user may not login');
+    }
+
+    /**
+     * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.)
+     *
+     * @scenario
+     */
+    public function addedUserCanLoginIfInNotInDisallowedGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->and('Horde uses the Kolab auth driver')
+            ->and('members of group "testgroup@example.org" may not login')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"')
+            ->and('adding a group with the mail address "testgroup@example.org" and no members')
+            ->and('trying to login to Horde with "test@example.org" and passowrd "test"')
+            ->then('the result indicates success')
+            ->and('the session shows "test@example.org" as the current user');
+    }
+
+    /**
+     * kolab/issue2207 (Make it possible to enable and disable users to be able to use the webclient.)
+     *
+     * @scenario
+     */
+    public function addedUserCannotLoginIfInDisallowedGroup()
+    {
+        $this->given('an empty Kolab server')
+            ->and('Horde uses the Kolab auth driver')
+            ->and('members of group "testgroup@example.org" may not login')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org" and password "test"')
+            ->and('adding a group with the mail address "testgroup@example.org" and the member "cn=Test Test"')
+            ->and('trying to login to Horde with "test@example.org" and passowrd "test"')
+            ->then('the user may not login');
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php
new file mode 100644 (file)
index 0000000..b62c6ac
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Test the group object.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * Test the group object.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/GroupTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_GroupTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Set up testing.
+     *
+     * @return NULL
+     */
+    protected function setUp()
+    {
+        $this->ldap = $this->prepareEmptyKolabServer();
+    }
+
+    /**
+     * Add a group object.
+     *
+     * @return NULL
+     */
+    private function _addValidGroups()
+    {
+        $groups = $this->validGroups();
+        foreach ($groups as $group) {
+            $result = $this->ldap->add($group[0]);
+            $this->assertNoError($result);
+        }
+    }
+
+    /**
+     * Test ID generation for a group.
+     *
+     * @return NULL
+     */
+    public function testGenerateId()
+    {
+        $groups = $this->validGroups();
+        $this->assertEquals('cn=empty.group@example.org,dc=example,dc=org',
+                            $this->ldap->generateUid(KOLAB_OBJECT_GROUP, $groups[0][0]));
+    }
+
+    /**
+     * Test fetching a group.
+     *
+     * @return NULL
+     */
+    public function testFetchGroup()
+    {
+        $this->_addValidGroups();
+
+        $group = $this->ldap->fetch('cn=empty.group@example.org,dc=example,dc=org');
+        $this->assertNoError($group);
+        $this->assertEquals('Horde_Kolab_Server_Object_group', get_class($group));
+    }
+
+    /**
+     * Test fetching a group.
+     *
+     * @return NULL
+     */
+    public function testToHash()
+    {
+        $this->_addValidGroups();
+
+        $group = $this->ldap->fetch('cn=empty.group@example.org,dc=example,dc=org');
+        $this->assertNoError($group);
+
+        $hash = $group->toHash();
+        $this->assertNoError($hash);
+        $this->assertContains('mail', array_keys($hash));
+        $this->assertContains('id', array_keys($hash));
+        $this->assertContains('visible', array_keys($hash));
+        $this->assertEquals('empty.group@example.org', $hash['mail']);
+        $this->assertEquals('empty.group@example.org', $hash['id']);
+        $this->assertTrue($hash['visible']);
+    }
+
+    /**
+     * Test listing groups.
+     *
+     * @return NULL
+     */
+    public function testListingGroups()
+    {
+        $this->assertEquals(0, count($GLOBALS['KOLAB_SERVER_TEST_DATA']));
+        $this->assertEquals(0,
+                            count($this->ldap->_search('(&(!(cn=domains))(objectClass=kolabGroupOfNames))',
+                                                       array(),
+                                                       $this->ldap->_base_dn)));
+
+        $this->_addValidGroups();
+
+        $this->assertEquals(3, count($GLOBALS['KOLAB_SERVER_TEST_DATA']));
+        $this->assertEquals(3,
+                            count($this->ldap->_search('(&(!(cn=domains))(objectClass=kolabGroupOfNames))',
+                                                       array(),
+                                                       $this->ldap->_base_dn)));
+
+        $list = $this->ldap->listObjects(KOLAB_OBJECT_GROUP);
+        $this->assertNoError($list);
+        $this->assertEquals(3, count($list));
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php
new file mode 100644 (file)
index 0000000..e35a65d
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Test the object class.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+require_once 'PEAR.php';
+require_once 'Horde/Kolab/Server/Object.php';
+
+/**
+ * The the handling of objects.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_ObjectTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Set up a dummy db object that will not be used during the
+     * tests. We just need it so that PHP does not complain about the
+     * inability to refernce the storage class.
+     *
+     * @return NULL
+     */
+    protected function setUp()
+    {
+        $this->_dummydb = &new DummyDB();
+    }
+
+    /**
+     * Test construction of the class.
+     *
+     * @return NULL
+     */
+    public function testConstruct()
+    {
+        $ko = &new Horde_Kolab_Server_Object($this->_dummydb);
+        /** Not enough information provided */
+        $this->assertEquals('Specify either the UID or a search result!',
+                            $ko->_cache->message);
+        $attr = $ko->get(KOLAB_ATTR_CN);
+        /** The base object supports nothing */
+        $this->assertError($attr, 'Attribute "cn" not supported!');
+        $ko2 = &new Horde_Kolab_Server_Object($this->_dummydb);
+        $ko  = &new Horde_Kolab_Server_Object($ko2);
+        $this->assertNoError($ko);
+        $this->assertNoError($ko2);
+        /** Ensure that referencing works */
+        $this->assertSame($ko->_db, $ko2);
+    }
+
+    /**
+     * Provide test data for the ConstructDn test.
+     *
+     * @return array The test object data.
+     */
+    public static function provideConstructDn()
+    {
+        return array(
+            array('test', null, 'test'),
+            array(false, array('dn' => 'test'), 'test'),
+            array(false, array('dn' => array('test')), 'test'),
+            array('test2', array('dn' => array('test')), 'test2'),
+        );
+    }
+
+    /**
+     * Check a few DN values when constructing the object.
+     *
+     * @param string $dn     The uid for the object.
+     * @param string $data   Object data.
+     * @param string $expect Expect this uid.
+     *
+     * @dataProvider provideConstructDn
+     *
+     * @return NULL
+     */
+    public function testConstructDn($dn, $data, $expect)
+    {
+        $ko  = &new Horde_Kolab_Server_Object($this->_dummydb, $dn, $data);
+        $ndn = $ko->get(KOLAB_ATTR_UID);
+        $this->assertNoError($ndn);
+        $this->assertEquals($expect, $ndn);
+    }
+
+    /**
+     * Provide test data for the GetFn test.
+     *
+     * @return array The test object data.
+     */
+    public static function provideGetFn()
+    {
+        return array(
+            array(
+                array(
+                    'dn' => 'test',
+                    'cn' => 'Frank Mustermann',
+                    'sn' => 'Mustermann'),
+                'Frank'));
+    }
+
+    /**
+     * Check the generating of the "First Name" attribute.
+     *
+     * @param string $data   Object data.
+     * @param string $expect Expect this full name.
+     *
+     * @dataProvider provideGetFn
+     *
+     * @return NULL
+     */
+    public function testGetFn($data, $expect)
+    {
+        $ko = &Horde_Kolab_Server_Object::factory(KOLAB_OBJECT_USER,
+                                                  null, $this->_dummydb, $data);
+        $this->assertNoError($ko);
+        $ndn = $ko->get(KOLAB_ATTR_FN);
+        $this->assertNoError($ndn);
+        $this->assertEquals($expect, $ndn);
+    }
+
+}
+
+/**
+ * A dummy class for testing.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ObjectTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class DummyDB {
+
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php
new file mode 100644 (file)
index 0000000..9959a90
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Test the server class.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php,v 1.6 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the unit test framework
+ */
+require_once 'PHPUnit/Framework.php';
+
+require_once 'Horde/Kolab/Server.php';
+
+/**
+ * Tests for the main server class.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ServerTest.php,v 1.6 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_ServerTest extends PHPUnit_Framework_TestCase {
+    /**
+     * The base class provides no abilities for reading data. So it
+     * should mainly return error. But it should be capable of
+     * returning a dummy Kolab user object.
+     *
+     * @return NULL
+     */
+    public function testFetch()
+    {
+        $ks   = &Horde_Kolab_Server::factory('none');
+        $user = $ks->fetch('test');
+        $this->assertEquals(KOLAB_OBJECT_USER, get_class($user));
+        $cn = $user->get(KOLAB_ATTR_CN);
+        $this->assertEquals('Not implemented!', $cn->message);
+    }
+
+    /**
+     * The base class returns no valid data. The DN request should
+     * just return false and the search for a mail address returns the
+     * provided argument.
+     *
+     * @return NULL
+     */
+    public function testDnFor()
+    {
+        $ks = &Horde_Kolab_Server::factory('none');
+        $dn = $ks->uidForIdOrMail('test');
+        $this->assertEquals(false, $dn);
+        $dn = $ks->uidForMailAddress('test');
+        $this->assertEquals('test', $dn);
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php
new file mode 100644 (file)
index 0000000..16fbdec
--- /dev/null
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Test the Kolab session handler.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php,v 1.10 2009/01/14 21:46:54 wrobel Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+require_once 'Horde/Kolab/Session.php';
+
+/**
+ * Test the Kolab session handler.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/SessionTest.php,v 1.10 2009/01/14 21:46:54 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_SessionTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Test class construction.
+     *
+     * @return NULL
+     */
+    public function testConstructEmpty()
+    {
+        global $conf;
+        $conf['kolab']['imap']['allow_special_users'] = true;
+
+        $session = &Horde_Kolab_Session::singleton();
+
+        $this->assertEquals('anonymous', $session->user_mail);
+
+        $params = $session->getImapParams();
+        $this->assertNoError($params);
+        $this->assertEquals('localhost', $params['hostspec']);
+        $this->assertEquals(143, $params['port']);
+    }
+
+    /**
+     * Test old style class construction.
+     *
+     * @return NULL
+     */
+    public function testConstructSimple()
+    {
+        global $conf;
+        $conf['kolab']['imap']['server']     = 'example.com';
+        $conf['kolab']['imap']['port']       = 200;
+        $conf['kolab']['freebusy']['server'] = 'fb.example.com';
+
+        $session = &new Horde_Kolab_Session();
+        $params  = $session->getImapParams();
+        if (is_a($params, 'PEAR_Error')) {
+            $this->assertEquals('', $params->getMessage());
+        }
+        $this->assertEquals('example.com', $params['hostspec']);
+        $this->assertEquals(200, $params['port']);
+    }
+
+    /**
+     * Test IMAP server retrieval.
+     *
+     * @return NULL
+     */
+    public function testGetServer()
+    {
+        $server = &$this->prepareEmptyKolabServer();
+        $result = $server->add($this->provideBasicUserTwo());
+        $this->assertNoError($result);
+        $this->assertEquals(1, count($GLOBALS['KOLAB_SERVER_TEST_DATA']));
+
+        $session = &Horde_Kolab_Session::singleton('test',
+                                                   array('password' => 'test'));
+
+        $this->assertNoError($session->auth);
+        $this->assertEquals('test@example.org', $session->user_mail);
+
+        $params = $session->getImapParams();
+        $this->assertNoError($params);
+        $this->assertEquals('home.example.org', $params['hostspec']);
+        $this->assertEquals(143, $params['port']);
+        $this->assertEquals('test@example.org', $session->user_mail);
+
+        $session->shutdown();
+
+        $hs = &Horde_SessionObjects::singleton();
+
+        $recovered_session = &$hs->query('kolab_session');
+        $params            = $recovered_session->getImapParams();
+        $this->assertNoError($params);
+        $this->assertEquals('home.example.org', $params['hostspec']);
+        $this->assertEquals(143, $params['port']);
+        $this->assertEquals('test@example.org', $session->user_mail);
+
+        $this->assertEquals('https://fb.example.org/freebusy', $session->freebusy_server);
+    }
+
+    /**
+     * Test retrieving the FreeBusy server for the unauthenticated state.
+     *
+     * @return NULL
+     */
+    public function testGetFreeBusyServer()
+    {
+        $server = &$this->prepareEmptyKolabServer();
+        $result = $server->add($this->provideBasicUserTwo());
+        $this->assertNoError($result);
+        $session = &Horde_Kolab_Session::singleton();
+        $this->assertEquals('', $session->freebusy_server);
+    }
+
+    /**
+     * Test group based login allow implemention.
+     *
+     * @return NULL
+     */
+    public function testLoginAllow()
+    {
+        global $conf;
+        $conf['kolab']['server']['allow_group'] = 'group2@example.org';
+        $conf['kolab']['server']['deny_group'] = null;
+
+        $server = &$this->prepareEmptyKolabServer();
+        $result = $server->add($this->provideBasicUserOne());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicUserTwo());
+        $this->assertNoError($result);
+        $groups = $this->validGroups();
+        foreach ($groups as $group) {
+            $result = $server->add($group[0]);
+            $this->assertNoError($result);
+        }
+
+        $session = &Horde_Kolab_Session::singleton('wrobel',
+                                                   array('password' => 'none'),
+                                                   true);
+
+        $this->assertNoError($session->auth);
+        $this->assertEquals('wrobel@example.org', $session->user_mail);
+
+        $session = &Horde_Kolab_Session::singleton('test',
+                                                   array('password' => 'test'),
+                                                   true);
+
+        $this->assertError($session->auth, 'You are no member of a group that may login on this server.');
+        $this->assertTrue(empty($session->user_mail));
+    }
+
+    /**
+     * Test group based login deny implemention.
+     *
+     * @return NULL
+     */
+    public function testLoginDeny()
+    {
+        global $conf;
+        $conf['kolab']['server']['deny_group'] = 'group2@example.org';
+        unset($conf['kolab']['server']['allow_group']);
+
+        $server = &$this->prepareEmptyKolabServer();
+        $result = $server->add($this->provideBasicUserOne());
+        $this->assertNoError($result);
+        $result = $server->add($this->provideBasicUserTwo());
+        $this->assertNoError($result);
+        $groups = $this->validGroups();
+        foreach ($groups as $group) {
+            $result = $server->add($group[0]);
+            $this->assertNoError($result);
+        }
+
+        $session = &Horde_Kolab_Session::singleton('test',
+                                                   array('password' => 'test'),
+                                                   true);
+
+        $this->assertNoError($session->auth);
+        $this->assertEquals('test@example.org', $session->user_mail);
+
+        $session = &Horde_Kolab_Session::singleton('wrobel',
+                                                   array('password' => 'none'),
+                                                   true);
+
+        $this->assertError($session->auth, 'You are member of a group that may not login on this server.');
+        $this->assertTrue(empty($session->user_mail));
+
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php
new file mode 100644 (file)
index 0000000..76c2cef
--- /dev/null
@@ -0,0 +1,681 @@
+<?php
+/**
+ * Handling users.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * Handling users.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/UserHandlingTest.php,v 1.3 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_UserHandlingTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Test listing userss if there are no users.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function listingUsersOnEmptyServer()
+    {
+        $this->given('an empty Kolab server')
+            ->when('listing all users')
+            ->then('the list is an empty array');
+    }
+
+    /**
+     * Test listing users after adding some users.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUsersAfterAddingUsers($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object list', $user_list)
+            ->and('listing all users')
+            ->then('the list has a number of entries equal to', count($user_list));
+    }
+
+    /**
+     * Test listing users after adding some users.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUserCount($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding an object list', $user_list)
+            ->and('retriving the result count')
+            ->then('the count equals to', count($user_list));
+    }
+
+    /**
+     * Test the list of users for the user id.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUsersHasAttributeId($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user list', $user_list)
+            ->then('the user list contains the unique ID for each user');
+    }
+
+    /**
+     * Test the list of users for the user type.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUsersHasAttributeType($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user list', $user_list)
+            ->then('the user list contains the user type for each user');
+    }
+
+    /**
+     * Test the list of users for the user full name.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUsersHasAttributeFullName($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user list', $user_list)
+            ->then('the user list contains the full name for each user');
+    }
+
+    /**
+     * Test the list of users for the user mail.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUsersHasAttributeEmail($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user list', $user_list)
+            ->then('the user list contains the email for each user');
+    }
+
+    /**
+     * Test the list of users for the user uid.
+     *
+     * @param array $user_list The users to add.
+     *
+     * @scenario
+     * @dataProvider userLists
+     *
+     * @return NULL
+     */
+    public function listingUsersHasAttributeUid($user_list)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user list', $user_list)
+            ->then('the list contains the uid for each user');
+    }
+
+    /**
+     * @scenario
+     * @dataProvider userListByLetter
+     */
+    public function listingUsersCanBeRestrictedByStartLetterOfTheLastName($letter, $count)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding user list', $this->largeList())
+            ->and('retrieving the result count of a list restricted by the start letter of the last name', $letter)
+            ->then('the list contains a correct amount of results', $count);
+    }
+
+    /**
+     * @scenario
+     * @dataProvider userListByLetter
+     */
+    public function countingUsersCanBeRestrictedByStartLetterOfTheLastName($letter, $count)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding user list', $this->largeList())
+            ->and('retrieving the result count of a list restricted by the start letter of the last name', $letter)
+            ->then('the count contains a correct number', $count);
+    }
+
+    /**
+     * @scenario
+     * @dataProvider userListByAttribute
+     */
+    public function countingUsersCanBeRestrictedByContentsInAnAttribute($attribute, $content, $count)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding user list', $this->largeList())
+            ->and('retrieving the result count of a list restricted by content in an attribute', $attribute, $content)
+            ->then('the count contains a correct number', $count);
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutGivenName()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without first name')
+            ->then('the result should indicate an error with', 'Adding object failed: Either the last name or the given name is missing!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutLastName()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without last name')
+            ->then('the result should indicate an error with', 'Adding object failed: Either the last name or the given name is missing!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutPassword()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without password')
+            ->then('the result should indicate an error with', 'Adding object failed: The value for "userPassword" is missing!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutPrimaryMail()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without primary mail')
+            ->then('the result should indicate an error with', 'Adding object failed: The value for "mail" is missing!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutTypeCreatesStandardUser()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without user type')
+            ->then('a standard user has been created');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutInvitationPolicySetsManualPolicy()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without an invitation policy')
+            ->then('the added user has a manual policy');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithoutHomeServerFails()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user without a home server')
+            ->then('the result should indicate an error with', 'The user cannot be added: The home Kolab server (or network) has not been specified!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserForDistributedKolabWithoutImapServerFails()
+    {
+        $this->given('an empty Kolab server')
+            ->and('distributed Kolab')
+            ->when('adding a user without an imap server')
+            ->then('the result should indicate an error with', 'The user cannot be added: The home imap server has not been specified!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithImapServerFailsOnNonDistributedKolab()
+    {
+        $this->given('an empty Kolab server')
+            ->and('monolithic Kolab')
+            ->when('adding a user with an imap server')
+            ->then('the result should indicate an error with', 'The user cannot be added: A home imap server is only supported with a distributed Kolab setup!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function creatingUserWithFreeBusyServerFailsOnNonDistributedKolab()
+    {
+        $this->given('an empty Kolab server')
+            ->and('monolithic Kolab')
+            ->when('adding a user with a free/busy server')
+            ->then('the result should indicate an error with', 'The user cannot be added: A seperate free/busy server is only supported with a distributed Kolab setup!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function modifyingUserMailAddressIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the mail address "test@example.org"')
+            ->and('modifying the mail address to "new@example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be modified: Changing the mail address from "test@example.org" to "new@example.org" is not allowed!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function modifyingUserHomeServerIsNotAllowd()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the home server "test.example.org"')
+            ->and('modifying the home server to "new.example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be modified: Changing the home server from "test.example.org" to "new.example.org" is not allowed!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function modifyingUserImapServerIsNotAllowd()
+    {
+        $this->given('an empty Kolab server')
+            ->and('distributed Kolab')
+            ->when('adding a user with the imap server "test.example.org"')
+            ->and('modifying the imap server to "new.example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be modified: Changing the imap server from "test.example.org" to "new.example.org" is not allowed!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenMailAndMailIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the mail address "test@example.org"')
+            ->and('adding a user "Test2 Test2" with the mail address "test@example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Mail address "test@example.org" is already the mail address of user "Test Test"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenMailAndAliasIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the mail address "test@example.org"')
+            ->and('adding a user with the alias address "test@example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Alias address "test@example.org" is already the mail address of user "Test Test"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenAliasAndAliasIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the alias address "test@example.org"')
+            ->and('adding a user with the alias address "test@example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Alias address "test@example.org" is already the alias address of user "Test Test"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenMailAndUidIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the mail address "test@example.org"')
+            ->and('adding a user with the uid "test@example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Uid "test@example.org" is already the mail address of user "Test Test"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function conflictBetweenUidAndUidIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "Test Test" with the uid "test"')
+            ->and('adding a user with the uid "test"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Uid "test" is already the uid of user "Test Test"!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function nonExistingDelegateIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the delegate address "test@example.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Delegate address "test@example.org" does not exist!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function addingUserInUndefinedDomainIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->and('the only served mail domain is "example.org"')
+            ->when('adding a user with the mail address "test@doesnotexist.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Domain "doesnotexist.org" is not being handled by this server!');
+    }
+
+    /**
+     *  kolab/issue444 (a kolab user may delegate to an external user which should not be possible)
+     *
+     * @scenario
+     */
+    public function addingUserWithDelegateInUndefinedDomainIsNotAllowed()
+    {
+        $this->given('an empty Kolab server')
+            ->and('the only served mail domain is "example.org"')
+            ->when('adding a user with the delegate mail address "test@doesnotexist.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Domain "doesnotexist.org" is not being handled by this server!');
+    }
+
+    /**
+     *   kolab/issue1368 (Webinterface allows to create email addresses with slash that cyrus cannot handle)
+     *
+     * @scenario
+     * @dataProvider invalidMails
+     */
+    public function disallowInvalidMailAddresses($address)
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with an invalid mail address', $address)
+            ->then('the result should indicate an error with', "The user cannot be added: Address \"$address\" is not a valid mail address!");
+    }
+
+    /**
+     * @scenario
+     */
+    public function addingUserOnUndefinedHomeServer()
+    {
+        $this->given('an empty Kolab server')
+            ->and('the only home server in the network is "example.org"')
+            ->when('adding a user with the home server "doesnotexist.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Host "doesnotexist.org" is not part of the Kolab network!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function addingUserOnUndefinedImapServer()
+    {
+        $this->given('an empty Kolab server')
+            ->and('distributed Kolab')
+            ->and('the only imap server in the network is "example.org"')
+            ->when('adding a user with the imap server "doesnotexist.org"')
+            ->then('the result should indicate an error with', 'The user cannot be added: Imap server "doesnotexist.org" is not part of the Kolab network!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function userAttributesCanBeExtended()
+    {
+        $this->given('an empty Kolab server')
+            ->and('an extended attribute "test" has been defined')
+            ->when('adding a user with the attribute "test" set to "FIND ME"')
+            ->then('the result indicates success')
+            ->and('the user can be found using the "test" attribute with the value "FIND ME"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function extendedObjectAttributeDescriptionsCanBeRetrieved()
+    {
+        $this->given('an empty Kolab server')
+            ->and('an extended attribute "test" has been defined')
+            ->when('retrieving the supported attributes by the object type "user"')
+            ->then('the result is an array of Horde attribute descriptions')
+            ->and('contains the description of "test"');
+    }
+
+    /**
+     * @scenario
+     */
+    public function removingUserFailsIfUserDoesNotExist()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the ID "cn=Test Test"')
+            ->and('deleting the user with the ID "cn=Dummy Dummy"')
+            ->then('the result should indicate an error with', 'The user cannot be deleted: User "cn=Dummy Dummy" does not exist!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function removingUserByMailSucceeds()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the mail address "test@example.org"')
+            ->and('deleting the user with mail address "test@example.org"')
+            ->then('the result indicates success')
+            ->and('listing all users returns an empty list');
+    }
+
+    /**
+     * @scenario
+     */
+    public function removingUserByIdSucceeds()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the ID "cn=Test Test"')
+            ->and('deleting the user with the ID "cn=Test Test"')
+            ->then('the result indicates success')
+            ->and('listing all users returns an empty list');
+    }
+
+    /**
+     * @scenario
+     */
+    public function addedUserCanLogin()
+    {
+        $this->given('an empty Kolab server')
+            ->and('Horde uses the Kolab auth driver')
+            ->when('adding a user with the mail address "test@example.org" and password "test"')
+            ->and('trying to login to Horde with "test@example.org" and passowrd "test"')
+            ->then('the result indicates success')
+            ->and('the session shows "test@example.org" as the current user');
+    }
+
+    /**
+     * @scenario
+     */
+    public function allowUserWithExtendedObjectClasses()
+    {
+        $this->given('an empty Kolab server')
+            ->and('an extended set of objectclasses')
+            ->when('adding a user with the mail address "test@example.org"')
+            ->and('fetching user "test@example.org"')
+            ->then('has the additional object classes set');
+    }
+
+    /**
+     * @scenario
+     */
+    public function allowToCheckUserPasswords()
+    {
+        $this->given('an empty Kolab server')
+            ->and('password check enabled')
+            ->when('adding a user with the mail address "test@example.org" and password "tosimple"')
+            ->then('the result should indicate an error with', 'The user cannot be added: The chosen password is not complex enough!');
+    }
+
+    /**
+     * @scenario
+     */
+    public function allowToSetAttributeDefaults()
+    {
+        $this->given('an empty Kolab server')
+            ->and('an extended attribute "test" with the default value "test" has been defined')
+            ->when('adding a user with the mail address "test@example.org" and an empty attribute "test"')
+            ->and('fetching user "test@example.org"')
+            ->then('the user object has the attribute "test" set to "test"');
+    }
+
+    /**
+     * kolab/issue2742 (Have a default quota value when creating new users via the web interface)
+     *
+     * @scenario
+     */
+    public function allowToSetDomainSpecificAttributeDefaults()
+    {
+        $this->given('an empty Kolab server')
+            ->and('domain "example.org" is served by the Kolab server')
+            ->and('domain "example2.org" is served by the Kolab server')
+            ->and('an extended attribute "test" with the default value "test" has been defined')
+            ->and('an extended attribute "test" with the default value "test2" has been defined for domain example2.org')
+            ->when('adding a user with the mail address "test@example.org" and an empty attribute "test"')
+            ->and('adding a user with the mail address "test@example2.org" and an empty attribute "test"')
+            ->and('fetching user "test@example.org" and "test@example2.org"')
+            ->then('the user "test@example.org" has the attribute "test" set to "test"')
+            ->and('the user "test@example2.org" has the attribute "test" set to "test2"');
+    }
+
+    /**
+     *     kolab/issue3035 (Initialise internal Horde parameters when creating a user)
+     *
+     * @scenario
+     * @dataProvider userAdd
+     */
+    public function addedUserHasPreferencesInitialized()
+    {
+        $this->given('an empty Kolab server')
+            ->and('Horde uses the Kolab auth driver')
+            ->when('adding a user', $user)
+            ->and('trying to login to Horde with "test@example.org" and passowrd "test"')
+            ->then('the preferences are automatically set to the user information', $user);
+    }
+
+    /**
+     *      kolab/issue1189 (IMAP login fails on some specific uids)
+     *
+     * @scenario
+     */
+    public function userUidsShouldNotResembleTheLocalPartOfMailAddresses()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user "cn=Test Test" with the mail address "test@example.org"')
+            ->and('adding a user with the uid "test"')
+            ->then('the result should indicate an error with', 'The user cannot be added: The uid "test" matches the local part of the mail address "test@example.org" assigned to user "cn=Test Test"!');
+    }
+
+    /**
+     *  kolab/issue606 (It is not possible to register people with middlename correctly)
+     *
+     * @scenario
+     */
+    public function allowToSetTheMiddleName()
+    {
+        $this->given('an empty Kolab server')
+            ->and('an extended attribute "middleName" has been defined')
+            ->when('adding a user with the mail address "test@example.org" and the middle name "Middle"')
+            ->and('fetching user "test@example.org"')
+            ->then('the user object has the attribute "middleName" set to "Middle"');
+    }
+
+    /**
+     *   kolab/issue1880 (Poor handling of apostrophes in ldap and admin webpages)
+     *
+     * @scenario
+     */
+    public function correctlyEscapeApostrophesInNames()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the mail address "test@example.org" and the last name "O\'Donnell"')
+            ->and('fetching user "test@example.org"')
+            ->then('the user name has the attribute "sn" set to "O\'Donnell"');
+    }
+
+    /**
+     *    kolab/issue1677 (Allow a user to use an external address as sender)
+     *
+     * @scenario
+     */
+    public function allowUserToUseExternalAddressAsSender()
+    {
+        $this->given('an empty Kolab server')
+            ->when('adding a user with the mail address "test@example.org" and the external address "other@doesnotexist.org"')
+            ->and('fetching user "test@example.org"')
+            ->then('the user has the attribute external address "other@doesnotexist.org"');
+    }
+
+    /**
+     *     kolab/issue3036 (cn = "givenName sn" ?)
+     *
+     * @scenario
+     */
+    public function allowCustomFullnameHandling()
+    {
+        $this->given('an empty Kolab server')
+            ->and('an extended attribute "middleName" has been defined')
+            ->and('custom full name handling has been set to "lastname, firstname middlename"')
+            ->when('adding a user with the mail address "test@example.org", the last name "Test", the first name "Test", and the middle name "Middle"')
+            ->and('fetching user "test@example.org"')
+            ->then('the user has the attribute full name "Test, Test Middle"');
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php
new file mode 100644 (file)
index 0000000..cd91823
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Test the user object.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php,v 1.5 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * Test the user object.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/UserTest.php,v 1.5 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_UserTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Set up testing.
+     *
+     * @return NULL
+     */
+    protected function setUp()
+    {
+        $this->server = $this->prepareEmptyKolabServer();
+        $users        = $this->validUsers();
+        foreach ($users as $user) {
+            $result = $this->server->add($user[0]);
+            $this->assertNoError($result);
+        }
+    }
+
+    /**
+     * Test ID generation for a user.
+     *
+     * @return NULL
+     */
+    public function testGenerateId()
+    {
+        $users = $this->validUsers();
+        $this->assertEquals('Gunnar Wrobel',
+                            Horde_Kolab_Server_Object_user::generateId($users[0][0]));
+
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org',
+                            $this->server->generateUid(KOLAB_OBJECT_USER,
+                                                       $users[0][0]));
+    }
+
+    /**
+     * Test adding invalid user.
+     *
+     * @return NULL
+     */
+    public function testAddInvalidUser()
+    {
+        $user = $this->provideInvalidUserWithoutGivenName();
+
+        $result = $this->server->add($user);
+
+        $this->assertError($result,
+                           'Adding object failed: Either the last name or the given name is missing!');
+    }
+
+    /**
+     * Test fetching a user.
+     *
+     * @return NULL
+     */
+    public function testFetchUser()
+    {
+        $user = $this->server->fetch('cn=Gunnar Wrobel,dc=example,dc=org');
+        $this->assertNoError($user);
+        $this->assertEquals('Horde_Kolab_Server_Object_user', get_class($user));
+    }
+
+    /**
+     * Test fetching server information.
+     *
+     * @return NULL
+     */
+    public function testGetServer()
+    {
+        $user = $this->server->fetch('cn=Gunnar Wrobel,dc=example,dc=org');
+        $this->assertNoError($user);
+        $imap = $user->getServer('imap');
+        $this->assertEquals('imap.example.org', $imap);
+
+        $user = $this->server->fetch('cn=Test Test,dc=example,dc=org');
+        $imap = $user->getServer('imap');
+        $this->assertEquals('home.example.org', $imap);
+
+        $user = $this->server->fetch('cn=Gunnar Wrobel,dc=example,dc=org');
+        $attr = $user->get(KOLAB_ATTR_FREEBUSYHOST);
+        if (is_a($attr, 'PEAR_Error')) {
+            $this->assertEquals('', $attr->getMessage());
+        }
+        $this->assertEquals('https://fb.example.org/freebusy', $attr);
+
+        $imap = $user->getServer('freebusy');
+        $this->assertEquals('https://fb.example.org/freebusy', $imap);
+
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php
new file mode 100644 (file)
index 0000000..3aebcb8
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+/**
+ * Test the LDAP driver.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the unit test framework
+ */
+require_once 'PHPUnit/Framework.php';
+
+require_once 'Horde/Kolab/Server.php';
+require_once 'Horde/Kolab/Server/ldap.php';
+
+/**
+ * Test the LDAP backend.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/ldapTest.php,v 1.7 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_ldapTest extends PHPUnit_Framework_TestCase {
+
+    /**
+     * Test handling of object classes.
+     *
+     * @return NULL
+     */
+    public function testGetObjectClasses()
+    {
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_read'));
+        $ldap->expects($this->any())
+            ->method('_read')
+            ->will($this->returnValue(array (
+                                          'objectClass' =>
+                                          array (
+                                              'count' => 4,
+                                              0 => 'top',
+                                              1 => 'inetOrgPerson',
+                                              2 => 'kolabInetOrgPerson',
+                                              3 => 'hordePerson',
+                                          ),
+                                          0 => 'objectClass',
+                                          'count' => 1)));
+
+        $classes = $ldap->_getObjectClasses('cn=Gunnar Wrobel,dc=example,dc=org');
+        $this->assertContains('top', $classes);
+        $this->assertContains('kolabinetorgperson', $classes);
+        $this->assertContains('hordeperson', $classes);
+
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_read'));
+        $ldap->expects($this->any())
+             ->method('_read')
+             ->will($this->returnValue(PEAR::raiseError('LDAP Error: No such object: cn=DOES NOT EXIST,dc=example,dc=org: No such object')));
+
+        $classes = $ldap->_getObjectClasses('cn=DOES NOT EXIST,dc=example,dc=org');
+        $this->assertEquals('LDAP Error: No such object: cn=DOES NOT EXIST,dc=example,dc=org: No such object',
+                            $classes->message);
+    }
+
+    /**
+     * Test retrieving a primary mail for a mail or uid.
+     *
+     * @return NULL
+     */
+    public function testMailForUidOrMail()
+    {
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes',
+                                                                '_search', '_count',
+                                                                '_firstEntry'));
+        $ldap->expects($this->any())
+            ->method('_getAttributes')
+            ->will($this->returnValue(array (
+                                          'mail' =>
+                                          array (
+                                              'count' => 1,
+                                              0 => 'wrobel@example.org',
+                                          ),
+                                          0 => 'mail',
+                                          'count' => 1)));
+        $ldap->expects($this->any())
+            ->method('_search')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_count')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_firstEntry')
+            ->will($this->returnValue(1));
+
+        $mail = $ldap->mailForIdOrMail('wrobel');
+        $this->assertEquals('wrobel@example.org', $mail);
+
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes',
+                                                                '_search',
+                                                                '_count',
+                                                                '_firstEntry',
+                                                                '_errno',
+                                                                '_error'));
+        $ldap->expects($this->any())
+             ->method('_getAttributes')
+             ->will($this->returnValue(false));
+        $ldap->expects($this->any())
+            ->method('_search')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_count')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_firstEntry')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_errno')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_error')
+            ->will($this->returnValue('cn=DOES NOT EXIST,dc=example,dc=org: No such object'));
+
+        $mail = $ldap->mailForIdOrMail('wrobel');
+        $this->assertEquals('Retrieving attributes failed. Error was: cn=DOES NOT EXIST,dc=example,dc=org: No such object',
+                            $mail->message);
+
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getAttributes',
+                                                                '_search',
+                                                                '_count'));
+        $ldap->expects($this->any())
+             ->method('_getAttributes')
+             ->will($this->returnValue(false));
+        $ldap->expects($this->any())
+            ->method('_search')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_count')
+            ->will($this->returnValue(4));
+
+        $mail = $ldap->mailForIdOrMail('wrobel');
+        $this->assertEquals('Found 4 results when expecting only one!',
+                            $mail->message);
+    }
+
+    /**
+     * Test retrieving a DN for a mail or uid.
+     *
+     * @return NULL
+     */
+    public function testDnForUidOrMail()
+    {
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getDn',
+                                                                '_search', '_count',
+                                                                '_firstEntry'));
+        $ldap->expects($this->any())
+            ->method('_getDn')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_search')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_count')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_firstEntry')
+            ->will($this->returnValue(1));
+
+        $dn = $ldap->uidForIdOrMail('wrobel');
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $dn);
+
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getDn',
+                                                                '_search',
+                                                                '_count',
+                                                                '_firstEntry',
+                                                                '_errno',
+                                                                '_error'));
+        $ldap->expects($this->any())
+             ->method('_getDn')
+             ->will($this->returnValue(false));
+        $ldap->expects($this->any())
+            ->method('_search')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_count')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_firstEntry')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_errno')
+            ->will($this->returnValue(1));
+        $ldap->expects($this->any())
+            ->method('_error')
+            ->will($this->returnValue('cn=DOES NOT EXIST,dc=example,dc=org: No such object'));
+
+        $dn = $ldap->uidForIdOrMail('wrobel');
+        $this->assertEquals('Retrieving DN failed. Error was: cn=DOES NOT EXIST,dc=example,dc=org: No such object',
+                            $dn->message);
+
+        $ldap = $this->getMock('Horde_Kolab_Server_ldap', array('_getDn',
+                                                                '_search',
+                                                                '_count'));
+        $ldap->expects($this->any())
+             ->method('_getDn')
+             ->will($this->returnValue(false));
+        $ldap->expects($this->any())
+            ->method('_search')
+            ->will($this->returnValue('cn=Gunnar Wrobel,dc=example,dc=org'));
+        $ldap->expects($this->any())
+            ->method('_count')
+            ->will($this->returnValue(4));
+
+        $dn = $ldap->uidForIdOrMail('wrobel');
+        $this->assertEquals('Found 4 results when expecting only one!',
+                            $dn->message);
+    }
+
+}
diff --git a/framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php b/framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php
new file mode 100644 (file)
index 0000000..992fe28
--- /dev/null
@@ -0,0 +1,604 @@
+<?php
+/**
+ * Test the test driver.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php,v 1.12 2009/01/06 17:49:27 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Server.php';
+
+/**
+ * Test the test backend.
+ *
+ * $Horde: framework/Kolab_Server/test/Horde/Kolab/Server/testTest.php,v 1.12 2009/01/06 17:49:27 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_Server_testTest extends Horde_Kolab_Test_Server {
+
+    /**
+     * Set up testing.
+     *
+     * @return NULL
+     */
+    protected function setUp()
+    {
+        $this->ldap = &$this->prepareBasicServer();
+    }
+
+    /**
+     * Test search base.
+     *
+     * @return NULL
+     */
+    public function testSearchBase()
+    {
+        $result = $this->ldap->_search('(objectClass=top)', array('objectClass'));
+        $this->assertNoError($result);
+        $this->assertEquals(12, count($result));
+        
+        $result = $this->ldap->_search('(objectClass=top)', array('objectClass'),
+                                       'cn=internal,dc=example,dc=org');
+        $this->assertNoError($result);
+        $this->assertEquals(3, count($result));
+    }
+
+    /**
+     * Test sorting.
+     *
+     * @return NULL
+     */
+    public function testSorting()
+    {
+        $result = $this->ldap->_search('(mail=*)', array('mail'));
+        $this->assertNoError($result);
+        $this->assertEquals(5, count($result));
+        $this->ldap->_sort($result, 'mail');
+        $this->assertEquals('address@example.org', $result[0]['data']['mail'][0]);
+        $this->assertEquals('wrobel@example.org',
+                            $result[count($result) - 1]['data']['mail'][0]);
+    }
+
+    /**
+     * Test listing objects.
+     *
+     * @return NULL
+     */
+    public function testListObjects()
+    {
+        $filter     = '(&(objectClass=kolabInetOrgPerson)(uid=*)(mail=*)(sn=*))';
+        $attributes = array(
+            KOLAB_ATTR_SN,
+            KOLAB_ATTR_CN,
+            KOLAB_ATTR_UID,
+            KOLAB_ATTR_MAIL,
+            KOLAB_ATTR_DELETED,
+        );
+
+        $sort   = KOLAB_ATTR_SN;
+        $result = $this->ldap->_search($filter);
+        $this->assertNoError($result);
+        $this->assertEquals(2, count($result));
+
+        $result = $this->ldap->listObjects(KOLAB_OBJECT_USER);
+        $this->assertNoError($result);
+        $this->assertEquals(2, count($result));
+        $this->assertEquals(KOLAB_OBJECT_USER, get_class($result[0]));
+
+        $result = $this->ldap->listObjects(KOLAB_OBJECT_SHAREDFOLDER);
+        $this->assertNoError($result);
+        $this->assertEquals(1, count($result));
+        $this->assertEquals(KOLAB_OBJECT_SHAREDFOLDER, get_class($result[0]));
+    }
+
+    /**
+     * Test handling of object classes.
+     *
+     * @return NULL
+     */
+    public function testGetObjectClasses()
+    {
+        $classes = $this->ldap->_getObjectClasses('cn=Gunnar Wrobel,dc=example,dc=org');
+        $this->assertNoError($classes);
+        $this->assertContains('top', $classes);
+        $this->assertContains('kolabinetorgperson', $classes);
+        $this->assertContains('hordeperson', $classes);
+
+        $classes = $this->ldap->_getObjectClasses('cn=DOES NOT EXIST,dc=example,dc=org');
+        $this->assertError($classes,
+                           'LDAP Error: No such object: cn=DOES NOT EXIST,dc=example,dc=org: No such object');
+
+        $classes = $this->ldap->_getObjectClasses('cn=The Administrator,dc=example,dc=org');
+        $this->assertNoError($classes);
+        $this->assertContains('kolabinetorgperson', $classes);
+    }
+
+    /**
+     * Test handling of object types.
+     *
+     * @return NULL
+     */
+    public function testDetermineType()
+    {
+        $type = $this->ldap->_determineType('cn=empty.group@example.org,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_GROUP, $type);
+
+        $type = $this->ldap->_determineType('cn=shared@example.org,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_SHAREDFOLDER, $type);
+
+        $type = $this->ldap->_determineType('cn=The Administrator,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_ADMINISTRATOR, $type);
+
+        $type = $this->ldap->_determineType('cn=Main Tainer,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_MAINTAINER, $type);
+
+        $type = $this->ldap->_determineType('cn=Domain Maintainer,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_DOMAINMAINTAINER, $type);
+
+        $type = $this->ldap->_determineType('cn=Test Address,cn=external,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_ADDRESS, $type);
+
+        $type = $this->ldap->_determineType('cn=Gunnar Wrobel,dc=example,dc=org');
+        $this->assertNoError($type);
+        $this->assertEquals(KOLAB_OBJECT_USER, $type);
+    }
+
+    /**
+     * Test retrieving a primary mail for a mail or id.
+     *
+     * @return NULL
+     */
+    public function testMailForIdOrMail()
+    {
+        $mail = $this->ldap->mailForIdOrMail('wrobel');
+        $this->assertNoError($mail);
+        $this->assertEquals('wrobel@example.org', $mail);
+
+        $mail = $this->ldap->mailForIdOrMail('wrobel@example.org');
+        $this->assertNoError($mail);
+        $this->assertEquals('wrobel@example.org', $mail);
+
+        $mail = $this->ldap->mailForIdOrMail('DOES NOT EXIST');
+        $this->assertNoError($mail);
+        $this->assertSame(null, $mail);
+    }
+
+    /**
+     * Test retrieving a UID for a mail or id.
+     *
+     * @return NULL
+     */
+    public function testUidForIdOrMail()
+    {
+        $uid = $this->ldap->uidForIdOrMail('wrobel');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForIdOrMail('wrobel@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForIdOrMail('DOES NOT EXIST');
+        $this->assertNoError($uid);
+        $this->assertSame(false, $uid);
+    }
+
+    /**
+     * Test retrieving a UID for a mail or id.
+     *
+     * @return NULL
+     */
+    public function testUidForMailOrIdOrAlias()
+    {
+        $uid = $this->ldap->uidForMailOrIdOrAlias('g.wrobel@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForMailOrIdOrAlias('wrobel@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForMailOrIdOrAlias('wrobel');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForMailOrIdOrAlias('DOES NOT EXIST');
+        $this->assertNoError($uid);
+        $this->assertSame(false, $uid);
+    }
+
+    /**
+     * Test retrieving all addresses for a mail or id.
+     *
+     * @return NULL
+     */
+    public function testAddrsForIdOrMail()
+    {
+        $addrs = $this->ldap->addrsForIdOrMail('wrobel');
+
+        $testuser = $this->ldap->fetch('cn=Test Test,dc=example,dc=org');
+        $this->assertNoError($testuser);
+        $this->assertContains('wrobel@example.org',
+                              $testuser->get(KOLAB_ATTR_KOLABDELEGATE, false));
+
+        $this->assertNoError($addrs);
+        $this->assertContains('wrobel@example.org', $addrs);
+        $this->assertContains('test@example.org', $addrs);
+        $this->assertContains('t.test@example.org', $addrs);
+        $this->assertContains('g.wrobel@example.org', $addrs);
+        $this->assertContains('gunnar@example.org', $addrs);
+
+        $addrs = $this->ldap->addrsForIdOrMail('test@example.org');
+        $this->assertNoError($addrs);
+        $this->assertContains('test@example.org', $addrs);
+        $this->assertContains('t.test@example.org', $addrs);
+    }
+
+    /**
+     * Test retrieving a UID for a primary mail.
+     *
+     * @return NULL
+     */
+    public function testUidForMailAddress()
+    {
+        $uid = $this->ldap->uidForMailAddress('wrobel@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForMailAddress('test@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Test Test,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForMailAddress('gunnar@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $uid = $this->ldap->uidForMailAddress('wrobel');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+    }
+
+    /**
+     * Test retrieving a UID for an attribute.
+     *
+     * @return NULL
+     */
+    public function testUidForAttr()
+    {
+        $uid = $this->ldap->uidForAttr('alias', 'g.wrobel@example.org');
+        $this->assertNoError($uid);
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+    }
+
+    /**
+     * Test group membership testing.
+     *
+     * @return NULL
+     */
+    public function testMemberOfGroupAddress()
+    {
+        $uid = $this->ldap->uidForMailAddress('g.wrobel@example.org');
+        $this->assertNoError($uid);
+        $member = $this->ldap->memberOfGroupAddress($uid, 'group@example.org');
+        $this->assertNoError($member);
+        $this->assertTrue($member);
+
+        $member = $this->ldap->memberOfGroupAddress(
+            $this->ldap->uidForMailAddress('test@example.org'),
+            'group@example.org');
+        $this->assertNoError($member);
+        $this->assertTrue($member);
+
+        $member = $this->ldap->memberOfGroupAddress(
+            $this->ldap->uidForMailAddress('somebody@example.org'),
+            'group@example.org');
+        $this->assertNoError($member);
+        $this->assertFalse($member);
+    }
+
+    /**
+     * Test group fetching.
+     *
+     * @return NULL
+     */
+    public function testGetGroups()
+    {
+        $filter = '(&(objectClass=kolabGroupOfNames)(member='
+            . Horde_LDAP::quote('cn=The Administrator,dc=example,dc=org') . '))';
+        $result = $this->ldap->_search($filter, array());
+        $this->assertNoError($result);
+        $this->assertTrue(!empty($result));
+
+        $entry = $this->ldap->_firstEntry($result);
+        $this->assertNoError($entry);
+        $this->assertTrue(!empty($entry));
+
+        $uid = $this->ldap->_getDn($entry);
+        $this->assertNoError($uid);
+        $this->assertTrue(!empty($uid));
+
+        $entry = $this->ldap->_nextEntry($entry);
+        $this->assertNoError($entry);
+        $this->assertTrue(empty($entry));
+
+        $entries = $this->ldap->_getDns($result);
+        $this->assertNoError($entries);
+        $this->assertTrue(!empty($entries));
+
+        $groups = $this->ldap->getGroups('cn=The Administrator,dc=example,dc=org');
+        $this->assertNoError($groups);
+        $this->assertTrue(!empty($groups));
+
+        $groups = $this->ldap->getGroups($this->ldap->uidForMailAddress('g.wrobel@example.org'));
+        $this->assertNoError($groups);
+        $this->assertContains('cn=group@example.org,dc=example,dc=org', $groups);
+
+        $groups = $this->ldap->getGroups($this->ldap->uidForMailAddress('test@example.org'));
+        $this->assertNoError($groups);
+        $this->assertContains('cn=group@example.org,dc=example,dc=org', $groups);
+
+        $groups = $this->ldap->getGroups('nobody');
+        $this->assertNoError($groups);
+        $this->assertTrue(empty($groups));
+
+    }
+
+    /**
+     * Test parsing of LDAP filters.
+     *
+     * @return NULL
+     */
+    public function testFilterParse()
+    {
+        $db = &Horde_Kolab_Server::factory('test', array());
+
+        $a = $db->_parse('(a=b)');
+        $this->assertNoError($a);
+        $this->assertEquals(array('att' => 'a', 'log' => '=', 'val' => 'b'),
+                            $a);
+
+        $a = $db->_parse('(&(a=b)(c=d))');
+        $this->assertNoError($a);
+        $this->assertEquals(array('op' => '&', 'sub' => array(
+                                      array('att' => 'a', 'log' => '=', 'val' => 'b'),
+                                      array('att' => 'c', 'log' => '=', 'val' => 'd'),
+                                  )), $a);
+
+        $a = $db->_parse('(&(a=1)(|(b=2)(c=3)))');
+        $this->assertNoError($a);
+        $this->assertEquals(array('op' => '&', 'sub' => array(
+                                      array('att' => 'a', 'log' => '=', 'val' => '1'),
+                                      array('op' => '|', 'sub' =>
+                                            array(
+                                                array('att' => 'b', 'log' => '=', 'val' => '2'),
+                                                array('att' => 'c', 'log' => '=', 'val' => '3'),
+                                            )))), $a);
+
+        $a = $db->_parseSub('(!(x=2))(b=1)');
+        $this->assertNoError($a);
+        $this->assertEquals(array(array('op' => '!', 'sub' =>
+                                        array(
+                                            array('att' => 'x', 'log' => '=', 'val' => '2'),
+                                        )
+                                  ),
+                                  array('att' => 'b', 'log' => '=', 'val' => '1'),
+                            ), $a);
+
+        $a = $db->_parse('(&(!(x=2))(b=1))');
+        $this->assertNoError($a);
+        $this->assertEquals(array('op' => '&', 'sub' => array(
+                                      array('op' => '!', 'sub' =>
+                                            array(
+                                                array('att' => 'x', 'log' => '=', 'val' => '2'),
+                                            )
+                                      ),
+                                      array('att' => 'b', 'log' => '=', 'val' => '1'),
+                                  )), $a);
+
+    }
+
+    /**
+     * Test searching in the simulated LDAP data.
+     *
+     * @return NULL
+     */
+    public function testSearch()
+    {
+        $db = &Horde_Kolab_Server::factory('test',
+                                           array('data' =>
+                                                 array(
+                                                     'cn=a' => array(
+                                                         'dn' => 'cn=a',
+                                                         'data' => array(
+                                                             'a' => '1',
+                                                             'b' => '1',
+                                                             'c' => '1',
+                                                         )
+                                                     ),
+                                                     'cn=b' => array(
+                                                         'dn' => 'cn=b',
+                                                         'data' => array(
+                                                             'a' => '1',
+                                                             'b' => '2',
+                                                             'c' => '2',
+                                                         )
+                                                     ),
+                                                     'cn=c' => array(
+                                                         'dn' => 'cn=c',
+                                                         'data' => array(
+                                                             'a' => '1',
+                                                             'b' => '2',
+                                                             'c' => '3',
+                                                         )
+                                                     ),
+                                                     'cn=d' => array(
+                                                         'dn' => 'cn=d',
+                                                         'data' => array(
+                                                             'a' => '2',
+                                                             'b' => '2',
+                                                             'c' => '1',
+                                                         )
+                                                     ),
+                                                 )
+                                           )
+        );
+
+        $a = $db->_search('(c=1)');
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=a',
+                    'data' => array(
+                        'a' => '1',
+                        'b' => '1',
+                        'c' => '1',
+                    )
+                ),
+                array(
+                    'dn' => 'cn=d',
+                    'data' => array(
+                        'a' => '2',
+                        'b' => '2',
+                        'c' => '1',
+                    )
+                ),
+            ),
+            $a
+        );
+
+        $a = $db->_search('(c=3)');
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=c',
+                    'data' => array(
+                        'a' => '1',
+                        'b' => '2',
+                        'c' => '3',
+                    )
+                ),
+            ),
+            $a
+        );
+
+        $a = $db->_search('(c=3)', array('a'));
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=c',
+                    'data' => array(
+                        'a' => '1',
+                    )
+                ),
+            ),
+            $a
+        );
+
+        $a = $db->_search('(&(a=1)(b=2))', array('a', 'b'));
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=b',
+                    'data' => array(
+                        'a' => '1',
+                        'b' => '2',
+                    )
+                ),
+                array(
+                    'dn' => 'cn=c',
+                    'data' => array(
+                        'a' => '1',
+                        'b' => '2',
+                    )
+                ),
+            ),
+            $a
+        );
+
+        $a = $db->_search('(&(b=2))', array('b'));
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=b',
+                    'data' => array(
+                        'b' => '2',
+                    )
+                ),
+                array(
+                    'dn' => 'cn=c',
+                    'data' => array(
+                        'b' => '2',
+                    )
+                ),
+                array(
+                    'dn' => 'cn=d',
+                    'data' => array(
+                        'b' => '2',
+                    )
+                ),
+            ),
+            $a
+        );
+
+        $a = $db->_search('(!(b=2))', array('a', 'b'));
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=a',
+                    'data' => array(
+                        'a' => '1',
+                        'b' => '1',
+                    )
+                ),
+            ),
+            $a
+        );
+
+        $a = $db->_search('(&(!(x=2))(b=1))', array('b'));
+        $this->assertNoError($a);
+        $this->assertEquals(
+            array(
+                array(
+                    'dn' => 'cn=a',
+                    'data' => array(
+                        'b' => '1',
+                    )
+                ),
+            ),
+            $a
+        );
+    }
+
+}