-
-
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: