<?php
/**
 * $Horde: framework/Share/Share/kolab.php,v 1.42.2.17 2008/06/05 07:39:46 wrobel Exp $
 *
 * @package Horde_Share
 */

/** Kolab */
require_once 'Horde/Kolab.php';

/**
 * Horde-specific annotations on the imap folder have this prefix.
 */
define('HORDE_ANNOT_SHARE_ATTR', HORDE_ANNOT_ROOT . 'share-');

/**
 * Marks a share without a name. These shares are still invalid
 */
define('KOLAB_SHARE_INVALID', 'KOLAB_SHARE_INVALID');

/**
 * Horde_Share_kolab:: provides the kolab backend for the horde share driver.
 *
 * Copyright 2004-2008 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  Stuart Binge <omicron@mighty.co.za>
 * @author  Gunnar Wrobel <wrobel@pardus.de>
 * @since   Horde 3.2
 * @package Horde_Share
 */
class Horde_Share_kolab extends Horde_Share {

    /**
     * Has the list of mailboxes been modified?
     *
     * @var boolean
     */
    var $dirty = true;

    /**
     * The shares for this app
     *
     * @var array
     */
    var $_shares = null;

    /**
     * Our Kolab_IMAP_Connection object, used to communicate with the
     * Cyrus server.
     *
     * @var Kolab_IMAP_Connection
     */
    var $_imap = null;

    /**
     * Caches the default share name
     *
     * @var string
     */
    var $_default = null;

    /**
     * Initializes the object.
     */
    function __wakeup()
    {
        if (empty($GLOBALS['conf']['kolab']['enabled'])) {
            Horde::fatal('You must enable the kolab settings to use the Kolab Share driver.', __FILE__, __LINE__);
        }

        // Connect to the IMAP server
        $this->_imap = &Kolab_IMAP_Connection::singleton(
            Kolab::getServer('imap'),
            $GLOBALS['conf']['kolab']['imap']['port'],
            true, false);

        // Login using the current Horde credentials
        $result = $this->_imap->connect(Auth::getAuth(),
                                        Auth::getCredential('password'));
        if (is_a($result, 'PEAR_Error')) {
            Horde::logMessage($result, __FILE__, __LINE__);
        }

        foreach (array_keys($this->_cache) as $name) {
            $this->_cache[$name]->setShareOb($this);
        }

        parent::__wakeup();
    }

    /**
     * Returns the properties that need to be serialized.
     *
     * @return array  List of serializable properties.
     */
    function __sleep()
    {
        $properties = get_object_vars($this);
        unset($properties['_sortList'], $properties['_imap']);
        $properties = array_keys($properties);
        return $properties;
    }

    /**
     * Create a default share for the current app
     *
     * @return string The share ID of the new default share.
     */
    function createDefaultShare()
    {
        $this->_loadShares();
        if (!empty($this->_default)) {
            return $this->_default;
        }

        $shares = $this->listShares(Auth::getAuth());
        if (is_a($shares, 'PEAR_Error')) {
            return $shares;
        }

        if (!empty($shares)) {
            $shares[0]->set('default', true);
            $shares[0]->save();
            return $shares[0]->getId();
        }

        $share = $this->newShare(Auth::getAuth());
        if (is_a($share, 'PEAR_Error')) {
            return $share;
        }
        $share->set('name', $this->getDefaultShareName());
        $result = $this->addShare($share);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        return $result->getName();
    }

    /**
     * Returns a Horde_Share_Object_kolab object of the request folder.
     *
     * @param string $object  The share to fetch.
     *
     * @return Horde_Share_Object_kolab  The share object.
     */
    function &_getShare($object)
    {
        if (empty($object)) {
            $error = PEAR::raiseError('No object requested.');
            return $error;
        }

        // Get the available folders for this app
        $shares = $this->_loadShares();
        if (is_a($shares, 'PEAR_Error')) {
            return $shares;
        }

        // Is the specified object really a share for this app?
        if (!in_array($object, $shares)) {
            return PEAR::raiseError(sprintf(_("Share \"%s\" does not exist."), $object));
        }

        $object = $this->_parseShareId($object);

        // Create the object from the folder name
        $share = new Horde_Share_Object_kolab($object);
        $share->setShareOb($this);

        if ($object == Auth::getAuth()) {
            $this->_loadShares();
            $object = &$this->_default;
        }

        $share->setFolder($object, null);
        /* FIXME: Is this call necessary at all? You should try to access a share that
         *        cannot be accessed and see what happens.
         *$result = $share->accessible();
         *if (is_a($result, 'PEAR_Error')) {
         *return $result;
         *}
         *if (!$result) {
         *return PEAR::raiseError(sprintf(_("Share \"%s\" not accessible."), $object));
         *}
         */
        return $share;
    }

