Ldap\Query: Extend SimpleQuery and add missing documentation
refs #8826 refs #8955
This commit is contained in:
parent
99213432f5
commit
5baa0590b1
|
@ -210,7 +210,7 @@ class Connection implements Selectable
|
|||
if (count($rows) > 1) {
|
||||
throw new LdapException(
|
||||
'Cannot fetch single DN for %s',
|
||||
$query->create()
|
||||
$query
|
||||
);
|
||||
}
|
||||
return key($rows);
|
||||
|
@ -272,16 +272,20 @@ class Connection implements Selectable
|
|||
* @return array The matched entries
|
||||
* @throws LdapException
|
||||
*/
|
||||
protected function runQuery(Query $query, $fields = array())
|
||||
protected function runQuery(Query $query, array $fields = null)
|
||||
{
|
||||
$limit = $query->getLimit();
|
||||
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
|
||||
|
||||
if (empty($fields)) {
|
||||
$fields = $query->getColumns();
|
||||
}
|
||||
|
||||
$results = @ldap_search(
|
||||
$this->ds,
|
||||
$query->hasBase() ? $query->getBase() : $this->root_dn,
|
||||
$query->create(),
|
||||
empty($fields) ? $query->listFields() : $fields,
|
||||
$query->getBase() ?: $this->root_dn,
|
||||
(string) $query,
|
||||
array_values($fields),
|
||||
0, // Attributes and values
|
||||
$limit ? $offset + $limit : 0
|
||||
);
|
||||
|
@ -292,8 +296,8 @@ class Connection implements Selectable
|
|||
|
||||
throw new LdapException(
|
||||
'LDAP query "%s" (base %s) failed. Error: %s',
|
||||
$query->create(),
|
||||
$query->hasBase() ? $query->getBase() : $this->root_dn,
|
||||
$query,
|
||||
$query->getBase() ?: $this->root_dn,
|
||||
ldap_error($this->ds)
|
||||
);
|
||||
} elseif (ldap_count_entries($this->ds, $results) === 0) {
|
||||
|
@ -336,28 +340,29 @@ class Connection implements Selectable
|
|||
*
|
||||
* @param Query $query The query to execute
|
||||
* @param array $fields The fields that will be fetched from the matches
|
||||
* @param int $page_size The maximum page size, defaults to Connection::PAGE_SIZE
|
||||
* @param int $pageSize The maximum page size, defaults to Connection::PAGE_SIZE
|
||||
*
|
||||
* @return array The matched entries
|
||||
* @throws LdapException
|
||||
* @throws ProgrammingError When executed without available page controls (check with pageControlAvailable() )
|
||||
*/
|
||||
protected function runPagedQuery(Query $query, $fields = array(), $pageSize = null)
|
||||
protected function runPagedQuery(Query $query, array $fields = null, $pageSize = null)
|
||||
{
|
||||
if (! $this->pageControlAvailable($query)) {
|
||||
throw new ProgrammingError('LDAP: Page control not available.');
|
||||
}
|
||||
|
||||
if (! isset($pageSize)) {
|
||||
$pageSize = static::PAGE_SIZE;
|
||||
}
|
||||
|
||||
$limit = $query->getLimit();
|
||||
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
|
||||
$queryString = $query->create();
|
||||
$base = $query->hasBase() ? $query->getBase() : $this->root_dn;
|
||||
$queryString = (string) $query;
|
||||
$base = $query->getBase() ?: $this->root_dn;
|
||||
|
||||
if (empty($fields)) {
|
||||
$fields = $query->listFields();
|
||||
$fields = $query->getColumns();
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
|
@ -368,7 +373,14 @@ class Connection implements Selectable
|
|||
// possibillity server to return an answer in case the pagination extension is missing.
|
||||
ldap_control_paged_result($this->ds, $pageSize, false, $cookie);
|
||||
|
||||
$results = @ldap_search($this->ds, $base, $queryString, $fields, 0, $limit ? $offset + $limit : 0);
|
||||
$results = @ldap_search(
|
||||
$this->ds,
|
||||
$base,
|
||||
$queryString,
|
||||
array_values($fields),
|
||||
0, // Attributes and values
|
||||
$limit ? $offset + $limit : 0
|
||||
);
|
||||
if ($results === false) {
|
||||
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
||||
break;
|
||||
|
@ -426,7 +438,7 @@ class Connection implements Selectable
|
|||
// pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
|
||||
// the server: https://www.ietf.org/rfc/rfc2696.txt
|
||||
ldap_control_paged_result($this->ds, 0, false, $cookie);
|
||||
ldap_search($this->ds, $base, $queryString, $fields); // Returns no entries, due to the page size
|
||||
ldap_search($this->ds, $base, $queryString); // Returns no entries, due to the page size
|
||||
} else {
|
||||
// Reset the paged search request so that subsequent requests succeed
|
||||
ldap_control_paged_result($this->ds, 0);
|
||||
|
|
|
@ -3,151 +3,151 @@
|
|||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
use Icinga\Data\SimpleQuery;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Exception\NotImplementedError;
|
||||
|
||||
/**
|
||||
* Search class
|
||||
*
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* LDAP query class
|
||||
*/
|
||||
/**
|
||||
* Search abstraction class
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* <code>
|
||||
* $connection->select()->from('user')->where('sAMAccountName = ?', 'icinga');
|
||||
* </code>
|
||||
*
|
||||
* @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.org>
|
||||
* @author Icinga-Web Team <info@icinga.org>
|
||||
* @package Icinga\Protocol\Ldap
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
|
||||
*/
|
||||
class Query
|
||||
class Query extends SimpleQuery
|
||||
{
|
||||
protected $connection;
|
||||
protected $filters = array();
|
||||
protected $fields = array();
|
||||
protected $limit_count = 0;
|
||||
protected $limit_offset = 0;
|
||||
protected $sort_columns = array();
|
||||
protected $count;
|
||||
protected $base;
|
||||
protected $usePagedResults = true;
|
||||
/**
|
||||
* This query's filters
|
||||
*
|
||||
* Currently just a basic key/value pair based array. Can be removed once Icinga\Data\Filter is supported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* The base dn being used for this query
|
||||
*
|
||||
* @param Connection LDAP Connection object
|
||||
* @return void
|
||||
* @var string
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* Whether this query is permitted to utilize paged results
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $usePagedResults;
|
||||
|
||||
/**
|
||||
* Initialize this query
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->filters = array();
|
||||
$this->usePagedResults = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base dn to be used for this query
|
||||
*
|
||||
* @param string $base
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBase($base)
|
||||
{
|
||||
$this->base = $base;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasBase()
|
||||
{
|
||||
return $this->base !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base dn being used for this query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBase()
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this query is permitted to utilize paged results
|
||||
*
|
||||
* @param bool $state
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUsePagedResults($state = true)
|
||||
{
|
||||
$this->usePagedResults = (bool) $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this query is permitted to utilize paged results
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUsePagedResults()
|
||||
{
|
||||
return $this->usePagedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count result set, ignoring limits
|
||||
* Choose an objectClass and the columns you are interested in
|
||||
*
|
||||
* @return int
|
||||
* {@inheritdoc} This creates an objectClass filter.
|
||||
*/
|
||||
public function count()
|
||||
public function from($target, array $fields = null)
|
||||
{
|
||||
if ($this->count === null) {
|
||||
$this->count = $this->connection->count($this);
|
||||
}
|
||||
return $this->count;
|
||||
$this->filters['objectClass'] = $target;
|
||||
return parent::from($target, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count result set, ignoring limits
|
||||
* Add a new filter to the query
|
||||
*
|
||||
* @return int
|
||||
* @param string $condition Column to search in
|
||||
* @param mixed $value Value to look for (asterisk wildcards are allowed)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function limit($count = null, $offset = null)
|
||||
public function where($condition, $value = null)
|
||||
{
|
||||
if (! preg_match('~^\d+~', $count . $offset)) {
|
||||
throw new Exception(
|
||||
'Got invalid limit: %s, %s',
|
||||
$count,
|
||||
$offset
|
||||
);
|
||||
// TODO: Adjust this once support for Icinga\Data\Filter is available
|
||||
if ($condition instanceof Expression) {
|
||||
$this->filters[] = $condition;
|
||||
} else {
|
||||
$this->filters[$condition] = $value;
|
||||
}
|
||||
$this->limit_count = (int) $count;
|
||||
$this->limit_offset = (int) $offset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a limit has been set
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasLimit()
|
||||
public function getFilter()
|
||||
{
|
||||
return $this->limit_count > 0;
|
||||
throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an offset (limit) has been set
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasOffset()
|
||||
public function applyFilter(Filter $filter)
|
||||
{
|
||||
return $this->limit_offset > 0;
|
||||
throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve result limit
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLimit()
|
||||
public function addFilter(Filter $filter)
|
||||
{
|
||||
return $this->limit_count;
|
||||
throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve result offset
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset()
|
||||
public function setFilter(Filter $filter)
|
||||
{
|
||||
return $this->limit_offset;
|
||||
throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as tree
|
||||
*
|
||||
* @return Node
|
||||
* @return Root
|
||||
*
|
||||
* @todo This is untested waste, not being used anywhere and ignores the query's order and base dn.
|
||||
* Evaluate whether it's reasonable to properly implement and test it.
|
||||
*/
|
||||
public function fetchTree()
|
||||
{
|
||||
|
@ -179,157 +179,32 @@ class Query
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetch result as an array of objects
|
||||
* Fetch the distinguished name of the first result
|
||||
*
|
||||
* @return array
|
||||
* @return string|false The distinguished name or false in case it's not possible to fetch a result
|
||||
*
|
||||
* @throws Exception In case the query returns multiple results
|
||||
* (i.e. it's not possible to fetch a unique DN)
|
||||
*/
|
||||
public function fetchAll()
|
||||
public function fetchDn()
|
||||
{
|
||||
return $this->connection->fetchAll($this);
|
||||
return $this->ds->fetchDn($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first result row
|
||||
* Return the LDAP filter to be applied on this query
|
||||
*
|
||||
* @return object
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception In case the objectClass filter does not exist
|
||||
*/
|
||||
public function fetchRow()
|
||||
protected function renderFilter()
|
||||
{
|
||||
return $this->connection->fetchRow($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch first column value from first result row
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
return $this->connection->fetchOne($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a key/value list, first column is key, second is value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetchPairs()
|
||||
{
|
||||
// STILL TODO!!
|
||||
return $this->connection->fetchPairs($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to select (which fields) from
|
||||
*
|
||||
* This creates an objectClass filter
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function from($objectClass, $fields = array())
|
||||
{
|
||||
$this->filters['objectClass'] = $objectClass;
|
||||
$this->fields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the query
|
||||
*
|
||||
* @param string Column to search in
|
||||
* @param string Filter text (asterisks are allowed)
|
||||
* @return Query
|
||||
*/
|
||||
public function where($key, $val)
|
||||
{
|
||||
$this->filters[$key] = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by given column
|
||||
*
|
||||
* TODO: Sort direction is not implemented yet
|
||||
*
|
||||
* @param string Order column
|
||||
* @param string Order direction
|
||||
* @return Query
|
||||
*/
|
||||
public function order($column, $direction = 'ASC')
|
||||
{
|
||||
$this->sort_columns[] = array($column, $direction);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of the desired fields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list containing current sort columns
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSortColumns()
|
||||
{
|
||||
return $this->sort_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a pagination adapter for the current query
|
||||
*
|
||||
* @return \Zend_Paginator
|
||||
*/
|
||||
public function paginate($limit = null, $page = null)
|
||||
{
|
||||
if ($page === null || $limit === null) {
|
||||
$request = \Zend_Controller_Front::getInstance()->getRequest();
|
||||
if ($page === null) {
|
||||
$page = $request->getParam('page', 0);
|
||||
}
|
||||
if ($limit === null) {
|
||||
$limit = $request->getParam('limit', 20);
|
||||
}
|
||||
}
|
||||
$paginator = new \Zend_Paginator(
|
||||
// TODO: Adapter doesn't fit yet:
|
||||
new \Icinga\Web\Paginator\Adapter\QueryAdapter($this)
|
||||
);
|
||||
$paginator->setItemCountPerPage($limit);
|
||||
$paginator->setCurrentPageNumber($page);
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter expression to this query
|
||||
*
|
||||
* @param Expression $expression
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function addFilter(Expression $expression)
|
||||
{
|
||||
$this->filters[] = $expression;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the LDAP filter that will be applied
|
||||
*
|
||||
* @string
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$parts = array();
|
||||
if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) {
|
||||
if (! isset($this->filters['objectClass'])) {
|
||||
throw new Exception('Object class is mandatory');
|
||||
}
|
||||
|
||||
$parts = array();
|
||||
foreach ($this->filters as $key => $value) {
|
||||
if ($value instanceof Expression) {
|
||||
$parts[] = (string) $value;
|
||||
|
@ -341,6 +216,7 @@ class Query
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($parts) > 1) {
|
||||
return '(&(' . implode(')(', $parts) . '))';
|
||||
} else {
|
||||
|
@ -348,17 +224,13 @@ class Query
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the LDAP filter to be applied on this query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Descructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// To be on the safe side:
|
||||
unset($this->connection);
|
||||
return $this->renderFilter();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,51 +36,11 @@ class QueryTest extends BaseTestCase
|
|||
return $select;
|
||||
}
|
||||
|
||||
public function testLimit()
|
||||
{
|
||||
$select = $this->prepareSelect();
|
||||
$this->assertEquals(10, $select->getLimit());
|
||||
$this->assertEquals(4, $select->getOffset());
|
||||
}
|
||||
|
||||
public function testHasLimit()
|
||||
{
|
||||
$select = $this->emptySelect();
|
||||
$this->assertFalse($select->hasLimit());
|
||||
$select = $this->prepareSelect();
|
||||
$this->assertTrue($select->hasLimit());
|
||||
}
|
||||
|
||||
public function testHasOffset()
|
||||
{
|
||||
$select = $this->emptySelect();
|
||||
$this->assertFalse($select->hasOffset());
|
||||
$select = $this->prepareSelect();
|
||||
$this->assertTrue($select->hasOffset());
|
||||
}
|
||||
|
||||
public function testGetLimit()
|
||||
{
|
||||
$select = $this->prepareSelect();
|
||||
$this->assertEquals(10, $select->getLimit());
|
||||
}
|
||||
|
||||
public function testGetOffset()
|
||||
{
|
||||
$select = $this->prepareSelect();
|
||||
$this->assertEquals(10, $select->getLimit());
|
||||
}
|
||||
|
||||
public function testFetchTree()
|
||||
{
|
||||
$this->markTestIncomplete('testFetchTree is not implemented yet - requires real LDAP');
|
||||
}
|
||||
|
||||
public function testFrom()
|
||||
{
|
||||
return $this->testListFields();
|
||||
}
|
||||
|
||||
public function testWhere()
|
||||
{
|
||||
$this->markTestIncomplete('testWhere is not implemented yet');
|
||||
|
@ -88,30 +48,13 @@ class QueryTest extends BaseTestCase
|
|||
|
||||
public function testOrder()
|
||||
{
|
||||
$select = $this->emptySelect()->order('bla');
|
||||
// tested by testGetSortColumns
|
||||
$this->markTestIncomplete('testOrder is not implemented yet, order support for ldap queries is incomplete');
|
||||
}
|
||||
|
||||
public function testListFields()
|
||||
{
|
||||
$select = $this->prepareSelect();
|
||||
$this->assertEquals(
|
||||
array('testIntColumn', 'testStringColumn'),
|
||||
$select->listFields()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetSortColumns()
|
||||
{
|
||||
$select = $this->prepareSelect();
|
||||
$cols = $select->getSortColumns();
|
||||
$this->assertEquals('testIntColumn', $cols[0][0]);
|
||||
}
|
||||
|
||||
public function testCreateQuery()
|
||||
public function testRenderFilter()
|
||||
{
|
||||
$select = $this->prepareSelect();
|
||||
$res = '(&(objectClass=dummyClass)(testIntColumn=1)(testStringColumn=test)(testWildcard=abc*))';
|
||||
$this->assertEquals($res, $select->create());
|
||||
$this->assertEquals($res, (string) $select);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue