<?php
/**
 * Custom implementation of Horde_Share class for managing Ansel
 * Galleries as Horde shared objects. This class is independent from
 * Horde_Share for now so it can handle hierarchical shares which are
 * being phased out of the main Share code.
 *
 * @author  Duck <duck@obala.net>
 * @since   Ansel 0.1
 * @package Ansel
 */
class Ansel_Shares {

    /**
     * Handle for the current database connection.
     *
     * @var DB
     */
    var $_db;

    /**
     * Handle for the current database connection, used for writing. Defaults
     * to the same handle as $db if a separate write database is not required.
     *
     * @var DB
     */
    var $_write_db;

    /**
     * Table
     *
     * @var string
     */
    var $_table = 'ansel_shares';

    /**
     * Constructor.
     */
    function Ansel_Shares($app = null)
    {
        if (!is_null($app)) {
            $this->_table = $app . '_shares';
        }

        $this->_connect();

        Horde::callHook('_horde_hook_share_init', array($this, $app));
    }

    /**
     * Return a Ansel_Gallery_Share object corresponding to the
     * given name/ID.
     *
     * @param integer $id  The ID of the gallery to retrieve.
     *
     * @return object Ansel_Gallery_Share  The gallery share object
     *                                     corresponding to the given name.
     */
    function &getGalleryShare($id)
    {
        if (!isset($this->_cache[$id])) {
            if ($GLOBALS['conf']['ansel_cache']['usecache'] &&
                ($gallery = $GLOBALS['cache']->get('Ansel_Gallery_Share' . $id, $GLOBALS['conf']['cache']['default_lifetime'])) !== false) {

                $this->_cache[$id] = unserialize($gallery);
            } else {
                $this->_cache[$id] = &$this->getShareById($id);
                if ($GLOBALS['conf']['ansel_cache']['usecache'] && !is_a($this->_cache[$id], 'PEAR_Error')) {
                    $GLOBALS['cache']->set('Ansel_Gallery_Share' . $id, serialize($this->_cache[$id]));
                }
            }

            if (!is_a($this->_cache[$id], 'PEAR_Error')) {
                $this->_cache[$id]->setShareOb($this);
            }
        }

        return $this->_cache[$id];
    }

    /**
     * Get storage table
     */
    function getTable()
    {
        return $this->_table;
    }

    /**
     * Refetence to write db
     */
    function &getWriteDb()
    {
        return $this->_write_db;
    }

    /**
     * Finds out if the share has user set
     */
    function _hasUsers($info)
    {
        return ($info == 2 || $info == 6);
    }

    /**
     * Finds out if the share has user set
     */
    function _hasGroups($info)
    {
        return ($info == 3 || $info == 6);
    }