    /**
     * Returns a Horde_Share_Object_kolab object of the requested folder.
     *
     * @param string $id  The id of the share to fetch.
     *
     * @return Horde_Share_Object_kolab  The share object.
     */
    function &_getShareById($id)
    {
        // Try to use a cache in order to make this somewhat faster.
        if (isset($this->_shareMap[$id])) {
            return $this->_shareMap[$id];
        }
        $sharelist = $this->_loadShares();
        foreach ($sharelist as $shareid) {
            $share = &$this->_getShare($shareid);
            if (is_a($share, 'PEAR_Error')) {
                return $share;
            }
            $this->_shareMap[$share->getId()] = $share->getName();
            if ($share->getId() == $id) {
                return $share;
            }
        }
        return PEAR::raiseError(sprintf(_("Share ID \"%s\" not found."), $id));
    }

    /**
     * Returns an array of Horde_Share_Object_kolab objects corresponding to
     * the requested folders.
     *
     * @param string $ids  The ids of the shares to fetch.
     *
     * @return array  An array of Horde_Share_Object_kolab objects.
     */
    function &_getShares($ids)
    {
        $objects = array();
        foreach ($ids as $id) {
            $result = &$this->_getShare($id);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
            $objects[$result->getName()] = &$result;
        }
        return $objects;
    }

    /**
     * Lists *all* shares for the current app/share, regardless of
     * permissions.
     *
     * Currently not implemented in this class.
     *
     * @return array  All shares for the current app/share.
     */
    function &_listAllShares()
    {
        $shares = array();
        return $shares;
    }

    /**
     * Returns an array of all shares that $userid has access to.
     *
     * @param string $userid     The userid of the user to check access for.
     * @param integer $perm      The level of permissions required.
     * @param mixed $attributes  Restrict the shares counted to those
     *                           matching $attributes. An array of
     *                           attribute/values pairs or a share owner
     *                           username.
     *
     * @return array  The shares the user has access to.
     */
    function &_listShares($userid, $perm = PERMS_SHOW, $attributes = null,
                          $from = 0, $count = 0, $sort_by = null,
                          $direction = 0)
    {
        $key = serialize(array($this->_app, $userid, $perm, $attributes));
        if (empty($this->_listCache[$key]) || $this->dirty) {
            $sharelist = $this->_loadShares();
            if (is_a($sharelist, 'PEAR_Error')) {
                return $sharelist;
            }

            $shares = array();
            foreach ($sharelist as $id) {
                $share = &$this->getShare($id);
                if (is_a($share, 'PEAR_Error')) {
                    return $share;
                }

                $keep = true;
                if (!$share->hasPermission($userid, $perm)) {
                    $keep = false;
                }
                if (isset($attributes) && $keep) {
                    if (is_array($attributes)) {
                        foreach ($attributes as $key => $value) {
                            if (!$share->get($key) == $value) {
                                $keep = false;
                                break;
                            }
                        }
                    } elseif (!$share->get('owner') == $attributes) {
                        $keep = false;
                    }
                }
                if ($keep) {
                    $shares[] = $id;
                }
            }
            $this->_listCache[$key] = $shares;
            $this->dirty = false;
        }

        return $this->_listCache[$key];
    }

    /**
     * Returns the number of shares that $userid has access to.
     *
     * @since Horde 3.2
     *
     * @param string $userid     The userid of the user to check access for.
     * @param integer $perm      The level of permissions required.
     * @param mixed $attributes  Restrict the shares counted to those
     *                           matching $attributes. An array of
     *                           attribute/values pairs or a share owner
     *                           username.
     *
     * @return integer  The number of shares
     */
    function _countShares($userid, $perm = PERMS_SHOW, $attributes = null)
    {
        $shares = $this->_listShares($userid, $perm, $attributes);
        if (is_a($share, 'PEAR_Error')) {
            return $share;
        }

        return count($shares);
    }

    /**
     * Returns a new share object.
     *
     * @param string $name  The share's name.
     *
     * @return Horde_Share_Object_kolab  A new share object.
     */
    function &_newShare($name)
    {
        $id = $this->_parseShareId($name);
        $storageObject = new Horde_Share_Object_kolab($id);
        $storageObject->setShareOb($this);
        return $storageObject;
    }

    /**
     * Adds a share to the shares system.
     *
     * The share must first be created with Horde_Share_kolab::_newShare(),
     * and have any initial details added to it, before this function is
     * called.
     *
     * @param Horde_Share_Object_kolab $share  The new share object.
     */
    function _addShare(&$share)
    {
        // Ensure that the share list has been initialized
        $this->_loadShares();

        $share_id = $share->get('folder');
        if ($share_id == KOLAB_SHARE_INVALID) {
            return PEAR::raiseError(_("Cannot add this share! The name has not yet been set."));
        }

        $result = $this->_imap->exists($share_id);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        if ($result) {
            return PEAR::raiseError(sprintf(_("Unable to add %s: destination folder already exists"), $share_id));
        }

        $share->setFolder($share_id);

        $result = $this->_imap->create($share_id);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        // Get the application constants
        $app_consts = Kolab::getAppConsts($this->_app);
        if (is_a($app_consts, 'PEAR_Error')) {
            return $app_consts;
        }
        $folder_type = $app_consts['folder_type'] . ($share->get('default') ? '.default' : '');
        $result = $this->_imap->setAnnotation(KOLAB_ANNOT_FOLDER_TYPE, array('value.shared' => $folder_type), $share_id);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        // Save permissions
        $result = $share->save();
        if (is_a($result, 'PEAR_Error')) {
            // If saving did not work, the folder should be removed again
            $this->_imap->delete($share_id);
            return $result;
        }

        array_push($this->_shares, $share->getName());

        if ($share->get('default')) {
            $this->_default = $share->get('folder');
        }

        return $share;
    }

    /**
     * Removes a share from the shares system permanently.
     *
     * @param Horde_Share_Object_kolab $share  The share to remove.
     */
    function _removeShare(&$share)
    {
        $share_id = $share->getName();
        $folder = $share->get('folder');
        $result = $this->_imap->exists($folder);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if ($result === true) {
            $result = $this->_imap->delete($folder);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        $shares = $this->_loadShares();
        $new_shares = array();
        foreach ($shares as $oldshare) {
            if ($oldshare != $share_id) {
                array_push($new_shares, $oldshare);
            }
        }

        $this->_shares = $new_shares;
    }

    /**
     * Checks if a share exists in the system.
     *
     * @param string $share  The share to check.
     *
     * @return boolean  True if the share exists.
     */
    function _exists($object)
    {
        if (empty($object)) {
            return false;
        }

        $object = $this->_parseShareId($object);

        if ($object == Auth::getAuth()) {
            $this->_loadShares();
            return !empty($this->_default);
        }

        // Get the available folders for this app
        $shares = $this->_loadShares();
        if (is_a($shares, 'PEAR_Error')) {
            return false;
        }

        // Is the specified object really a share for this app?
        if (!in_array($object, $shares)) {
            return false;
        }
        return true;
    }

    /**
     * Converts the share ID depending on the application we are called from.
     *
     * @param string $id  The unique ID of the share.
     *
     * @return string  The converted ID.
     */
    function _parseShareId($id)
    {
        if ($this->_app == 'ingo') {
            list(, $id) = explode(':', $id, 2);
        }
        if ($id != Auth::getAuth()) {
            return rawurldecode($id);
        }
        return $id;
    }

    /**
     * Builds the share ID depending on the application we are called from.
     *
     * @param string $id  The unique ID of the share.
     *
     * @return string  The correct, application dependant ID.
     */
    function buildShareId($id)
    {
        if ($this->_app == 'ingo') {
            return 'kolab:' . $id;
        }
        return $id;
    }

    /**
     * Returns the IMAP connection.
     *
     * @return Kolab_IMAP_Connection  The IMAP connection.
     */
    function &getImap()
    {
        return $this->_imap;
    }

    /**
     * Fills the $_shares property with the IDs of all shares accessible to
     * the user and returns this list.
     *
     * @return array  The shares accessible to the user.
     */
    function _loadShares()
    {
        if (!isset($this->_shares)) {
            // Get the application constants
            $app_consts = Kolab::getAppConsts($this->_app);
            if (is_a($app_consts, 'PEAR_Error')) {
                return $app_consts;
            }

            // Obtain a list of all folders the current user has access to
            $folder_list = $this->_imap->getMailboxes();
            if (is_a($folder_list, 'PEAR_Error')) {
                return $folder_list;
            }

            // We're only interested in the shares of the specific resource
            // type for the current application, so filter out the rest
            $shares = array();

            // We will also check that the folder name actually looks
            // like an IMAP folder name.
            $folders_preg = ';shared\.|INBOX[/]?|user/[^/]+[/]?[^@]+(@.*)?;';

            foreach ($folder_list as $folder) {

                // Check for a correct IMAP folder name
                preg_match($folders_preg, $folder, $matches);
                if (!isset($matches)) {
                    continue;
                }

                // Retrieve the folder annotation
                $annotation = $this->_imap->getAnnotation(KOLAB_ANNOT_FOLDER_TYPE, 'value.shared', $folder);
                if (is_a($annotation, 'PEAR_Error')) {
                    return $annotation;
                }

                // If there is no annotation then we treat it as a standard
                // mail folder (i.e. we ignore it).
                if (empty($annotation)) {
                    continue;
                }

                // If the folder is of the correct type then we add it to our
                // list of folders that should correspond to a share. Here we
                // get any other additional information we may need, such as
                // whether the folder was previously mapped to a share, its
                // ACL, its owner (if it is a shared folder) and its display
                // name.
                $type = explode('.', $annotation);
                if ($type[0] == $app_consts['folder_type']) {
                    if (isset($type[1]) && $type[1] == 'default' &&
                        substr($folder, 0, 6) == 'INBOX/' && !$this->_default) {
                        $this->_default = $folder;
                        array_push($shares, $this->buildShareId(Auth::getAuth()));
                    } else {
                        array_push($shares, $this->buildShareId(rawurlencode($folder)));
                    }
                }
            }
            $this->_shares = $shares;
        }

        return $this->_shares;
    }

    /**
     * Returns the default share name for the current application.
     *
     * @return string  The default share name.
     */
    function getDefaultShareName()
    {
        switch ($this->_app) {
        case 'turba':
            return _("Contacts");
        case 'mnemo':
            return _("Notes");
        case 'kronolith':
            return _("Calendar");
        case 'nag':
            return _("Tasks");
        case 'ingo':
            return _("Filters");
        case 'h-prefs':
            return _("Preferences");
        }
    }

    /**
     * Resets the share name cache.
     */
    function unsetShares()
    {
        $this->_shares = null;
    }

}

/**
 * Extension of the Horde_Share_Object class for handling Kolab share
 * information.
 *
 * @author  Stuart Binge <omicron@mighty.co.za>
 * @author  Gunnar Wrobel <wrobel@pardus.de>
 * @since   Horde 3.2
 * @package Horde_Share
 */
class Horde_Share_Object_kolab extends Horde_Share_Object {

