<?php

require_once HORDE_BASE . '/lib/Category.php';

/** Existence of object is known - object is shown to user. */
define('_PERMS_SHOW', 2);

/** Contents of the object can be read. */
define('_PERMS_READ', 4);

/** Contents of the object can be edited. */
define('_PERMS_EDIT', 8);

/** The object can be deleted. */
define('_PERMS_DELETE', 16);

/**
 * The Perms:: class provides the Horde permissions system.
 *
 * $Horde: horde/lib/Perms.php,v 1.45 2003/08/01 17:23:05 chuck Exp $
 *
 * Copyright 2001-2003 Chuck Hagenbuch <chuck@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  Chuck Hagenbuch <chuck@horde.org>
 * @version $Revision: 1.45 $
 * @since   Horde 2.1
 * @package horde.perms
 */
class Perms {

    /**
     * Pointer to a category instance to manage the different
     * permissions.
     *
     * @var object Category $perms
     */
    var $_perms;

    /**
     * Constructor.
     */
    function Perms()
    {
        global $conf;

        if (!isset($conf['category']['driver'])) {
            Horde::fatal(sprintf(_("You must configure a Category backend to use %s."), $GLOBALS['registry']->getParam('name')), __FILE__, __LINE__);
        }
        $driver = $conf['category']['driver'];
        $this->_perms = &Category::singleton($driver,
                                             array_merge(Horde::getDriverConfig('category', $driver),
                                                         array('group' => 'horde.perms')));
    }

    /**
     * Attempts to return a reference to a concrete Perms instance.
     * It will only create a new instance if no Perms instance
     * currently exists.
     *
     * This method must be invoked as: $var = &Perms::singleton()
     *
     * @return object Perms  The concrete Perm reference, or false on an
     *                       error.
     */
    function &singleton()
    {
        static $perm;

        if (!isset($perm)) {
            $perm = &new Perms();
        }

        return $perm;
    }

    /**
     * Return a new permissions object.
     *
     * @param string $name  The perm's name.
     *
     * @return object CategoryObject_Permissions  A new permissions object.
     */
    function &newPermission($name)
    {
        if (empty($name)) {
            return PEAR::raiseError('Permission names must be non-empty');
        }
        $perm = &new CategoryObject_Permission($name);
        $perm->setPermsOb($this);
        return $perm;
    }

    /**
     * Return a CategoryObject_Permission object corresponding to the
     * named perm, with the users and other data retrieved
     * appropriately.
     *
     * @param string $name The name of the perm to retrieve.
     */
    function &getPermission($name)
    {
        /* cache of previous retrieved perms */
        static $permsCache;

        if (!is_array($permsCache)) {
            $permsCache = array();
        }

        if (array_key_exists($name, $permsCache)) {
            return $permsCache[$name];
        }

        $permsCache[$name] = $this->_perms->getCategory($name, 'CategoryObject_Permission');
        if (!is_a($permsCache[$name], 'PEAR_Error')) {
            $permsCache[$name]->setPermsOb($this);
        }
        return $permsCache[$name];
    }

    /**
     * Return a CategoryObject_Permission object corresponding to the
     * given unique ID, with the users and other data retrieved
     * appropriately.
     *
     * @param string $cid  The unique ID of the permission to retrieve.
     */
    function &getPermissionById($cid)
    {
        $perm = $this->_perms->getCategoryById($cid, 'CategoryObject_Permission');
        if (!is_a($perm, 'PEAR_Error')) {
            $perm->setPermsOb($this);
        }
        return $perm;
    }

    /**
     * Add a perm to the perms system. The perm must first be created
     * with Perm::newPermission(), and have any initial users added to
     * it, before this function is called.
     *
     * @param object CategoryObject_Permission $perm The new perm object.
     */
    function addPermission($perm)
    {
        if (!is_a($perm, 'CategoryObject_Permission')) {
            return PEAR::raiseError('Permissions must be CategoryObject_Permission objects or extend that class.');
        }

        return $this->_perms->addCategory($perm);
    }

    /**
     * Store updated data - users, etc. - of a perm to the backend
     * system.
     *
     * @param object CategoryObject_Permission $perm   The perm to update.
     */
    function updatePermission($perm)
    {
        if (!is_a($perm, 'CategoryObject_Permission')) {
            return PEAR::raiseError('Permissions must be CategoryObject_Permission objects or extend that class.');
        }
        return $this->_perms->updateCategoryData($perm);
    }

    /**
     * Remove a perm from the perms system permanently.
     *
     * @param object CategoryObject_Permission $perm   The permission to remove.
     *
     * @param optional boolean force [default = false] Force to remove
     *                         every child
     */
    function removePermission($perm, $force = false)
    {
        if (!is_a($perm, 'CategoryObject_Permission')) {
            return PEAR::raiseError('Permissions must be CategoryObject_Permission objects or extend that class.');
        }

        return $this->_perms->removeCategory($perm->getName(), $force);
    }

    /**
     * Find out what rights the given user has to this object.
     *
     * @param mixed  $permission  The full permission name of the object
     *                            to check the permissions of, or the
     *                            CategoryObject_Permission object.
     * @param string $user        (optional) The user to check for.
     *                            Defaults to Auth::getAuth().
     * @param string $creator     (optional) The user who created the event.
     *
     * @return integer  Any permissions the user has, false if there
     *                  are none.
     */
    function getPermissions($permission, $user = null, $creator = null)
    {
        if (!is_a($permission, 'CategoryObject_Permission')) {
            $permission = &$this->getPermission($permission);
            if (is_a($permission, 'PEAR_Error')) {
                Horde::logMessage($permission, __FILE__, __LINE__);
                return false;
            }
        }

        if (is_null($user)) {
            $user = Auth::getAuth();
        }

        // If this is a guest user, only check guest permissions.
        if (empty($user)) {
            return $permission->getGuestPermissions();
        }

        // Check user-level permissions first.
        $userperms = $permission->getUserPermissions();
        if (!empty($user) && array_key_exists($user, $userperms)) {
            return $userperms[$user];
        }

        // If no user permissions are found, try group permissions.
        if (isset($permission->data['groups']) &&
            is_array($permission->data['groups']) &&
            count($permission->data['groups'])) {
            require_once HORDE_BASE . '/lib/Group.php';
            $groups = &Group::singleton();

            $composite_perm = null;
            foreach ($permission->data['groups'] as $group => $perm) {
                if ($groups->userIsInGroup($user, $groups->getGroupName($group), true)) {
                    if (is_null($composite_perm)) {
                        $composite_perm = 0;
                    }
                    $composite_perm |= $perm;
                }
            }

            if (!is_null($composite_perm)) {
                return $composite_perm;
            }
        }

        // If there is no creator, then assume the current user will
        // be the creator (likely it's an add).
        if (empty($creator)) {
            $creator = Auth::getAuth();
        }

        // If the user is the creator of the event see if there are
        // creator permissions.
        if (!empty($user) && $user == $creator && 
            ($perms = $permission->getCreatorPermissions()) !== null) {
            return $perms;
        }

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

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

    /**
     * Get the unique identifier of this permission.
     *
     * @param object CategoryObject_Permission $permission  The permission object to get the ID of.
     *
     * @return integer  The unique id.
     */
    function getPermissionId($permission)
    {
        return $this->_perms->getCategoryId($permission->getName());
    }

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

    /**
     * Check if a permission exists in the system.
     *
     * @param string $permission  The permission to check.
     *
     * @return boolean  True if the permission exists, false otherwise.
     */
    function exists($permission)
    {
        return $this->_perms->exists($permission);
    }

    /**
     * Get a list of parent permissions.
     *
     * @param string $child The name of the child to retrieve parents for.
     *
     * @return array [child] [parent] with a tree format
     */
    function getParents($child)
    {
        return $this->_perms->getParents($child);
    }

}

/**
 * Extension of the CategoryObject class for storing Permission
 * information in the Categories driver. If you want to store
 * specialized Permission information, you should extend this class
 * instead of extending CategoryObject directly.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @version $Revision: 1.45 $
 * @since   Horde 2.1
 * @package horde.perms
 */
class CategoryObject_Permission extends CategoryObject {

    /**
     * The Perms object which this permission came from - needed for
     * updating data in the backend to make changes stick, etc.
     *
     * @var object Perms $permsOb
     */
    var $_permsOb;

    /**
     * The CategoryObject_Permission constructor. Just makes sure to
     * call the parent constructor so that the perm's name is set
     * properly.
     *
     * @param string $name The name of the perm.
     */
    function CategoryObject_Permission($name)
    {
        parent::CategoryObject($name);
    }

    /**
     * Associates a Perms object with this perm.
     *
     * @param object Perm $permsOb The Perm object.
     */
    function setPermsOb(&$permsOb)
    {
        $this->_permsOb = &$permsOb;
    }

    /**
     * Get the unique identifier of this permission.
     *
     * @return integer  The unique id.
     */
    function getId()
    {
        return $this->_permsOb->getPermissionId($this);
    }

    /**
     * Give a user additional permissions to this object.
     *
     * @param string   $user       The user to grant additional permissions to.
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to add.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function addUserPermission($user, $permission, $update = true)
    {
        if (empty($user)) {
            return;
        }
        if (isset($this->data['users'][$user])) {
            $this->data['users'][$user] |= $permission;
        } else {
            $this->data['users'][$user] = $permission;
        }
        if ($update) {
            $this->_permsOb->updatePermission($this);
        }
    }

    /**
     * Grant guests additional permissions to this object.
     *
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to add.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function addGuestPermission($permission, $update = true)
    {
        if (isset($this->data['guest'])) {
            $this->data['guest'] |= $permission;
        } else {
            $this->data['guest'] = $permission;
        }
        if ($update) {
            $this->_permsOb->updatePermission($this);
        }
    }

    /**
     * Grant creators additional permissions to this object.
     *
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to add.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function addCreatorPermission($permission, $update = true)
    {
        if (isset($this->data['creator'])) {
            $this->data['creator'] |= $permission;
        } else {
            $this->data['creator'] = $permission;
        }
        if ($update) {
            $this->_permsOb->updatePermission($this);
        }
    }

    /**
     * Grant additional default permissions to this object.
     *
     * @param integer $permission  The permission (_PERMS_DELE, etc.) to add.
     * @param boolean $update      (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function addDefaultPermission($permission, $update = true)
    {
        if (isset($this->data['default'])) {
            $this->data['default'] |= $permission;
        } else {
            $this->data['default'] = $permission;
        }
        if ($update) {
            $this->_permsOb->updatePermission($this);
        }
    }

    /**
     * Give a group additional permissions to this object.
     *
     * @param integer  $groupId    The id of the group to grant additional permissions to.
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to add.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function addGroupPermission($groupId, $permission, $update = true)
    {
        if (empty($groupId)) {
            return;
        }

        if (isset($this->data['groups'][$groupId])) {
            $this->data['groups'][$groupId] |= $permission;
        } else {
            $this->data['groups'][$groupId] = $permission;
        }
        if ($update) {
            $this->_permsOb->updatePermission($this);
        }
    }

    /**
     * Remove a permission that a user currently has on this object.
     *
     * @param string   $user       The user to remove the permission from.
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to remove.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function removeUserPermission($user, $permission, $update = true)
    {
        if (empty($user)) {
            return;
        }
        if (isset($this->data['users'][$user])) {
            $this->data['users'][$user] &= ~$permission;
            if (empty($this->data['users'][$user])) {
                unset($this->data['users'][$user]);
            }
            if ($update) {
                $this->_permsOb->updatePermission($this);
            }
        }
    }

    /**
     * Remove a permission that guests currently have on this object.
     *
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to remove.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function removeGuestPermission($permission, $update = true)
    {
        if (isset($this->data['guest'])) {
            $this->data['guest'] &= ~$permission;
            if ($update) {
                $this->_permsOb->updatePermission($this);
            }
        }
    }

    /**
     * Remove a permission that creators currently have on this object.
     *
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to remove.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function removeCreatorPermission($permission, $update = true)
    {
        if (isset($this->data['creator'])) {
            $this->data['creator'] &= ~$permission;
            if ($update) {
                $this->_permsOb->updatePermission($this);
            }
        }
    }

    /**
     * Remove a default permission on this object.
     *
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to remove.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function removeDefaultPermission($permission, $update = true)
    {
        if (isset($this->data['default'])) {
            $this->data['default'] &= ~$permission;
            if ($update) {
                $this->_permsOb->updatePermission($this);
            }
        }
    }

    /**
     * Remove a permission that a group currently has on this object.
     *
     * @param integer  $groupId    The id of the group to remove the permission from.
     * @param constant $permisson  The permission (_PERMS_DELE, etc.) to remove.
     * @param boolean  $update     (optional) Whether to automatically update the
     *                             backend. Defaults to true.
     */
    function removeGroupPermission($groupId, $permission, $update = true)
    {
        if (empty($groupId)) {
            return;
        }

        if (isset($this->data['groups'][$groupId])) {
            $this->data['groups'][$groupId] &= ~$permission;
            if (empty($this->data['groups'][$groupId])) {
                unset($this->data['groups'][$groupId]);
            }
            if ($update) {
                $this->_permsOb->updatePermission($this);
            }
        }
    }

    /**
     * Save any changes to this object to the backend permanently.
     */
    function save()
    {
        $this->_permsOb->updatePermission($this);
    }

    /**
     * Get an array of all user permissions on this object.
     *
     * @return array  All user permissions for this object, indexed by user.
     */
    function getUserPermissions()
    {
        return (isset($this->data['users']) && is_array($this->data['users'])) ?
            $this->data['users'] :
            array();
    }

    /**
     * Get the guest permissions on this object.
     *
     * @return integer  The guest permissions on this object.
     */
    function getGuestPermissions()
    {
        return !empty($this->data['guest']) ?
            $this->data['guest'] :
            null;
    }

    /**
     * Get the creator permissions on this object.
     *
     * @return integer  The creator permissions on this object.
     */
    function getCreatorPermissions()
    {
        return !empty($this->data['creator']) ?
            $this->data['creator'] :
            null;
    }

    /**
     * Get the default permissions on this object.
     *
     * @return integer  The default permissions on this object.
     */
    function getDefaultPermissions()
    {
        return !empty($this->data['default']) ?
            $this->data['default'] :
            null;
    }

    /**
     * Get an array of all group permissions on this object.
     *
     * @return array  All group permissions for this object, indexed by group.
     */
    function getGroupPermissions()
    {
        return (isset($this->data['groups']) && is_array($this->data['groups'])) ?
            $this->data['groups'] :
            array();
    }

}