    /**
     * Get users permissions
     *
     * @param array $share Share data array
     */
    function _getShareUsers(&$share)
    {
        if ($this->_hasUsers($share['perm_extra'])) {
            $query = 'SELECT user_uid, perm FROM ' . $this->_table . '_users WHERE share_id = ?';
            $result = $this->_db->query($query, array($share['share_id']));
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            } elseif (!empty($result)) {
                while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
                    $share['perm']['users'][$row['user_uid']] = (int)$row['perm'];
                }
            }
        }
    }

    /**
     * Get groups permissions
     *
     * @param array $share Share data array
     */
    function _getShareGroups(&$share)
    {
        if ($this->_hasGroups($share['perm_extra'])) {

            // Get groups permissions
            $query = 'SELECT group_uid, perm FROM ' . $this->_table . '_groups WHERE share_id = ?';
            $result = $this->_db->query($query, array($share['share_id']));
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            } elseif (!empty($result)) {
                while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
                    $share['perm']['groups'][(int)$row['group_uid']] = (int)$row['perm'];
                }
            }
        }
    }

    /**
     * Returns the DataTree name for the given ID.
     *
     * @param integer $id  The ID of the DataTreeObject.
     *
     * @return string  The name of the DataTreeObject.
     */
    function getName($id)
    {
        $query = 'SELECT share_name FROM ' . $this->_table . ' WHERE share_id = ?';
        return $this->_db->getOne($query, $id);
    }

    /**
     * Returns the datatree_id from the given DataTree name.
     *
     * @param string $name  The DataTreeObject name.
     *
     * @return mixed  The datatree_id | PEAR_Error
     */
     function getIdFromName($name)
     {
        $query = 'SELECT share_id FROM ' . $this->_table . ' WHERE share_name = ?';
        return $this->_db->getOne($query, $name);
     }

    /**
     * Return a new gallery share object.
     *
     * @param string $name  Gallery's DataTree name.
     *
     * @return Ansel_Gallery_Share  A new gallery share object.
     */
    function &newGalleryShare($name)
    {
        if (empty($name)) {
            return PEAR::raiseError(_("Gallery names must be non-empty"));
        }

        $galleryShare = new Ansel_Gallery_Share(array('share_name' => $name));
        $galleryShare->setShareOb($this);

        return $galleryShare;
    }

    /**
     * Adds a new Ansel_Gallery_Share to the object backend.
     *
     * @param Ansel_Gallery_Share $gallery  The gallery share to add.
     */
    function addGalleryShare(&$galleryShare)
    {
        if (!is_a($galleryShare, 'Ansel_Gallery_Share')) {
            return PEAR::raiseError('Must be Ansel_Gallery_Share objects or extend that class.');
        }

        $result = $galleryShare->save();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $parent = $this->getParent($galleryShare);
        // Check if we are adding a gallery under datatree root
        if (is_a($parent, 'PEAR_Error')) {
            return true;
        }

        return $GLOBALS['ansel_db']->exec('UPDATE ansel_galleries SET gallery_has_subgalleries = 1 WHERE gallery_id = ' . $parent->data['gallery-id']);
    }

    /**
     * Returns an array of criteria for querying shares.
     * @access protected
     *
     * @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 returned to those who
     *                             have these attribute values.
     * @param string $parent      The parent gallery to start looking in.
     * @param boolean $allLevels  Return all levels, or just the direct
     *                            children of $parent? Defaults to all levels.
     *
     * @return string  The criteria string for fetching this user's shares.
     */
    function _getShareCriteria($userid, $perm = PERMS_SHOW, $attributes = null,
                                $parent = DATATREE_ROOT, $allLevels = true)
    {
        static $criteria;

        $key = $userid . $perm . $parent . $allLevels . (is_array($attributes) ? serialize($attributes) : $attributes);
        if (isset($criteria[$key])) {
            return $criteria[$key];
        }

        $query = ' FROM ' . $this->_table . ' s ';
        $where = '';

        if (!empty($userid)) {

            // (owner == $userid)
            $where .= 's.share_owner = ' . $this->_db->quote($userid);

            // (name == perm_creator and val & $perm)
            $where .= ' OR s.perm_creator & ' . $perm;

            // (name == perm_creator and val & $perm)
            $where .= ' OR s.perm_default & ' . $perm;

            if ($GLOBALS['conf']['share']['no_sharing'] !== true) {
                // (name == perm_users and key == $userid and val & $perm)
                $query .= ' LEFT JOIN ' . $this->_table . '_users AS u ON u.share_id = s.share_id';
                $where .= ' OR ( u.user_uid = ' .  $this->_db->quote($userid)
                        . ' AND u.perm & ' . $perm . ')';

                // If the user has any group memberships, check for those also.
                require_once 'Horde/Group.php';
                $group = &Group::singleton();
                $groups = $group->getGroupMemberships($userid, true);
                if (!is_a($groups, 'PEAR_Error') && $groups) {
                    // (name == perm_groups and key in ($groups) and val & $perm)
                    $query .= ' LEFT JOIN ' . $this->_table . '_groups AS g ON g.share_id = g.share_id';
                    $where .= ' OR ( g.group_uid IN ("' . implode('","', array_keys($groups)) . '")'
                            . ' AND g.perm & ' . $perm .')';
                }
            }

        } else {

            $where = 's.perm_guest & ' . $perm;

        }

        if (is_array($attributes)) {
            // Build attribute/key filter.
            foreach ($attributes as $key => $value) {
                $where .= ' AND ' . $key . ' = ' . $this->_db->quote($value);
            }
        } elseif (!empty($attributes)) {
            // Restrict to shares owned by the user specified in the
            // $attributes string.
            $where = ' s.share_owner = ' . $this->_db->quote($attributes);
        }

        // Add filtering by parent, and for one or all levels.
        if ($parent != DATATREE_ROOT) {
            $parts = explode(':', $parent);
            $parents = '';
            $pstring = '';
            foreach ($parts as $part) {
                $pstring .= (empty($pstring) ? '' : ':') . $part;
                $pid = $this->getIdFromName($pstring);
                if (is_a($pid, 'PEAR_Error')) {
                    return $pid;
                }
                $parents .= ':' . $pid;
            }

            if ($allLevels) {
                $where_parent = ' AND (share_parents = ' . $this->_db->quote($parents)
                        . ' OR share_parents LIKE ' . $this->_db->quote($parents . ':%') . ')';
            } else {
                $where_parent = ' AND s.share_parents = ' . $this->_db->quote($parents);
            }
        } elseif (!$allLevels) {
            $where_parent = " AND s.share_parents = ''";
        }

        if (empty($where_parent)) {
            $criteria[$key] = $query . ' WHERE ' . $where;
        } else {
            $criteria[$key] = $query . ' WHERE (' . $where . ')' . $where_parent;
        }

        return $criteria[$key];
    }

    /**
     * Return a list of users who have galleries with the given permissions
     * for the current user.
     *
     * @param integer $perm       The level of permissions required.
     * @param string $parent      The parent gallery to start looking in.
     * @param boolean $allLevels  Return all levels, or just the direct
     *                            children of $parent? Defaults to all levels.
     * @param integer $from       The gallery to start listing at.
     * @param integer $count      The number of galleries to return.
     *
     * @return array  List of users.
     */
    function listOwners($perm = PERMS_SHOW, $parent = DATATREE_ROOT, $allLevels = true,
                        $from = 0, $count = 0)
    {
        $sql = 'SELECT DISTINCT(s.share_owner) '
                . $this->_getShareCriteria(Auth::getAuth(), $perm, null, $parent, $allLevels);

        if ($count) {
            $sql = $this->_db->modifyLimitQuery($sql, $from, $count);
            if (is_a($sql, 'PEAR_Error')) {
                return $sql;
            }
        }

        $allowners = $this->_db->getCol($sql);

        $owners = array();
        foreach ($allowners as $owner) {
            if ($this->countShares(Auth::getAuth(), $perm, $owner, $parent,
                                   $allLevels)) {

                $owners[] = $owner;
            }
        }

        return $owners;
    }

    /**
     * Count the number of users who have galleries with the given permissions
     * for the current user.
     *
     * @param integer $perm       The level of permissions required.
     * @param string $parent      The parent gallery to start looking in
     * @param boolean $allLevels  Return all levels, or just the direct
     *                            children of $parent? Defaults to all levels.
     *
     * @return integer  Number of users.
     */
    function countOwners($perm = PERMS_SHOW, $parent = DATATREE_ROOT, $allLevels = true)
    {
        $sql = 'SELECT COUNT(DISTINCT(s.share_owner)) '
            . $this->_getShareCriteria(Auth::getAuth(), $perm, null, $parent, $allLevels);

        return $this->_db->getOne($sql);
    }

    /**
     * Return a list of categories containing galleries with the given
     * permissions for the current user.
     *
     * @param integer $perm   The level of permissions required.
     * @param integer $from   The gallery to start listing at.
     * @param integer $count  The number of galleries to return.
     *
     * @return array  List of categories.
     */
    function listCategories($perm = PERMS_SHOW, $from = 0, $count = 0)
    {
        $all_categories = Prefs_CategoryManager::get();
        $all_categories[] = '';

        $categories = array();
        $item = 0;
        foreach ($all_categories as $category) {
            if ($count) {
                $item++;
                if ($item <= $from) {
                    continue;
                }
                if ($item - $from > $count) {
                    continue;
                }
            }

            if ($this->countShares(Auth::getAuth(), $perm,
                                   array('gallery_category' => $category))) {

                $categories[] = $category;
            }
        }

        return $categories;
    }

    /**
     * Count the number of categories containing galleries with the given
     * permissions for the current user.
     *
     * @param integer $perm  The level of permissions required.
     *
     * @return integer  Number of categories.
     */
    function countCategories($perm = PERMS_SHOW)
    {
        return count($this->listCategories());
    }

    /**
     * Returns an Ansel_Gallery_Share object corresponding to the given
     * share name, with the details retrieved appropriately.
     *
     * @param string $name  The name of the gallery to retrieve.
     *
     * @return object Ansel_Gallery_Share  The requested gallery share.
     */
    function &getShare($name)
    {
        $query = 'SELECT share_id, share_name, share_owner, perm_creator, perm_default, perm_extra,'
                . ' perm_guest, `attribute_gallery-id` FROM ' . $this->_table . ' WHERE share_name = ?';
        $data = $this->_db->getRow($query, array($name), DB_FETCHMODE_ASSOC);
        if (is_a($data, 'PEAR_Error')) {
            return $data;
        } elseif (empty($data)) {
            return PEAR::RaiseError(sprintf(_("Share %s does not exists"), $name));
        }

        // Get users permissions
        $this->_getShareUsers($data);

        // Get users permissions
        $this->_getShareGroups($data);

        $share = &new Ansel_Gallery_Share($data);
        return $share;
    }

    /**
     * Returns an Ansel_Gallery_Share object corresponding to the given
     * unique ID, with the details retrieved appropriately.
     *
     * @param string $cid  The id of the gallery to retrieve.
     *
     * @return object Ansel_Gallery_Share The requested gallery.
     */
    function &getShareById($id)
    {
        $params = array($id);
        $query = 'SELECT share_id, share_name, share_owner, perm_creator, perm_default, perm_extra,'
                . ' perm_guest, `attribute_gallery-id` FROM ' . $this->_table . ' WHERE share_id = ?';
        $data = $this->_db->getRow($query, $params, DB_FETCHMODE_ASSOC);
        if (is_a($data, 'PEAR_Error')) {
            return $data;
        } elseif (empty($data)) {
            return PEAR::RaiseError(sprintf(_("Share id %d does not exists"), $id));
        }

        // Get users permissions
        $this->_getShareUsers($data);

        // Get users permissions
        $this->_getShareGroups($data);

        $share = &new Ansel_Gallery_Share($data);
        return $share;
    }

    /**
     * Returns an array of Ansel_Gallery_Share objects corresponding to the
     * given set of unique IDs, with the details retrieved appropriately.
     *
     * @param array $cids  The array of ids to retrieve.
     *
     * @return array  The requested Ansel_Gallery_Share objects.
     */
    function &getShares($ids)
    {
        $shares = array();

        $query = 'SELECT share_id, share_name, share_owner, perm_creator, perm_default, perm_extra,'
                . ' perm_guest, `attribute_gallery-id` FROM ' . $this->_table
                . ' WHERE share_id IN (' . implode(', ', $ids) . ')';
        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        } elseif (empty($result)) {
            return array();
        }

        $groups = array();
        $users = array();
        while ($share = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
            $shares[(int)$share['share_id']] = $share;
            if ($this->_hasUsers($share['perm_extra'])) {
                $users[] = (int)$share['share_id'];
            }
            if ($this->_hasGroups($share['perm_extra'])) {
                $groups[] = (int)$share['share_id'];
            }
        }

        // Get users permissions
        if (!empty($users)) {
            $query = 'SELECT share_id, user_uid, perm FROM ' . $this->_table . '_users '
                    . ' WHERE share_id IN (' . implode(', ', $users) . ')';
            $result = $this->_db->query($query);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            } elseif (!empty($result)) {
                while ($share = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
                    $shares[$share['share_id']]['perm_users'][$share['user_uid']] = (int)$share['perm'];
                }
            }
        }

        // Get groups permissions
        if (!empty($groups)) {
            $query = 'SELECT share_id, group_uid, perm FROM ' . $this->_table . '_groups'
                   . ' WHERE share_id IN (' . implode(', ', $groups) . ')';
            $result = $this->_db->query($query);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            } elseif (!empty($result)) {
                while ($share = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
                    $shares[$share['share_id']]['perm_groups'][$share['group_uid']] = (int)$share['perm'];
                }
            }
        }

        $sharelist = array();
        foreach ($shares as $id => $data) {
            $sharelist[$data['share_name']] = &new Ansel_Gallery_Share($data);
        }

        return $sharelist;
    }

    /**
     * Removes a share from the shares system permanently.
     *
     * @param Ansel_Gallery_Share $share  The share to remove.
     *
     * @return boolean|PEAR_Error  PEAR_Error on failure.
     */
    function removeShare(&$share)
    {
        $result = Horde::callHook('_horde_hook_share_remove', array($share),
                                  'horde', false);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        /* Delete cache */
        if ($GLOBALS['conf']['ansel_cache']['usecache']) {
            $GLOBALS['cache']->expire('Ansel_Gallery_Share'. $id);
        }

        /* Get parent share */
        $parent = $this->getParent($share);

        $params = array($share->getId());
        $tables = array($this->_table,
                        $this->_table . '_users',
                        $this->_table . '_groups');
        foreach ($tables as $table) {
            $query = 'DELETE FROM ' . $this->_table . ' WHERE share_id = ?';
            $result = $this->_write_db->query($query, $params);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        // See if we are deleting a root share
        if (is_a($parent, 'PEAR_Error')) {
            return true;
        }

        $sub = $this->hasChildren($parent) ? 1 : 0;
        $sql = 'UPDATE ansel_galleries SET gallery_has_subgalleries = ' . $sub . ' WHERE gallery_id = ' . $parent->data['gallery-id'];

        return $GLOBALS['ansel_db']->exec($sql);
    }

    /**
     * Checks to see if a share has any child shares.
     *
     * @param Ansel_Gallery_Share $share  The share to check for children.
     *
     * @return boolean  True if the specified share has child shares.
     */
    function hasChildren($share)
    {
        if (!is_a($share, 'Ansel_Gallery_Share')) {
            return PEAR::raiseError(_("Invalid data"));
        }

        $id = $share->getId();
        if (!isset($this->_hasChildren[$id])) {
            $sql = 'SELECT COUNT(*) FROM ' . $this->_table . ' WHERE share_parent LIKE ?';
            $this->_hasChildren[$id] = (bool)$this->_db->getOne($sql, '%' . $id , '%');
        }

        return $this->_hasChildren[$id];
    }

    /**
     * Returns a share's direct parent object.
     *
     * @param string $share  Get the parent of this share.
     *
     * @return object Ansel_Gallery_Share The parent share, if it exists.
     */
    function &getParent($child)
    {
        $parents = $child->get('parent');

        if (empty($parent)) {
            return PEAR::raiseError(_("Parent does not exist."));
        }

        $parents = explode(':', $parents);

        return $this->getShareById(array_pop($parents));
    }

    /**
     * Checks if a share exists in the system.
     *
     * @param string $share  The share to check.
     *
     * @return boolean  True if the share exists, false otherwise.
     */
    function exists($share)
    {
        $query = 'SELECT COUNT(share_name) FROM ' . $this->_table . ' WHERE share_name = ?';
        return $this->_db->getOne($query, array($share));
    }

    /**
     * Returns the number of all gallery_ids 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.
     * @param string  $parent       The parent share to start searching at.
     * @param boolean $allLevels    Return all levels, or just the direct
     *                              children of $parent? Defaults to all
     *                              levels.
     * @param integer $from         The share to start listing at.
     * @param integer $count        The number of shares to return.
     * @param string  $sortby_name  Attribute name to use for sorting.
     * @param string  $sortby_key   Attribute key to use for sorting.
     * @param integer $direction    Sort direction:
     *                                0 - ascending
     *                                1 - descending
     *
     * @return mixed  An array of Ansel_Gallery ids | PEAR_Error
     */
    function countGalleryIds($userid, $perm = PERMS_SHOW, $attributes = null,
                           $parent = DATATREE_ROOT, $allLevels = true)
    {
        $sql = 'SELECT COUNT(s.`attribute_gallery-id` )'
                    . $this->_getShareCriteria($userid, $perm, $attributes, $parent, $allLevels);

        return $this->_db->getOne($sql);
    }

    /**
     * Returns an array of all gallery_ids 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.
     * @param string  $parent       The parent share to start searching at.
     * @param boolean $allLevels    Return all levels, or just the direct
     *                              children of $parent? Defaults to all
     *                              levels.
     * @param integer $from         The share to start listing at.
     * @param integer $count        The number of shares to return.
     * @param string  $sortby_name  Attribute name to use for sorting.
     * @param string  $sortby_key   Attribute key to use for sorting.
     * @param integer $direction    Sort direction:
     *                                0 - ascending
     *                                1 - descending
     *
     * @return mixed  An array of Ansel_Gallery ids | PEAR_Error
     */
    function getGalleryIds($userid, $perm = PERMS_SHOW, $attributes = null,
                           $parent = DATATREE_ROOT, $allLevels = true,
                           $from = 0, $count = 0, $sortby_name = null,
                           $sortby_key = null, $direction = 0)
    {
        $sql = 'SELECT s.`attribute_gallery-id` '
                    . $this->_getShareCriteria($userid, $perm, $attributes, $parent, $allLevels);

        if ($count) {
            $sql = $this->_db->modifyLimitQuery($sql, $from, $count);
            if (is_a($sql, 'PEAR_Error')) {
                return $sql;
            }
        }

        return $this->_db->getCol($sql);
    }

    /**
     * Returns the count 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.
     * @param string  $parent      The parent share to start searching at.
     * @param boolean $allLevels   Return all levels, or just the direct
     *                             children of $parent? Defaults to all levels.
     *
     * @return integer  Number of shares the user has access to.
     */
    function countShares($userid, $perm = PERMS_SHOW, $attributes = null,
                         $parent = DATATREE_ROOT, $allLevels = true)
    {
        $query = 'SELECT COUNT(share_id) '
                . $this->_getShareCriteria($userid, $perm, $attributes, $parent, $allLevels);

        return $this->_db->getOne($query);
    }

    /**
     * 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.
     * @param string  $parent       The parent share to start searching at.
     * @param boolean $allLevels    Return all levels, or just the direct
     *                              children of $parent? Defaults to all
     *                              levels.
     * @param integer $from         The share to start listing at.
     * @param integer $count        The number of shares to return.
     * @param string  $sortby_name  Attribute name to use for sorting.
     * @param string  $sortby_key   Attribute key to use for sorting.
     * @param integer $direction    Sort direction:
     *                                0 - ascending
     *                                1 - descending
     *
     * @return mixed  An array of Ansel_Gallery_Share objects | PEAR_Error
     */
    function &listShares($userid, $perm = PERMS_SHOW, $attributes = null,
                         $parent = DATATREE_ROOT, $allLevels = true, $from = 0,
                         $count = 0, $sortby_name = null, $sortby_key = null,
                         $direction = 0)
    {

        $shares = array();
        $query = 'SELECT s.* '
                . $this->_getShareCriteria($userid, $perm, $attributes, $parent, $allLevels)
                . ' ORDER BY s.share_name ASC';

        $result = $this->_db->query($query);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        } elseif (empty($result)) {
            return array();
        }

        $users = array();
        $groups = array();
        while ($share = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
            $shares[(int)$share['share_id']] = $share;
            if ($this->_hasUsers($share['perm_extra'])) {
                $users[] = (int)$share['share_id'];
            }
            if ($this->_hasGroups($share['perm_extra'])) {
                $groups[] = (int)$share['share_id'];
            }
        }

        // Get users permissions
        if (!empty($users)) {
            $query = 'SELECT share_id, user_uid, perm FROM ' . $this->_table . '_users '
                    .' WHERE share_id IN (' . implode(', ', $users) . ')';
            $result = $this->_db->query($query);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            } elseif (!empty($result)) {
                while ($share = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
                    $shares[$share['share_id']]['perm_users'][$share['user_uid']] = (int)$share['perm'];
                }
            }
        }

        // Get groups permissions
        if (!empty($groups)) {
            $query = 'SELECT share_id, group_uid, perm FROM ' . $this->_table . '_groups'
                    . ' WHERE share_id IN (' . implode(', ', $groups) . ')';
            $result = $this->_db->query($query);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            } elseif (!empty($result)) {
                while ($share = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
                    $shares[$share['share_id']]['perm_groups'][(int)$share['group_uid']] = (int)$share['perm'];
                }
            }
        }

        $sharelist = array();
        foreach ($shares as $id => $data) {
            $sharelist[$data['share_name']] = &new Ansel_Gallery_Share($data);
        }
        unset($shares);

        $result = Horde::callHook('_horde_hook_share_list',
                                  array($userid, $perm, $attributes, $sharelist),
                                  'horde', false);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return $sharelist;
    }

    /**
     * TODO
     *
     * @see Perms::getPermissions
     *
     * @param TODO
     * @param TODO
     *
     * @return TODO
     */
    function getPermissions($share, $user = null)
    {
        if (!is_a($share, 'Ansel_Gallery_Share')) {
            $share = &$this->getShare($share);
        }

        $perm = $share->getPermission();
        return $GLOBALS['perms']->getPermissions($perm, $user);
    }


    /**
     * Attempts to open a persistent connection to the sql server.
     *
     * @return boolean  True on success; exits (Horde::fatal()) on error.
     */
    protected function _connect()
    {
        $_params = $GLOBALS['conf']['sql'];

        if (!isset($_params['database'])) {
            $_params['database'] = '';
        }
        if (!isset($_params['username'])) {
            $_params['username'] = '';
        }
        if (!isset($_params['hostspec'])) {
            $_params['hostspec'] = '';
        }

        /* Connect to the sql server using the supplied parameters. */
        require_once 'DB.php';
        $this->_write_db = DB::connect($_params,
                                       array('persistent' => !empty($_params['persistent'])));
        if ($this->_write_db  instanceof PEAR_Error) {
            Horde::fatal($this->_write_db, __FILE__, __LINE__);
        }

        /* Set DB portability options. */
        switch ($this->_write_db->phptype) {
        case 'mssql':
            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
            break;
        default:
            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
        }

        /* Check if we need to set up the read DB connection seperately. */
        if (!empty($_params['splitread'])) {
            $params = array_merge($_params, $_params['read']);
            $this->_db = DB::connect($params,
                                    array('persistent' => !empty($params['persistent'])));
            if ($this->_db  instanceof PEAR_Error) {
                Horde::fatal($this->_db, __FILE__, __LINE__);
            }

            /* Set DB portability options. */
            switch ($this->_db->phptype) {
            case 'mssql':
                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
                break;
            default:
                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
            }
        } else {
            /* Default to the same DB handle for the writer too. */
            $this->_db = $this->_write_db;
        }

        return true;
    }

}

