10b3fd2d3SAndreas Gohr<?php 2*dad993c5SAndreas Gohr 30b3fd2d3SAndreas Gohr/** 40b3fd2d3SAndreas Gohr * This file is part of the FreeDSx LDAP package. 50b3fd2d3SAndreas Gohr * 60b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 70b3fd2d3SAndreas Gohr * 80b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 90b3fd2d3SAndreas Gohr * file that was distributed with this source code. 100b3fd2d3SAndreas Gohr */ 110b3fd2d3SAndreas Gohr 120b3fd2d3SAndreas Gohrnamespace FreeDSx\Ldap\Search; 130b3fd2d3SAndreas Gohr 140b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Control; 150b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\PagingControl; 160b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Controls; 170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entries; 18*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException; 190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException; 200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\LdapClient; 210b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\SearchRequest; 220b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\SearchResponse; 230b3fd2d3SAndreas Gohr 240b3fd2d3SAndreas Gohr/** 250b3fd2d3SAndreas Gohr * Provides a simple wrapper around paging a search operation. 260b3fd2d3SAndreas Gohr * 270b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 280b3fd2d3SAndreas Gohr */ 290b3fd2d3SAndreas Gohrclass Paging 300b3fd2d3SAndreas Gohr{ 310b3fd2d3SAndreas Gohr /** 320b3fd2d3SAndreas Gohr * @var PagingControl|null 330b3fd2d3SAndreas Gohr */ 340b3fd2d3SAndreas Gohr protected $control; 350b3fd2d3SAndreas Gohr 360b3fd2d3SAndreas Gohr /** 370b3fd2d3SAndreas Gohr * @var LdapClient 380b3fd2d3SAndreas Gohr */ 390b3fd2d3SAndreas Gohr protected $client; 400b3fd2d3SAndreas Gohr 410b3fd2d3SAndreas Gohr /** 420b3fd2d3SAndreas Gohr * @var int 430b3fd2d3SAndreas Gohr */ 440b3fd2d3SAndreas Gohr protected $size; 450b3fd2d3SAndreas Gohr 460b3fd2d3SAndreas Gohr /** 470b3fd2d3SAndreas Gohr * @var SearchRequest 480b3fd2d3SAndreas Gohr */ 490b3fd2d3SAndreas Gohr protected $search; 500b3fd2d3SAndreas Gohr 510b3fd2d3SAndreas Gohr /** 520b3fd2d3SAndreas Gohr * @var bool 530b3fd2d3SAndreas Gohr */ 540b3fd2d3SAndreas Gohr protected $ended = false; 550b3fd2d3SAndreas Gohr 560b3fd2d3SAndreas Gohr /** 57*dad993c5SAndreas Gohr * @var bool 58*dad993c5SAndreas Gohr */ 59*dad993c5SAndreas Gohr protected $isCritical = false; 60*dad993c5SAndreas Gohr 61*dad993c5SAndreas Gohr /** 620b3fd2d3SAndreas Gohr * @param LdapClient $client 630b3fd2d3SAndreas Gohr * @param SearchRequest $search 640b3fd2d3SAndreas Gohr * @param int $size 650b3fd2d3SAndreas Gohr */ 660b3fd2d3SAndreas Gohr public function __construct(LdapClient $client, SearchRequest $search, int $size = 1000) 670b3fd2d3SAndreas Gohr { 680b3fd2d3SAndreas Gohr $this->search = $search; 690b3fd2d3SAndreas Gohr $this->client = $client; 700b3fd2d3SAndreas Gohr $this->size = $size; 710b3fd2d3SAndreas Gohr } 720b3fd2d3SAndreas Gohr 730b3fd2d3SAndreas Gohr /** 74*dad993c5SAndreas Gohr * Set the criticality of the control. Setting this will cause the LDAP server to return an error if paging is not 75*dad993c5SAndreas Gohr * possible. 76*dad993c5SAndreas Gohr * 77*dad993c5SAndreas Gohr * @param bool $isCritical 78*dad993c5SAndreas Gohr * @return $this 79*dad993c5SAndreas Gohr */ 80*dad993c5SAndreas Gohr public function isCritical(bool $isCritical = true): self 81*dad993c5SAndreas Gohr { 82*dad993c5SAndreas Gohr $this->isCritical = $isCritical; 83*dad993c5SAndreas Gohr 84*dad993c5SAndreas Gohr return $this; 85*dad993c5SAndreas Gohr } 86*dad993c5SAndreas Gohr 87*dad993c5SAndreas Gohr /** 880b3fd2d3SAndreas Gohr * Start a new paging operation with a search request. This must be called first if you reuse the paging object. 890b3fd2d3SAndreas Gohr * 900b3fd2d3SAndreas Gohr * @param SearchRequest $search 910b3fd2d3SAndreas Gohr * @param int|null $size 920b3fd2d3SAndreas Gohr */ 930b3fd2d3SAndreas Gohr public function start(SearchRequest $search, ?int $size = null): void 940b3fd2d3SAndreas Gohr { 950b3fd2d3SAndreas Gohr $this->size = $size ?? $this->size; 960b3fd2d3SAndreas Gohr $this->search = $search; 970b3fd2d3SAndreas Gohr $this->control = null; 980b3fd2d3SAndreas Gohr $this->ended = false; 990b3fd2d3SAndreas Gohr } 1000b3fd2d3SAndreas Gohr 1010b3fd2d3SAndreas Gohr /** 1020b3fd2d3SAndreas Gohr * End the paging operation. This can be triggered at any time. 1030b3fd2d3SAndreas Gohr * 1040b3fd2d3SAndreas Gohr * @return $this 105*dad993c5SAndreas Gohr * @throws OperationException 1060b3fd2d3SAndreas Gohr */ 1070b3fd2d3SAndreas Gohr public function end() 1080b3fd2d3SAndreas Gohr { 1090b3fd2d3SAndreas Gohr $this->send(0); 1100b3fd2d3SAndreas Gohr $this->ended = true; 1110b3fd2d3SAndreas Gohr 1120b3fd2d3SAndreas Gohr return $this; 1130b3fd2d3SAndreas Gohr } 1140b3fd2d3SAndreas Gohr 1150b3fd2d3SAndreas Gohr /** 1160b3fd2d3SAndreas Gohr * Get the next set of entries of results. 1170b3fd2d3SAndreas Gohr * 1180b3fd2d3SAndreas Gohr * @param int|null $size 1190b3fd2d3SAndreas Gohr * @return Entries 120*dad993c5SAndreas Gohr * @throws OperationException 1210b3fd2d3SAndreas Gohr */ 1220b3fd2d3SAndreas Gohr public function getEntries(?int $size = null): Entries 1230b3fd2d3SAndreas Gohr { 1240b3fd2d3SAndreas Gohr return $this->send($size); 1250b3fd2d3SAndreas Gohr } 1260b3fd2d3SAndreas Gohr 1270b3fd2d3SAndreas Gohr /** 1280b3fd2d3SAndreas Gohr * @return bool 1290b3fd2d3SAndreas Gohr */ 1300b3fd2d3SAndreas Gohr public function hasEntries() 1310b3fd2d3SAndreas Gohr { 1320b3fd2d3SAndreas Gohr if ($this->ended) { 1330b3fd2d3SAndreas Gohr return false; 1340b3fd2d3SAndreas Gohr } 1350b3fd2d3SAndreas Gohr 1360b3fd2d3SAndreas Gohr return $this->control === null || !($this->control->getCookie() === ''); 1370b3fd2d3SAndreas Gohr } 1380b3fd2d3SAndreas Gohr 1390b3fd2d3SAndreas Gohr /** 1400b3fd2d3SAndreas Gohr * The size may be set to the server's estimate of the total number of entries in the entire result set. Servers 1410b3fd2d3SAndreas Gohr * that cannot provide such an estimate may set this size to zero. 1420b3fd2d3SAndreas Gohr * 1430b3fd2d3SAndreas Gohr * @return int|null 1440b3fd2d3SAndreas Gohr */ 1450b3fd2d3SAndreas Gohr public function sizeEstimate(): ?int 1460b3fd2d3SAndreas Gohr { 1470b3fd2d3SAndreas Gohr return ($this->control !== null) ? $this->control->getSize() : null; 1480b3fd2d3SAndreas Gohr } 1490b3fd2d3SAndreas Gohr 1500b3fd2d3SAndreas Gohr /** 1510b3fd2d3SAndreas Gohr * @param int|null $size 1520b3fd2d3SAndreas Gohr * @return Entries 153*dad993c5SAndreas Gohr * @throws OperationException 1540b3fd2d3SAndreas Gohr */ 1550b3fd2d3SAndreas Gohr protected function send(?int $size = null) 1560b3fd2d3SAndreas Gohr { 157*dad993c5SAndreas Gohr $cookie = ($this->control !== null) 158*dad993c5SAndreas Gohr ? $this->control->getCookie() 159*dad993c5SAndreas Gohr : ''; 160*dad993c5SAndreas Gohr $message = $this->client->sendAndReceive( 161*dad993c5SAndreas Gohr $this->search, 162*dad993c5SAndreas Gohr Controls::paging($size ?? $this->size, $cookie) 163*dad993c5SAndreas Gohr ->setCriticality($this->isCritical) 164*dad993c5SAndreas Gohr ); 165*dad993c5SAndreas Gohr $control = $message->controls() 166*dad993c5SAndreas Gohr ->get(Control::OID_PAGING); 167*dad993c5SAndreas Gohr 1680b3fd2d3SAndreas Gohr if ($control !== null && !$control instanceof PagingControl) { 1690b3fd2d3SAndreas Gohr throw new ProtocolException(sprintf( 1700b3fd2d3SAndreas Gohr 'Expected a paging control, but received: %s.', 1710b3fd2d3SAndreas Gohr get_class($control) 1720b3fd2d3SAndreas Gohr )); 1730b3fd2d3SAndreas Gohr } 1740b3fd2d3SAndreas Gohr # OpenLDAP returns no paging control in response to an abandon request. However, other LDAP implementations do; 1750b3fd2d3SAndreas Gohr # such as Active Directory. It's not clear from the paging RFC which is correct. 176*dad993c5SAndreas Gohr if ($control === null && $size !== 0 && $this->isCritical) { 1770b3fd2d3SAndreas Gohr throw new ProtocolException('Expected a paging control, but received none.'); 1780b3fd2d3SAndreas Gohr } 179*dad993c5SAndreas Gohr # The server does not support paging, but the control was not marked as critical. In this case the server will 180*dad993c5SAndreas Gohr # return results but might ignore the control altogether. 181*dad993c5SAndreas Gohr if ($control === null && $size !== 0 && !$this->isCritical) { 182*dad993c5SAndreas Gohr $this->ended = true; 183*dad993c5SAndreas Gohr } 1840b3fd2d3SAndreas Gohr $this->control = $control; 1850b3fd2d3SAndreas Gohr /** @var SearchResponse $response */ 1860b3fd2d3SAndreas Gohr $response = $message->getResponse(); 1870b3fd2d3SAndreas Gohr 1880b3fd2d3SAndreas Gohr return $response->getEntries(); 1890b3fd2d3SAndreas Gohr } 1900b3fd2d3SAndreas Gohr} 191