Files
server/usr/share/psa-pear/pear/php/Horde/Share/Sqlng.php
2026-01-07 20:52:11 +01:00

504 lines
18 KiB
PHP

<?php
/**
* Horde_Share_Sqlng provides the next-generation SQL backend driver for the
* Horde_Share library.
*
* Copyright 2011-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @author Jan Schneider <jan@horde.org>
* @package Share
*/
/**
* @package Share
*/
class Horde_Share_Sqlng extends Horde_Share_Sql
{
/* Serializable version */
const VERSION = 1;
/**
* The Horde_Share_Object subclass to instantiate objects as
*
* @var string
*/
protected $_shareObject = 'Horde_Share_Object_Sqlng';
/**
* A list of available permission.
*
* This is necessary to unset certain permission when updating existing
* share objects.
*
* @param array
*/
protected $_availablePermissions = array();
/**
*
* @see Horde_Share_Base::__construct()
*/
public function __construct($app, $user, Horde_Perms_Base $perms,
Horde_Group_Base $groups)
{
parent::__construct($app, $user, $perms, $groups);
$this->_table = $this->_app . '_sharesng';
}
/**
* Passes the available permissions to the share object.
*
* @param Horde_Share_Object $object
*/
public function initShareObject(Horde_Share_Object $object)
{
parent::initShareObject($object);
$object->availablePermissions = array_keys($this->_availablePermissions);
}
/**
* Returns an array of all shares that $userid has access to.
*
* @param string $userid The userid of the user to check access for.
* @param array $params Additional parameters for the search.
*<pre>
* 'perm' Require this level of permissions. Horde_Perms constant.
* 'attributes' Restrict shares to these attributes. A hash or username.
* 'from' Offset. Start at this share
* 'count' Limit. Only return this many.
* 'sort_by' Sort by attribute.
* 'direction' Sort by direction.
* 'parent' Start at this share in the hierarchy. Either share_id or
* Horde_Share_Object
* 'all_levels' List all levels or just the direct children of parent?
*</pre>
*
* @return array The shares the user has access to.
* @throws Horde_Share_Exception
*/
public function listShares($userid, array $params = array())
{
$params = array_merge(array('perm' => Horde_Perms::SHOW,
'attributes' => null,
'from' => 0,
'count' => 0,
'sort_by' => null,
'direction' => 0,
'parent' => null,
'all_levels' => true),
$params);
$key = md5(serialize(array($userid, $params)));
if (isset($this->_listcache[$key])) {
return $this->_listcache[$key];
}
$perms = $this->convertBitmaskToArray($params['perm']);
$shareids = null;
if (!empty($userid)) {
list($users, $groups, $shareids) = $this->_getUserAndGroupShares($userid, $perms);
}
if (is_null($params['sort_by'])) {
$sortfield = 'share_id';
} elseif ($params['sort_by'] == 'owner' || $params['sort_by'] == 'id') {
$sortfield = 'share_' . $params['sort_by'];
} else {
$sortfield = 'attribute_' . $params['sort_by'];
}
$where = $this->_getShareCriteria($userid, $perms, $params['attributes'], $shareids, $params['parent'], $params['all_levels']);
$query = 'SELECT ' . $this->_getDistinctClause() . ' FROM ' . $this->_table . ' s ' .
(!empty($where) ? ' WHERE ' . $where : '')
. ' ORDER BY ' . $sortfield
. (($params['direction'] == 0) ? ' ASC' : ' DESC');
$query = $this->_db->addLimitOffset($query, array('limit' => $params['count'], 'offset' => $params['from']));
try {
$rows = $this->_db->select($query);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e);
}
$sharelist = array();
$shares = array();
foreach ($rows as $share) {
$shares[(int)$share['share_id']] = $this->_fromDriverCharset($share);
}
$this->_fetchClobFields($shares);
foreach ($shares as $share) {
$this->_loadPermissions($share);
$sharelist[$share['share_name']] = $this->_createObject($share);
}
unset($shares);
// Run the results through the callback, if configured.
if (!empty($this->_callbacks['list'])) {
$sharelist = $this->runCallback('list', array($userid, $sharelist, $params));
}
$this->_listcache[$key] = $sharelist;
return $this->_listcache[$key];
}
/**
* Returns an array of all system shares.
*
* @return array All system shares.
* @throws Horde_Share_Exception
*/
public function listSystemShares()
{
$query = 'SELECT * FROM ' . $this->_table . ' WHERE share_owner IS NULL';
try {
$rows = $this->_db->select($query);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e->getMessage());
}
$sharelist = array();
foreach ($rows as $share) {
$this->_convertClobs($share);
$data = $this->_fromDriverCharset($share);
$this->_loadPermissions($data);
$sharelist[$data['share_name']] = $this->_createObject($data);
}
return $sharelist;
}
/**
* 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 mixed $parent The share to start searching from
* (Horde_Share_Object, share_id, or null)
* @param boolean $allLevels Return all levels, or just the direct
* children of $parent?
*
* @return integer Number of shares the user has access to.
* @throws Horde_Share_Exception
*/
public function countShares($userid, $perm = Horde_Perms::SHOW,
$attributes = null, $parent = null, $allLevels = true)
{
$perms = $this->convertBitmaskToArray($perm);
$shareids = null;
if (!empty($userid)) {
list(, , $shareids) = $this->_getUserAndGroupShares($userid, $perms);
}
$query = 'SELECT COUNT(DISTINCT share_id) FROM '
. $this->_table . ' s WHERE '
. $this->_getShareCriteria($userid, $perms, $attributes, $shareids, $parent, $allLevels);
try {
return $this->_db->selectValue($query);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e);
}
}
/**
* Count the number of users who have shares with the given permissions
* for the current user.
*
* @param integer $perm The level of permissions required.
* @param mixed $parent The parent share to start looking in.
* (Horde_Share_Object, share_id, or null).
* @param boolean $allLevels Return all levels, or just the direct
* children of $parent?
*
* @return integer Number of users.
* @throws Horde_Share_Exception
*/
public function countOwners($perm = Horde_Perms::SHOW, $parent = null, $allLevels = true)
{
$perms = self::convertBitmaskToArray($perm);
$sql = 'SELECT COUNT(DISTINCT(s.share_owner)) FROM ' . $this->_table . ' s WHERE '
. $this->_getShareCriteria($this->_user, $perms, null, $parent, $allLevels);
try {
$results = $this->_db->selectValue($sql);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e);
}
return $results;
}
/**
* Return a list of users who have shares with the given permissions
* for the current user.
*
* @param integer $perm The level of permissions required.
* @param mixed $parent The parent share to start looking in.
* (Horde_Share_Object, share_id, or null)
* @param boolean $allLevels Return all levels, or just the direct
* children of $parent? Defaults to all levels.
* @param integer $from The user to start listing at.
* @param integer $count The number of users to return.
*
* @return array List of users.
* @throws Horde_Share_Exception
*/
public function listOwners($perm = Horde_Perms::SHOW, $parent = null, $allLevels = true,
$from = 0, $count = 0)
{
$perms = self::convertBitmaskToArray($perm);
$sql = 'SELECT DISTINCT(s.share_owner) FROM ' . $this->_table . ' s WHERE '
. $this->_getShareCriteria($this->_user, $perms, null, $parent, $allLevels);
if ($count) {
$sql = $this->_db->addLimitOffset($sql, array('limit' => $count, 'offset' => $from));
}
try {
$allowners = $this->_db->selectValues($sql);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e);
}
$owners = array();
foreach ($allowners as $owner) {
if ($this->countShares($this->_user, $perm, $owner, $parent, $allLevels)) {
$owners[] = $owner;
}
}
return $owners;
}
/**
* Converts a bit mask number to a bit mask array.
*
* @param integer A bit mask.
*
* @return array The bit mask as an array.
*/
public static function convertBitmaskToArray($perm)
{
$perms = array();
for ($bit = 1; $perm; $bit *= 2, $perm >>= 1) {
if ($perm % 2) {
$perms[] = $bit;
}
}
return $perms;
}
/**
* Builds a permission bit mask from all columns in a data row prefixed
* with "perm_".
*
* @param array $row A data row including permission columns.
*
* @return integer A permission mask.
*/
protected function _buildPermsFromRow($row)
{
$perms = 0;
foreach ($row as $column => $value) {
if (substr($column, 0, 5) != 'perm_') {
continue;
}
$perm = (int)substr($column, 5);
$this->_availablePermissions[$perm] = true;
if ($value) {
$perms |= $perm;
}
}
return $perms;
}
/**
* Converts the permissions from the database table format into the
* Horde_Share format.
*
* @param array $data The share object data to convert.
*/
protected function _getSharePerms(&$data)
{
$data['perm']['type'] = 'matrix';
$data['perm']['default'] = $data['perm']['guest'] = $data['perm']['creator'] = 0;
foreach ($data as $column => $value) {
$perm = explode('_', $column, 3);
if ($perm[0] != 'perm' || count($perm) != 3) {
continue;
}
$permvalue = (int)$perm[2];
$this->_availablePermissions[$permvalue] = true;
if ($value) {
$data['perm'][$perm[1]] |= $permvalue;
}
unset($data[$column]);
}
}
/**
* Returns the records and share IDs from the user and group tables that
* match the search criteria.
*
* @param string $userid The userid of the user to check access for.
* @param array $perms The level of permissions required.
*
* @return array A set of user, groups, and shareids.
*/
protected function _getUserAndGroupShares($userid, array $perms)
{
$shareids = array();
// Get users permissions.
$query = 'SELECT * FROM ' . $this->_table
. '_users WHERE user_uid = ' . $this->_db->quote($userid)
. ' AND (' . $this->_getPermsCriteria('perm', $perms) . ')';
try {
$users = $this->_db->select($query);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e->getMessage());
}
foreach ($users as $user) {
$shareids[] = $user['share_id'];
}
// Get groups permissions.
$groups = array();
try {
$groupNames = $this->_groups->listGroups($userid);
if ($groupNames) {
$group_ids = array();
foreach (array_keys($groupNames) as $id) {
$group_ids[] = $this->_db->quote((string)$id);
}
$query = 'SELECT * FROM ' . $this->_table
. '_groups WHERE group_uid IN ('
. implode(',', $group_ids) . ') AND ('
. $this->_getPermsCriteria('perm', $perms) . ')';
try {
$groups = $this->_db->select($query);
} catch (Horde_Db_Exception $e) {
throw new Horde_Share_Exception($e->getMessage());
}
foreach ($groups as $group) {
$shareids[] = $group['share_id'];
}
}
} catch (Horde_Group_Exception $e) {
$this->_logger->err($e);
}
return array($users, $groups, array_unique($shareids));
}
/**
* Returns a criteria statement for querying shares.
*
* @param string $userid The userid of the user to check access for.
* @param array $perms The level of permissions required.
* @param array $attributes Restrict the shares returned to those who
* have these attribute values.
* @param array $shareids Additional share IDs from user and group
* permissions.
*
* @return string The criteria string for fetching this user's shares.
*/
protected function _getShareCriteria($userid, array $perms, $attributes,
$shareids = null, $parent = null,
$allLevels = true)
{
/* Convert to driver's keys */
$attributes = $this->_toDriverKeys($attributes);
/* ...and to driver charset */
$attributes = $this->toDriverCharset($attributes);
$where = '';
if (empty($userid)) {
$where = $this->_getPermsCriteria('perm_guest', $perms);
} else {
// (owner == $userid)
$where .= 'share_owner = ' . $this->_db->quote($userid);
// (name == perm_creator and val & $perm)
$where .= ' OR ' . $this->_getPermsCriteria('perm_creator', $perms);
// (name == perm_default and val & $perm)
$where .= ' OR ' . $this->_getPermsCriteria('perm_default', $perms);
if ($shareids) {
$where .= ' OR share_id IN (' . implode(',', $shareids) . ')';
}
}
if (is_array($attributes)) {
// Build attribute/key filter.
$where = '(' . $where . ') ';
foreach ($attributes as $key => $value) {
if (is_array($value)) {
$value = array_map(array($this->_db, 'quote'), $value);
$where .= ' AND ' . $key . ' IN (' . implode(', ', $value) . ')';
} else {
$where .= ' AND ' . $key . ' = ' . $this->_db->quote($value);
}
}
} elseif (!empty($attributes)) {
// Restrict to shares owned by the user specified in the
// $attributes string.
$where = '(' . $where . ') AND share_owner = ' . $this->_db->quote($attributes);
}
// See if we need to filter by parent or get the parent object
if ($parent != null) {
if (!($parent instanceof Horde_Share_Object)) {
$parent = $this->getShareById($parent);
}
// Need to append the parent's share id to the list of parents in
// order to search the share_parents field.
$parents = $parent->get('parents') . ':' . $parent->getId();
if ($allLevels) {
$where_parent = '(share_parents = ' . $this->_db->quote($parents)
. ' OR share_parents LIKE ' . $this->_db->quote($parents . ':%') . ')';
} else {
$where_parent = 's.share_parents = ' . $this->_db->quote($parents);
}
} elseif (!$allLevels) {
// No parents, and we only want the root.
$where_parent = "(s.share_parents = '' OR s.share_parents IS NULL)";
}
if (!empty($where_parent)) {
if (empty($where)) {
$where = $where_parent;
} else {
$where = '(' . $where . ') AND ' . $where_parent;
}
}
return $where;
}
/**
* Builds an ANDed criteria snippet for a set or permissions.
*
* @param string $base A column name prefix.
* @param array $perms A list of permissions.
*
* @return string The generated criteria string.
*/
protected function _getPermsCriteria($base, array $perms)
{
$criteria = array();
foreach ($perms as $perm) {
$criteria[] = $base . '_' . $perm . ' = ' . $this->_db->quoteTrue();
}
return implode(' OR ', $criteria);
}
}