/**
 * Class for storing Galleries.
 *
 * @author  Duck <duck@obala.net>
 * @since   Horde 3.2
 * @package Ansel
 */
class Ansel_Gallery_Share {

    /**
     * The Horde_Share object which this share came from - needed for updating
     * data in the backend to make changes stick, etc.
     *
     * @var Horde_Share
     */
    var $_shareOb;

    /**
     * The actual storage object that holds the data.
     *
     * @var mixed
     */
    var $data;

    /**
     * Constructor.
     *
     * @param sqlObject_Share $sqlObject  A sqlObject_Share
     *                                              instance.
     */
    function Ansel_Gallery_Share($data)
    {
        $this->data['perm'] = array(
                    'users' => isset($data['perm_users']) ? $data['perm_users'] : array(),
                    'type' => 'matrix',
                    'default' => isset($data['perm_default']) ? (int)$data['perm_default'] : 0,
                    'guest' => isset($data['perm_guest']) ? (int)$data['perm_guest'] : 0,
                    'creator' => isset($data['perm_creator']) ? (int)$data['perm_creator'] : 0,
                    'groups' => isset($data['perm_groups']) ? $data['perm_groups'] : array());

        unset($data['perm_creator'], $data['perm_guest'], $data['perm_default'],
                $data['perm_users'], $data['perm_groups']);

        $this->data = array_merge($data, $this->data);
    }

    /**
     * Associates a Share object with this share.
     *
     * @param Horde_Share $shareOb  The Share object.
     */
    function setShareOb(&$shareOb)
    {
        if (!is_a($shareOb, 'Ansel_Shares')) {
            return PEAR::raiseError('This object needs a Ansel_Shares instance as storage handler!');
        }

        $this->_shareOb = &$shareOb;
    }

    /**
     * Returns a child's direct parent ID.
     *
     * @return mixed  The unique ID of the parent or PEAR_Error on error.
     */
    function getParent()
    {
        return $this->_shareOb->getParent($this);
    }

    /**
     * Sets an attribute value in this object.
     *
     * @param string $attribute  The attribute to set.
     * @param mixed $value       The value for $attribute.
     * @param boolean $update                  Should the share be saved
     *                                         after this operation?
     *
     * @return mixed  True if setting the attribute did succeed, a PEAR_Error
     *                otherwise.
     */
    function set($attribute, $value, $update = false)
    {
        if ($attribute == 'owner') {
            $this->data['share_owner'] = $value;
        } else {
            $this->data['attribute_' . $attribute] = $value;
        }

        if ($update) {
            $db = &$this->_shareOb->getWriteDb();
            $table = $this->_shareOb->getTable();

            $query = 'UPDATE ' . $table . ' SET `attribute_' . $attribute . '` = ? WHERE share_id = ?';
            return $db->query($query, array($value, $this->getId()));
        }
    }

