xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Search/Paging.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
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