    /**
     * The folder name of this share.
     *
     * These names have the same requirements as other object names - they
     * must be unique, etc.
     *
     * @var string
     */
    var $_folder;

    /**
     * A cache for the folder attributes.
     *
     * @var array
     */
    var $data;

    /**
     * The permission handler for the share
     *
     * @var ImapFolder_Permission
     */
    var $_perm;

    /**
     * Constructor.
     *
     * Sets the folder name.
     *
     * @param string $id  The share id.
     */
    function Horde_Share_Object_kolab($id)
    {
        $this->_folder = KOLAB_SHARE_INVALID;

        // We actually ignore the random id string that all horde apps provide
        // as initial name and wait for a set('name', 'xyz') call. But we want
        // to know if we should create a default share.
        if ($id == Auth::getAuth()) {
            $this->data['default'] = true;
        } else {
            $this->data['default'] = false;
        }
    }

    /**
     * Associates a Share object with this share.
     *
     * @param Horde_Share $shareOb  The Share object.
     */
    function setShareOb(&$shareOb)
    {
        $result = parent::setShareOb($shareOb);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (isset($this->_perm)) {
            $this->_perm->setImap($shareOb->getImap());
        }
    }

    /**
     * Sets the folder name for this storage object.
     *
     * @param string $folder  Name of the IMAP folder.
     * @param boolean $force  Enforce setting the folder.
     * @param array $perms    The permissions of the folder if they are known.
     */
    function setFolder($folder, $force = false, $perms = null)
    {
        if ($this->_folder == KOLAB_SHARE_INVALID || $force) {
            $this->_folder = $folder;
            if (isset($this->_perm)) {
                $this->_perm->setFolder($folder);
            } else {
                if (empty($perms)) {
                    if ($this->exists()) {
                        // The permissions are unknown but the folder exists
                        // -> discover permissions
                        $perms = null;
                    } else {
                        $perms = array(
                            'users' => array(
                                Auth::getAuth() => PERMS_SHOW | PERMS_READ |
                                                   PERMS_EDIT | PERMS_DELETE));
                    }
                }
                $this->_perm = &new ImapFolder_Permission($folder,
                                                          $this->_shareOb->getImap(),
                                                          $this->getOwner(), $perms);
            }
        }
    }

    /**
     * Checks to see if a user has a given permission.
     *
     * @param string $userid       The userid of the user.
     * @param integer $permission  A PERMS_* constant to test for.
     * @param string $creator      The creator of the shared object.
     *
     * @return boolean  Whether or not $userid has $permission.
     */
    function hasPermission($userid, $permission, $creator = null)
    {
        if ($userid == $this->getOwner()) {
            return true;
        }

        $perm = &$this->getPermission();
        return $perm->hasPermission($userid, $permission, $creator);
    }

    /**
     * Returns the permissions from this storage object.
     *
     * @return ImapFolder_Permission  The permissions on the share.
     */
    function &getPermission()
    {
        if (!isset($this->_perm)) {
            $this->_perm = &new ImapFolder_Permission($this->_folder,
                                                      $this->_shareOb->getImap(),
                                                      $this);
        }
        return $this->_perm;
    }

    /**
     * Sets the permissions on the share.
     *
     * @param ImapFolder_Permission $perm  Permission object to store on the
     *                                     object.
     * @param boolean $update              Save the updated information?
     *
     * @return mixed  True on success or a PEAR_Error on failure.
     */
    function setPermission(&$perm, $update = true)
    {
        if (!is_a($perm, 'ImapFolder_Permission')) {
            return PEAR::raiseError('The permissions for this share must be specified as an instance of the ImapFolder_Permission class!');
        }

        $this->_perm = &$perm;

        if ($update) {
            return $this->save();
        }

        return true;
    }

    /**
     * Returns the ID of this share.
     *
     * @return string  The share's ID.
     */
    function _getId()
    {
        return $this->_getName();
    }

    /**
     * Returns the name of this share.
     *
     * @return string  The share's name.
     */
    function _getName()
    {
        if ($this->get('default')) {
            return $this->_shareOb->buildShareId(Auth::getAuth());
        }
        return $this->_shareOb->buildShareId(rawurlencode($this->get('folder')));
    }

    /**
     * Returns the title of this object.
     *
     * @param string $title  Name of the IMAP folder.
     *
     * @return string  The object title.
     */
    function getTitle($title = null)
    {
        if (empty($title)) {
            $title = $this->get('folder');
        }

        if (substr($title, 0, 6) == 'INBOX/') {
            $title = substr($title, 6);
        }
        $title = str_replace('/', ':', $title);

        return String::convertCharset($title, 'UTF7-IMAP');
    }

    /**
     * Returns whether the share exists.
     *
     * @return boolean  True if the share exists.
     */
    function exists($share = null)
    {
        if (empty($share)) {
            $share = $this->_folder;
        }
        // Verify that the folder can be accessed
        $imap = &$this->_shareOb->getImap();
        $result = $imap->exists($share);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        return $result;
    }

    /**
     * Returns whether the share is accessible.
     *
     * @return boolean  True if the share can be accessed.
     */
    function accessible($share = null)
    {
        if (empty($share)) {
            $share = $this->_folder;
        }
        // Verify that the folder can be accessed
        $imap = &$this->_shareOb->getImap();
        $result = $imap->select($share);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        return $result;
    }

    /**
     * Deletes this object from the backend permanently.
     */
    function delete()
    {
        return $this->_shareOb->remove($this);
    }

    /**
     * Sets the name of this object.
     *
     * @param string $name  The new name of the object.
     */
    function setTitle($name)
    {
        return true;
    }

    /**
     * Is this a default share?
     *
     * @return mixed  Boolean that indicates the default status or a PEAR_Error
     *                if the folder annotation cannot be accessed.
     */
    function isDefault()
    {
        $imap = &$this->_shareOb->getImap();
        $annotation = $imap->getAnnotation(KOLAB_ANNOT_FOLDER_TYPE, 'value.shared', $this->_folder);
        if (is_a($annotation, 'PEAR_Error')) {
            return false;
        }
        if (empty($annotation)) {
            return false;
        }
        $type = explode('.', $annotation);
        return (!empty($type[1]) && $type[1] == 'default');
    }

    /**
     * Returns the owner of the shared folder.
     *
     * @return mixed  The owner or a PEAR_Error.
     */
    function getOwner()
    {
        if (!preg_match(";(shared\.|INBOX[/]?|user/([^/]+)/)([^@]+)(@.*)?;", $this->get('folder'), $matches)) {
            return PEAR::raiseError(sprintf(_("Owner of folder %s cannot be determined."), $this->get('folder')));
        }

        if (substr($matches[1], 0, 6) == 'INBOX/') {
            return Auth::getAuth();
        } elseif (substr($matches[1], 0, 5) == 'user/') {
            $domain = strstr(Auth::getAuth(), '@');
            $user_domain = isset($matches[4]) ? $matches[4] : $domain;
            return $matches[2] . $user_domain;
        } elseif ($matches[1] == 'shared.') {
            return 'anonymous';
        }
    }

    /**
     * Returns one of the attributes of the object, or null if it isn't
     * defined.
     *
     * @param string $attribute  The attribute to retrieve.
     *
     * @return mixed  The value of the attribute, or an empty string.
     */
    function getAttribute($attribute)
    {
        $imap = &$this->_shareOb->getImap();
        if ($attribute == 'desc') {
            $entry = '/comment';
        } else {
            $entry = HORDE_ANNOT_SHARE_ATTR . $attribute;
        }
        $annotation = $imap->getAnnotation($entry, 'value.shared', $this->_folder);
        if (is_a($annotation, 'PEAR_Error') || empty($annotation)) {
            $annotation = '';
        }
        return base64_decode($annotation);
    }

    /**
     * Returns an attribute value from this object.
     *
     * @param string $attribute  The attribute to return.
     *
     * @return mixed  The value for $attribute.
     */
    function _get($attribute)
    {
        if (isset($this->data[$attribute])) {
            return $this->data[$attribute];
        }

        switch ($attribute) {
        case 'owner':
            $this->data['owner'] = $this->getOwner();
            break;

        case 'folder':
            $this->data['folder'] = $this->_folder;
            break;

        case 'name':
            $this->data['name'] = $this->getTitle();
            break;

        case 'params':
            $params = @unserialize($this->getAttribute('params'));
            $default = array('source' => 'kolab',
                             'default' => $this->get('default'),
                             'name' => $this->get('name'));
            if (is_a($params, 'PEAR_Error') || $params == '') {
                $params = $default;
            }
            $this->data['params'] = serialize(array_merge($default, $params));
            break;

        case 'default':
            $this->data['default'] = $this->isDefault();
            break;

        default:
            $annotation = $this->getAttribute($attribute);
            if (is_a($annotation, 'PEAR_Error') || empty($annotation)) {
                $annotation = '';
            }
            $this->data[$attribute] = $annotation;
            break;
        }

        return $this->data[$attribute];
    }

    /**
     * Sets an attribute value in this object.
     *
     * @param string $attribute  The attribute to set.
     * @param mixed $value       The value for $attribute.
     *
     * @return mixed  True if setting the attribute did succeed, a PEAR_Error
     *                otherwise.
     */
    function _set($attribute, $value)
    {
        switch ($attribute) {
        case 'name':
            /* On folder creation of default shares we wish to ignore
             * the names provided by the Horde applications. We use
             * the Kolab default names. */
            if ($this->_folder == KOLAB_SHARE_INVALID && $this->get('default')) {
                $value = 'INBOX/' . $this->_shareOb->getDefaultShareName();
            } else {
                $value = str_replace(':', '/', $value);
                if (substr($value, 0, 5) != 'user/' && substr($value, 0, 7) != 'shared.') {
                    $value = "INBOX/".$value;
                }
            }
            $this->data['folder'] = String::convertCharset($value, NLS::getCharset(), 'UTF7-IMAP');
            $this->data['name'] = $this->getTitle($this->data['folder']);
            break;

        case 'params':
            $value = unserialize($value);
            if (isset($value['default'])) {
                $this->data['default'] = $value['default'];
            }
            break;

        default:
            $this->data[$attribute] = $value;
        }
    }

    /**
     * Saves the current attribute values.
     */
    function _save()
    {
        if ($this->get('folder') != $this->_folder) {
            $new_folder = $this->get('folder');

            if ($this->_folder != KOLAB_SHARE_INVALID) {
                $imap = &$this->_shareOb->getImap();
                $result = $imap->exists($new_folder);
                if (is_a($result, 'PEAR_Error')) {
                    $this->data['name'] = $this->getTitle($this->_folder);
                    return $result;
                }
                if ($result) {
                    $this->data['name'] = $this->getTitle($this->_folder);
                    return PEAR::raiseError(sprintf(_("Unable to rename %s to %s: destination folder already exists"), $this->_folder, $new_folder));
                }

                $result = $imap->rename($this->_folder, $new_folder);
                if (is_a($result, 'PEAR_Error')) {
                    $this->data['name'] = $this->getTitle($this->_folder);
                    return $result;
                }
            }
            $this->setFolder($new_folder, true);

            /* This is not funny anymore: We need to inform the
             * Horde_Share driver that the list cache needs to be
             * refreshed. This is another bad hack. */
            $this->_shareOb->dirty = true;
            $this->_shareOb->unsetShares();
        }

        foreach ($this->data as $attribute => $value) {
            if ($attribute == 'owner') {
                if ($value != $this->getOwner()) {
                    return PEAR::raiseError(_("Cannot modify the owner of a share!"));
                }
            } elseif ($attribute == 'default') {
                $imap = &$this->_shareOb->getImap();
                $annotation = $imap->getAnnotation(KOLAB_ANNOT_FOLDER_TYPE, 'value.shared', $this->_folder);
                if (is_a($annotation, 'PEAR_Error')) {
                    return $annotation;
                }
                $type = explode('.', $annotation);
                if ($value && empty($type[1])) {
                    $result = $imap->setAnnotation(KOLAB_ANNOT_FOLDER_TYPE, array('value.shared' => $type[0] . '.default'), $this->_folder);
                    if (is_a($result, 'PEAR_Error')) {
                        return $result;
                    }
                } elseif (!$value && !empty($type[1]) && $type[1] == 'default') {
                    $result = $imap->setAnnotation(KOLAB_ANNOT_FOLDER_TYPE, array('value.shared' => $type[0]), $this->_folder);
                    if (is_a($result, 'PEAR_Error')) {
                        return $result;
                    }
                }
            } elseif ($attribute == 'name') {
                continue;
            } elseif ($attribute == 'folder') {
                continue;
            } else {
                $imap = &$this->_shareOb->getImap();
                // setAnnotation apparently does not suppoort UTF-8 nor any special characters
                $store = base64_encode($value);
                if ($attribute == 'desc') {
                    $entry = '/comment';
                } else {
                    $entry = HORDE_ANNOT_SHARE_ATTR . $attribute;
                }
                $result = $imap->setAnnotation($entry, array('value.shared' => $store), $this->_folder);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
            }
        }

        // Now the save object permissions
        if (isset($this->_perm)) {
            $result = $this->_perm->save();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        return true;
    }

}

/**
 * A wrapper that handles permissions on an IMAP folder
 *
 * @author  Gunnar Wrobel <wrobel@pardus.de>
 * @author  Stuart Binge <omicron@mighty.co.za>
 * @since   Horde 3.2
 * @package Horde_Perms
 */
class ImapFolder_Permission extends Horde_Permission {