    /**
     * 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 get($attribute)
    {
        if ($attribute == 'owner') {
            return $this->data['share_owner'];
        } elseif (isset($this->data['attribute_' . $attribute])) {
            return $this->data['attribute_' . $attribute];
        } else {
            return null;
        }
    }

    /**
     * Returns the ID of this share.
     *
     * @return string  The share's ID.
     */
    function getId()
    {
        return $this->data['share_id'];
    }

    /**
     * Returns the name of this share.
     *
     * @return string  The share's name.
     */
    function getName()
    {
        return $this->data['share_name'];
    }

    /**
     * Saves the current attribute values.
     */
    function save()
    {
        $db = &$this->_shareOb->getWriteDb();
        $table = $this->_shareOb->getTable();

        if (empty($this->data['share_id'])) {
            $this->data['share_id'] = $db->nextId($table);

            // parents?
            $this->data['share_parents'] = '';
            if (strpos($this->data['share_name'], ':') !== false) {
                $parts = explode(':', $this->data['share_name']);
                $this->data['share_name'] = array_pop($parts);
                $pstring = '';
                foreach ($parts as $par) {
                    $pstring .= (empty($pstring) ? '' : ':') . $par;
                    $pid = $this->_shareOb->getIdFromName($pstring);
                    if (is_a($pid, 'PEAR_Error')) {
                        return $pid;
                    }
                    $this->data['share_parents'] .= ':' . $pid;
                }
            }
        }

        $params = array();
        $query = 'REPLACE INTO ' . $table . ' SET ';
        foreach ($this->data as $key => $value) {
            if ($key != 'perm' && $key != 'perm_extra') {
                $query .= '`' . $key . '` = ?, ';
                $params[] = $value;
            }
        }

        if (!empty($this->data['perm']['creator'])) {
            $query .= 'perm_creator = ?, ';
            $params[] = $this->data['perm']['creator'];
        }

        if (!empty($this->data['perm']['default'])) {
            $query .= 'perm_default = ?, ';
            $params[] = $this->data['perm']['default'];
        }

        if (!empty($this->data['perm']['guest'])) {
            $query .= 'perm_guest = ?, ';
            $params[] = $this->data['perm']['guest'];
        }

        // permison count
        $query .= 'perm_extra = ?, ';
        $extra = 0;
        if (!empty($this->data['perm']['users'])) {
            $extra = 2;
        }
        if (!empty($this->data['perm']['groups'])) {
            $extra = $extra ? 6 : 3;
        }
        $params[] = $extra;
        $query = substr($query, 0, -2);
        $result = $db->query($query, $params);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $query = 'DELETE FROM ' . $table . '_users WHERE share_id = ?';
        $result = $db->query($query, array($this->data['share_id']));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (!empty($this->data['perm']['users'])) {
            $data = array();
            foreach ($this->data['perm']['users'] as $user => $perm) {
                $data[] = array($this->data['share_id'], $user, $perm);
            }
            $query = 'INSERT INTO ' . $table . '_users (share_id, user_uid, perm) VALUES (?, ?, ?)';
            $sth = $db->prepare($query);
            $result = $db->executeMultiple($sth, $data);
            if ($result instanceof PEAR_Error) {
                return $result;
            }
        }

        $query = 'DELETE FROM ' . $table . '_groups WHERE share_id = ?';
        $result = $db->query($query, array($this->data['share_id']));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if (!empty($this->data['perm']['groups'])) {
            $data = array();
            foreach ($this->data['perm']['groups'] as $user => $perm) {
                $data[] = array($this->data['share_id'], $user, $perm);
            }
            $query = 'INSERT INTO ' . $table . '_groups (share_id, group_uid, perm) VALUES (?, ?, ?)';
            $sth = $db->prepare($query);
            $result = $db->executeMultiple($sth, $data);
            if ($result instanceof PEAR_Error) {
                return $result;
            }
        }


        return true;
    }

