Created
February 23, 2017 14:27
-
-
Save ThaDafinser/1d081bed8e5e6505e97bedf5863a187c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 andldap_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 aPagingIterator
, passing in the LDAP resource and your filters etc. as you normally would, then iterate over the results, for example: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.