    /**
     * The folder name.
     *
     * @var string
     */
    var $_folder;

    /**
     * The imap connection.
     *
     * @var string
     */
    var $_imap;

    /**
     * A cache for the folder acl settings. The cache holds the permissions
     * in horde compatible format, not in the IMAP permission format.
     *
     * @var string
     */
    var $data;

    /**
     * Owner of the share.
     *
     * @var string
     */
    var $_owner;

    /**
     * The ImapFolder_Permission constructor.
     *
     * @param string                 $folder   The name of the folder that
                                               these permissions belongs to.
     * @param Kolab_IMAP_Connection  $imap     The IMAP connection to the server
     * @param Horde_Storage          $storage  A reference to the storage class
     * @param array                  $perms    A set of initial permissions.
     */
    function ImapFolder_Permission($folder, &$imap, $owner, $perms = null)
    {
        $this->_folder = $folder;
        $this->_imap = &$imap;
        $this->_owner = $owner;

        // Load the permission from the folder now
        if (empty($perms)) {
            $perms = $this->getPerm();
            // Exit on error
            if (is_a($perms, 'PEAR_Error')) {
                return $perms;
            }
        }

        $this->data = $perms;
    }

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

    /**
     * Sets the imap driver for this permission object.
     *
     * @param
     */
    function setImap(&$imap)
    {
        $this->_imap = &$imap;
    }

    /**
     * Sets the folder name for this permission object.
     *
     * @param string $folder  Name of the IMAP folder.
     */
    function setFolder($folder)
    {
        $this->_folder = $folder;
    }

    /**
     * Gets one of the attributes of the object, or null if it isn't defined.
     *
     * @param string $attribute  The attribute to get.
     *
     * @return mixed  The value of the attribute, or null.
     */
    function get($attribute)
    {
        // This object only handles permissions. So only return these
        switch ($attribute) {
        case 'perm':
            return $this->data;
        case 'type':
            return 'matrix';
        default:
            // User requested something other than permissions: return null
            return null;
        }
    }

    /**
     * Gets the current permission of the folder and stores the values in the
     * cache.
     *
     * @return mixed  True on success or a PEAR error if the imap call failed
     */
    function getPerm()
    {
        /* Perform the imap call
         * Check if the getPerm comes from the owner
         * in this case we can use getACL to have all the right of the share
         * Otherwise we just ask the right of the current user for a folder */
        if ($this->_owner == Auth::getAuth()) {
            $acl = $this->_imap->getACL($this->_folder);

            if (is_a($acl, 'PEAR_Error')) {
                Horde::logMessage($acl, __FILE__, __LINE__);
                return array();
            }
        } else {
            $my_rights = $this->_imap->getMyrights($this->_folder);

            if (is_a($my_rights, 'PEAR_Error')) {
                Horde::logMessage($my_rights, __FILE__, __LINE__);
                return array();
            }

            $acl = array();
            $acl[Auth::getAuth()] = $my_rights;
        }

        // Loop through the returned users
        $data = array();
        foreach ($acl as $user => $rights) {
            // Convert the user rights to horde format
            $result = 0;
            for ($i = 0, $j = strlen($rights); $i < $j; $i++) {
                switch ($rights[$i]) {
                case 'l':
                    $result |= PERMS_SHOW;
                    break;
                case 'r':
                    $result |= PERMS_READ;
                    break;
                case 'i':
                    $result |= PERMS_EDIT;
                    break;
                case 'd':
                    $result |= PERMS_DELETE;
                    break;
                }
            }

            // Check for special users
            $name = '';
            switch ($user) {
            case 'anyone':
                $name = 'default';
                break;
            case 'anonymous':
                $name = 'guest';
                break;
            }

            // Did we have a special user?
            if ($name) {
                // Store the converted acl in the cache
                $data[$name] = $result;
                continue;
            }

            // Is it a group?
            if (substr($user, 0, 6) == 'group:') {
                if (!isset($groups)) {
                    require_once 'Horde/Group.php';
                    $groups = &Group::singleton();
                }
                $group_id = $groups->getGroupId(substr($user, 6));
                if (!is_a($group_id, 'PEAR_Error')) {
                    // Store the converted acl in the cache
                    $data['groups'][$group_id] = $result;
                }

                continue;
            }

            // Standard user
            // Store the converted acl in the cache
            $data['users'][$user] = $result;
        }

        return $data;
    }

    /**
     * Saves the current permission values in the cache to the imap folder.
     *
     * @return mixed  True on success or a PEAR error if the imap call failed
     */
    function save()
    {
        // FIXME: If somebody else accessed the folder before us, we will overwrite
        //        the change here.
        $current = $this->getPerm();

        foreach ($this->data as $user => $user_perms) {
            if (is_array($user_perms)) {
                foreach ($user_perms as $userentry => $perms) {
                    if ($user == 'groups') {
                        if (!isset($groups)) {
                            require_once 'Horde/Group.php';
                            $groups = &Group::singleton();
                        }
                        // Convert group id back to name
                        $group_name = $groups->getGroupName($userentry);
                        if (is_a($group_name, 'PEAR_Error')) {
                            return $group_name;
                        }
                        $name = 'group:' . $group_name;
                    } else {
                        $name = $userentry;
                    }
                    $result = $this->savePermission($name, $perms);
                    if (is_a($result, 'PEAR_Error')) {
                        return $result;
                    }
                    unset($current[$user][$userentry]);
                }
            } else {
                switch ($user) {
                case 'default':
                    $name = 'anyone';
                    break;
                case 'guest':
                    $name = 'anonymous';
                    break;
                default:
                    continue;
                }

                $result = $this->savePermission($name, $user_perms);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
                unset($current[$user]);
            }
        }

        // Delete ACLs that have been removed
        foreach ($current as $user => $user_perms) {
            if (is_array($user_perms)) {
                foreach ($user_perms as $userentry => $perms) {
                    if ($user == 'groups') {
                        if (!isset($groups)) {
                            require_once 'Horde/Group.php';
                            $groups = &Group::singleton();
                        }
                        // Convert group id back to name
                        $group_name = $groups->getGroupName($userentry);
                        if (is_a($group_name, 'PEAR_Error')) {
                            return $group_name;
                        }
                        $name = 'group:' . $group_name;
                    } else {
                        $name = $userentry;
                    }

                    $result = $this->_imap->deleteACL($this->_folder, $name);
                    if (is_a($result, 'PEAR_Error')) {
                        return $result;
                    }
                }
            } else {
                switch ($user) {
                case 'default':
                    $name = 'anyone';
                    break;
                case 'guest':
                    $name = 'anonymous';
                    break;
                default:
                    continue;
                }
                $result = $this->_imap->deleteACL($this->_folder, $name);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }
            }
        }

        // Load the permission from the folder again
        $this->data = $this->getPerm();

        return true;
    }

    /**
     * Saves the current permission values in the cache to the imap folder.
     *
     * @return mixed  True on success or a PEAR error if the imap call failed
     */
    function savePermission($user, $perms)
    {
        // Convert the horde permission style to IMAP permissions
        $result = $user == $this->_owner ? 'a' : '';
        if ($perms & PERMS_SHOW) {
            $result .= 'l';
        }
        if ($perms & PERMS_READ) {
            $result .= 'r';
        }
        if ($perms & PERMS_EDIT) {
            $result .= 'iswc';
        }
        if ($perms & PERMS_DELETE) {
            $result .= 'd';
        }

        // Save the ACL
        $result = $this->_imap->setACL($this->_folder, $user, $result);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return true;
    }

    /**
     * Finds out what rights the given user has to this object.
     *
     * @param string $user       The user to check for. Defaults to the current
     *                           user.
     * @param string $creator    The user who created the object.
     *
     * @return mixed  A bitmask of permissions, a permission value, or an array
     *                of permission values the user has, depending on the
     *                permission type and whether the permission value is
     *                ambiguous. False if there is no such permsission.
     */
    function getPermissions($user = null, $creator = null)
    {
        if ($user === null) {
            $user = Auth::getAuth();
        }
        // If $creator was specified, check creator permissions.
        if ($creator !== null) {
            // If the user is the creator see if there are creator
            // permissions.
            if (strlen($user) && $user === $creator &&
                ($perms = $this->getCreatorPermissions()) !== null) {
                return $perms;
            }
        }

        // Check user-level permissions.
        $userperms = $this->getUserPermissions();
        if (isset($userperms[$user])) {
            return $userperms[$user];
        }

        // If no user permissions are found, try group permissions.
        $groupperms = $this->getGroupPermissions();
        if (!empty($groupperms)) {
            require_once 'Horde/Group.php';
            $groups = &Group::singleton();

            $composite_perm = null;
            foreach ($this->data['groups'] as $group => $perm) {
                $result = $groups->userIsInGroup($user, $group);
                if (is_a($result, 'PEAR_Error')) {
                    return $result;
                }

                if ($result) {
                    if ($composite_perm === null) {
                        $composite_perm = 0;
                    }
                    $composite_perm |= $perm;
                }
            }

            if ($composite_perm !== null) {
                return $composite_perm;
            }
        }

        // If there are default permissions, return them.
        if (($perms = $this->getDefaultPermissions()) !== null) {
            return $perms;
        }

        // Otherwise, deny all permissions to the object.
        return false;
    }

    /**
     * Finds out if the user has the specified rights to the given object.
     *
     * @param string $user        The user to check for.
     * @param integer $perm       The permission level that needs to be checked
     *                            for.
     * @param string $creator     The creator of the shared object.
     *
     * @return boolean  True if the user has the specified permissions.
     */
    function hasPermission($user, $perm, $creator = null)
    {
        return ($this->getPermissions($user, $creator) & $perm);
    }

}