    /**
     * 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 event.
     *
     * @return boolean  Whether or not $userid has $permission.
     */
    function hasPermission($userid, $permission, $creator = null)
    {
        if ($userid == $this->data['share_owner']) {
            return true;
        }

        return $GLOBALS['perms']->hasPermission($this->getPermission(),
                                                $userid, $permission, $creator);
    }

    /**
     * Sets the permission of this share.
     *
     * @param sqlObject_Permission $perm  Permission object.
     * @param boolean $update                  Should the share be saved
     *                                         after this operation?
     *
     * @return boolean  True if no error occured, PEAR_Error otherwise
     */
    function setPermission(&$perm, $update = true)
    {
        $this->data['perm'] = $perm->getData();
        if ($update) {
            return $this->save();
        }
        return true;
    }

    /**
     * Returns the permission of this share.
     *
     * @return sqlObject_Permission  Permission object that represents the
     *                                    permissions on this share
     */
    function &getPermission()
    {
        if (class_exists('SQLObject_Permission')) {
            $perm = new SQLObject_Permission($this->getName());
        } else {
            $perm = new DataTreeObject_Permission($this->getName());
        }
        $perm->data = isset($this->data['perm'])
            ? $this->data['perm']
            : array();

        return $perm;
    }
}

/**
 * Class for interfacing with back end data storage.
 *
 * @author Duck <duck@obala.net>
 *
 * @package Ansel
 */
class Ansel_Storage_share extends Ansel_Storage {


    /**
     * Return the count of galleries that the user has specified permissions to
     * and that match any of the requested attributes.
     *
     * @param string  $userid       The user to check access for.
     * @param integer $perm         The level of permissions to require for a
     *                              gallery to return it.
     * @param mixed   $attributes   Restrict the galleries counted to those
     *                              matching $attributes. An array of
     *                              attribute/values pairs or a gallery owner
     *                              username.
     * @param string  $parent       The parent share to start counting at.
     * @param boolean $allLevels    Return all levels, or just the direct
     *                              children of $parent? Defaults to all levels.
     */
    function countGalleries($userid, $perm = PERMS_SHOW, $attributes = null,
                            $parent = null, $allLevels = true)
    {
        // We need to fetch the shares limited by userid and parent first,
        // then if we have further attributes to filter on, perform a count
        // query against the galleries table with appropriate clauses.
        if (!empty($parent)) {
            $gallery = $this->getGallery($parent);
            $parent = $this->shares->getName($gallery->_galleryShare->getId());
        } else {
            $parent = DATATREE_ROOT;
        }

        // $attributes should only ever contain a share owner when passed
        // to Ansel_Share:: methods.
        if (is_array($attributes) && isset($attributes['owner'])) {
            $owner = $attributes['owner'];
            unset($attributes['owner']);
        } elseif (!is_null($attributes) && !is_array($attributes)) {
            $owner = $attributes;
        } else {
            $owner = null;
        }

        if (is_array($attributes) && count($attributes)) {

            $galleryIds = $this->shares->getGalleryIds(Auth::getAuth(), $perm, $owner, $parent, $allLevels);
            if (is_a($galleryIds, 'PEAR_Error')) {
                $GLOBALS['notification']->push(
                    sprintf(_("An error occurred counting galleries: %s"),
                            $galleryIds->getMessage()), 'horde.error');

                Horde::logMessage($galleryIds->getMessage(), __FILE__, __LINE__,
                                PEAR_LOG_ERR);
                return 0;
            } elseif (empty($galleryIds)) {
                return 0;
            }

            $sql = 'SELECT COUNT(*) FROM ansel_galleries WHERE gallery_id IN (' . str_repeat('?, ', count($galleryIds) - 1) . '?) ';
            $values = array_merge($galleryIds, array_values($attributes));
            $keys = array_keys($attributes);
            foreach ($keys as $key) {
                $sql .= 'AND gallery_' . $key . '= ? ';
            }

            $q = $this->_db->prepare($sql);
            $result = $q->execute($values);
            $count = $result->fetchOne();

        } else {

            $count = $this->shares->countGalleryIds(Auth::getAuth(), $perm, $owner, $parent, $allLevels);

        }

        return $count;
    }

    /**
     * Retrieves the current user's gallery list from storage.
     *
     * @param integer $perm         The level of permissions to require for a
     *                              gallery to return it.
     * @param mixed   $attributes   Restrict the galleries counted to those
     *                              matching $attributes. An array of
     *                              attribute/values pairs or a gallery owner
     *                              username.
     * @param string  $parent       The parent gallery to start listing at.
     * @param boolean $allLevels    Return all levels, or just the direct
     *                              children of $parent? Defaults to all levels.
     * @param integer $from         The gallery to start listing at.
     * @param integer $count        The number of galleries to return.
     * @param string  $sort_by      The field to order the results by.
     * @param integer $direction    Sort direction:
     *                               0 - ascending
     *                               1 - descending
     *
     * @return mixed An array of Ansel_Gallery objects | PEAR_Error
     */
    function listGalleries($perm = PERMS_SHOW,
                           $attributes = null,
                           $parent = null,
                           $allLevels = true,
                           $from = 0,
                           $count = 0,
                           $sort_by = null,
                           $direction = 0)
    {
        global $notification, $prefs;

        if (!is_null($parent)) {
            $gallery = $this->getGallery($parent);
            if (is_a($gallery, 'PEAR_Error')) {
                return $gallery;
            }
            $parent = $this->shares->getName($gallery->_galleryShare->getId());
        } else {
            $parent = DATATREE_ROOT;
        }

        // $attributes should only contain a gallery owner when passed to
        // Ansel_Share:: methods.
        if (is_array($attributes) && isset($attributes['owner'])) {
            $owner = $attributes['owner'];
            unset($attribtues['owner']);
        } elseif (!is_null($attributes) && !is_array($attributes)) {
            $owner = $attributes;
        } else {
            $owner = null;
        }
;
        $galleryIds = $this->shares->getGalleryIds(Auth::getAuth(), $perm,
                                                   $owner, $parent, $allLevels,
                                                   $from, $count);

        if (is_a($galleryIds, 'PEAR_Error')) {
            $notification->push(
                sprintf(_("An error occurred listing galleries: %s"),
                        $galleryIds->getMessage()), 'horde.error');

            Horde::logMessage($galleryIds->getMessage(), __FILE__, __LINE__,
                              PEAR_LOG_ERR);
            return array();
        } elseif (empty($galleryIds)) {
            return array();
        }

        $sql = 'SELECT gallery_id, gallery_default, gallery_default_type, '
               . 'gallery_default_prettythumb, gallery_style, gallery_desc, '
               . 'gallery_name, gallery_category, gallery_last_modified, '
               . 'gallery_date_created, gallery_share_id, gallery_images, '
               . 'gallery_has_subgalleries, gallery_slug FROM ansel_galleries '
               . 'WHERE gallery_id IN ('
               . str_repeat('?, ', count($galleryIds) - 1) . '?) ';

        if (is_array($attributes) && count($attributes)) {
            $values = array_merge($galleryIds, array_values($attributes));
            $keys = array_keys($attributes);
            foreach ($keys as $key) {
                $sql .= 'AND gallery_' . $key . '= ? ';
            }
        } else {
            $values = $galleryIds;
        }
        if (!is_null($sort_by)) {
            $sql .= 'ORDER BY ' . 'gallery_' . $sort_by . (($direction == 1) ? ' DESC' : '');
        }

        $q = $this->_db->prepare($sql);
        if (is_a($q, 'PEAR_Error')) {
            return $q;
        }

        $results = $q->execute($values);
        if (is_a($results, 'PEAR_Error')) {
            return $results;
        }

        $galleries = array();
        while ($row = $results->fetchRow(MDB2_FETCHMODE_ASSOC)) {
            $atts = $this->_fromDriver($row);
            $galleries[$atts['id']] = new Ansel_Gallery($atts);
        }

        return $galleries;
    }
}