-
-
Save ThaDafinser/1d081bed8e5e6505e97bedf5863a187c to your computer and use it in GitHub Desktop.
<?php | |
namespace EsSyncAd; | |
use Iterator; | |
use Zend\Ldap\Ldap; | |
use Zend\Ldap\Exception; | |
use Zend\Ldap\ErrorHandler; | |
use Zend\Ldap\Exception\LdapException; | |
final class PagingIterator implements Iterator | |
{ | |
private $ldap; | |
private $filter; | |
private $baseDn; | |
private $returnAttributes; | |
private $pageSize; | |
private $resolveRangedAttributes; | |
private $entries; | |
private $current; | |
/** | |
* Required for paging | |
* | |
* @var unknown | |
*/ | |
private $currentResult; | |
/** | |
* Required for paging | |
* | |
* @var unknown | |
*/ | |
private $cookie = true; | |
public function __construct(Ldap $ldap, string $filter, string $baseDn = null, array $returnAttributes = null, $pageSize = 250, bool $resolveRangedAttributes = false) | |
{ | |
$this->ldap = $ldap; | |
$this->filter = $filter; | |
$this->baseDn = $baseDn; | |
$this->returnAttributes = $returnAttributes; | |
$this->pageSize = $pageSize; | |
$this->resolveRangedAttributes = $resolveRangedAttributes; | |
} | |
private function getLdap() | |
{ | |
return $this->ldap; | |
} | |
private function getFilter() | |
{ | |
return $this->filter; | |
} | |
private function getBaseDn() | |
{ | |
return $this->baseDn; | |
} | |
private function getReturnAttributes() | |
{ | |
return $this->returnAttributes; | |
} | |
private function getPageSize() | |
{ | |
return $this->pageSize; | |
} | |
/** | |
* | |
* @return bool | |
*/ | |
private function getResolveRangedAttributes() | |
{ | |
return $this->resolveRangedAttributes; | |
} | |
private function fetchPagedResult() | |
{ | |
if ($this->cookie === null || $this->cookie === '') { | |
return false; | |
} | |
if ($this->cookie === true) { | |
// First fetch! | |
$this->cookie = ''; | |
} | |
$ldap = $this->getLdap(); | |
$resource = $ldap->getResource(); | |
ldap_control_paged_result($resource, $this->getPageSize(), true, $this->cookie); | |
if ($this->getReturnAttributes() !== null) { | |
$resultResource = ldap_search($resource, $ldap->getBaseDn(), $this->getFilter(), $this->getReturnAttributes()); | |
} else { | |
$resultResource = ldap_search($resource, $ldap->getBaseDn(), $this->getFilter()); | |
} | |
if (! is_resource($resultResource)) { | |
/* | |
* @TODO better exception msg | |
*/ | |
throw new \Exception('ldap_search returned something wrong...' . ldap_error($resource)); | |
} | |
$entries = ldap_get_entries($resource, $resultResource); | |
if ($entries === false) { | |
throw new LdapException($ldap, 'Entires could not get fetched'); | |
} | |
$entries = $this->getConvertedEntries($entries); | |
ErrorHandler::start(); | |
$response = ldap_control_paged_result_response($resource, $resultResource, $this->cookie); | |
ErrorHandler::stop(); | |
if ($response !== true) { | |
throw new LdapException($ldap, 'Paged result was empty'); | |
} | |
if ($this->entries === null) { | |
$this->entries = []; | |
} | |
$this->entries = array_merge($this->entries, $entries); | |
return true; | |
} | |
private function getConvertedEntries(array $entries) | |
{ | |
$result = []; | |
foreach ($entries as $key => $entry) { | |
if ($key === 'count') { | |
continue; | |
} | |
$result[$key] = $this->getConvertedEntry($entry); | |
} | |
return $result; | |
} | |
private function getConvertedEntry(array $entry) | |
{ | |
$result = []; | |
foreach ($entry as $key => $value) { | |
if (is_int($key)) { | |
continue; | |
} | |
if ($key === 'count') { | |
continue; | |
} | |
if (isset($value['count'])) { | |
unset($value['count']); | |
} | |
$result[$key] = $value; | |
} | |
if ($this->getResolveRangedAttributes() === true) { | |
$result = $this->resolveRangedAttributes($result); | |
} | |
return $result; | |
} | |
private function resolveRangedAttributes(array $row) | |
{ | |
$result = []; | |
foreach ($row as $key => $value) { | |
$keyExploded = explode(';range=', $key); | |
if (count($keyExploded) === 2) { | |
$range = explode('-', $keyExploded[1]); | |
$offsetAndLimit = (int) $range[1] + 1; | |
$result[$keyExploded[0]] = array_merge($value, $this->getAttributeRecursive($row['dn'], $keyExploded[0], $offsetAndLimit, $offsetAndLimit)); | |
} else { | |
$result[$key] = $value; | |
} | |
} | |
return $result; | |
} | |
private function getAttributeRecursive(string $dn, string $attrName, int $offset, int $maxPerRequest) | |
{ | |
$attributeValue = []; | |
$limit = $offset + $maxPerRequest - 1; | |
$searchedAttribute = $attrName . ';range=' . $offset . '-' . $limit; | |
$ldap = $this->getLdap(); | |
$entry = $ldap->getEntry($dn, [ | |
$searchedAttribute | |
], true); | |
foreach ($entry as $key => $value) { | |
// skip DN and other fields (if returned) | |
if (stripos($key, $attrName) === false) { | |
continue; | |
} | |
$attributeValue = $value; | |
// range result (pagination) | |
$keyExploded = explode(';range=', $key); | |
$range = explode('-', $keyExploded[1]); | |
$rangeEnd = (int) $range[1]; | |
if ($range[0] == $offset && $range[1] == $limit) { | |
// more pages, there are more pages to fetch | |
$attributeValue = array_merge($attributeValue, $this->getAttributeRecursive($dn, $attrName, $rangeEnd + 1, $maxPerRequest)); | |
} | |
} | |
return $attributeValue; | |
} | |
public function current() | |
{ | |
if (! is_array($this->current)) { | |
$this->rewind(); | |
} | |
if (! is_array($this->current)) { | |
return; | |
} | |
return $this->current; | |
} | |
public function key() | |
{ | |
if (! is_array($this->current)) { | |
$this->rewind(); | |
} | |
if (! is_array($this->current)) { | |
return; | |
} | |
return $this->current['dn']; | |
} | |
public function next() | |
{ | |
// initial | |
if ($this->entries === null) { | |
$this->fetchPagedResult(); | |
} | |
next($this->entries); | |
$this->current = current($this->entries); | |
} | |
public function rewind() | |
{ | |
// initial | |
if ($this->entries === null) { | |
$this->fetchPagedResult(); | |
} | |
reset($this->entries); | |
$this->current = current($this->entries); | |
} | |
public function valid() | |
{ | |
if (is_array($this->current)) { | |
return true; | |
} | |
return $this->fetchPagedResult(); | |
} | |
} |
One minor revision that I made to this - lines 103 and 105 should be $this->getBaseDn()
instead of $ldap->getBaseDn()
- otherwise the LDAP connections base DN is used which is generally much wider than you specifically want to iterate over (otherwise why allow specifying a DN at all, leaving it blank and ldap_search()
will automatically use the base DN of the connection).
Also, to use this class, you just create the Zend\Ldap\Ldap
class as normal, then create a PagingIterator
, passing in the LDAP resource and your filters etc. as you normally would, then iterate over the results, for example:
$ldap = new Zend\Ldap\Ldap($options);
$users = new PagingIterator($ldap, '(&(objectClass=user))');
foreach ($users as $user) {
printf($user['dn'][0]);
}
// Reset the LDAP pagination control back to the original, otherwise all further LDAP read queries fail
ldap_control_paged_result($ldap->getResource(), 1000);
Note: After using this iterator, you need to reset the pagination control. Setting it to 0 should work, but it doesn't - I'm not sure if this is a PHP or Active Directory issue. Setting it to 1,000 (the default value for a number of LDAP servers including Active Directory) does the trick. I couldn't find a neat way to do that within the iterator itself.
I'm looking to try and merge this into the zend-ldap library via a pull request.
Could you add an example of the proper use of this class to retrieve all the results from a query? i'm not sure to understand ho it works properly..
i guess something like this would do the job but i'm not sure: