xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Search/Vlv.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\Sorting\SortingControl;
16*dad993c5SAndreas Gohruse FreeDSx\Ldap\Control\Sorting\SortKey;
170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Vlv\VlvControl;
180b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Vlv\VlvResponseControl;
190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Controls;
200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entries;
21*dad993c5SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException;
220b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\ProtocolException;
230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\LdapClient;
240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\SearchRequest;
250b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Response\SearchResponse;
260b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Search\Filter\GreaterThanOrEqualFilter;
270b3fd2d3SAndreas Gohr
280b3fd2d3SAndreas Gohr/**
290b3fd2d3SAndreas Gohr * Provides a simple wrapper around a VLV (Virtual List View) search operation.
300b3fd2d3SAndreas Gohr *
310b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
320b3fd2d3SAndreas Gohr */
330b3fd2d3SAndreas Gohrclass Vlv
340b3fd2d3SAndreas Gohr{
350b3fd2d3SAndreas Gohr    /**
360b3fd2d3SAndreas Gohr     * @var LdapClient
370b3fd2d3SAndreas Gohr     */
380b3fd2d3SAndreas Gohr    protected $client;
390b3fd2d3SAndreas Gohr
400b3fd2d3SAndreas Gohr    /**
410b3fd2d3SAndreas Gohr     * @var SearchRequest
420b3fd2d3SAndreas Gohr     */
430b3fd2d3SAndreas Gohr    protected $search;
440b3fd2d3SAndreas Gohr
450b3fd2d3SAndreas Gohr    /**
460b3fd2d3SAndreas Gohr     * @var VlvResponseControl|null
470b3fd2d3SAndreas Gohr     */
480b3fd2d3SAndreas Gohr    protected $control;
490b3fd2d3SAndreas Gohr
500b3fd2d3SAndreas Gohr    /**
510b3fd2d3SAndreas Gohr     * @var int
520b3fd2d3SAndreas Gohr     */
530b3fd2d3SAndreas Gohr    protected $before;
540b3fd2d3SAndreas Gohr
550b3fd2d3SAndreas Gohr    /**
560b3fd2d3SAndreas Gohr     * @var int
570b3fd2d3SAndreas Gohr     */
580b3fd2d3SAndreas Gohr    protected $after;
590b3fd2d3SAndreas Gohr
600b3fd2d3SAndreas Gohr    /**
610b3fd2d3SAndreas Gohr     * @var int
620b3fd2d3SAndreas Gohr     */
630b3fd2d3SAndreas Gohr    protected $offset = 1;
640b3fd2d3SAndreas Gohr
650b3fd2d3SAndreas Gohr    /**
660b3fd2d3SAndreas Gohr     * @var GreaterThanOrEqualFilter
670b3fd2d3SAndreas Gohr     */
680b3fd2d3SAndreas Gohr    protected $filter;
690b3fd2d3SAndreas Gohr
700b3fd2d3SAndreas Gohr    /**
710b3fd2d3SAndreas Gohr     * @var SortingControl
720b3fd2d3SAndreas Gohr     */
730b3fd2d3SAndreas Gohr    protected $sort;
740b3fd2d3SAndreas Gohr
750b3fd2d3SAndreas Gohr    /**
760b3fd2d3SAndreas Gohr     * @var bool
770b3fd2d3SAndreas Gohr     */
780b3fd2d3SAndreas Gohr    protected $asPercentage = false;
790b3fd2d3SAndreas Gohr
800b3fd2d3SAndreas Gohr    /**
810b3fd2d3SAndreas Gohr     * @param LdapClient $client
820b3fd2d3SAndreas Gohr     * @param SearchRequest $search
83*dad993c5SAndreas Gohr     * @param SortingControl|SortKey|string $sort
840b3fd2d3SAndreas Gohr     * @param int $before
850b3fd2d3SAndreas Gohr     * @param int $after
860b3fd2d3SAndreas Gohr     */
870b3fd2d3SAndreas Gohr    public function __construct(LdapClient $client, SearchRequest $search, $sort, int $after = 100, int $before = 0)
880b3fd2d3SAndreas Gohr    {
890b3fd2d3SAndreas Gohr        $this->client = $client;
900b3fd2d3SAndreas Gohr        $this->search = $search;
910b3fd2d3SAndreas Gohr        $this->sort = $sort instanceof SortingControl ? $sort : Controls::sort($sort);
920b3fd2d3SAndreas Gohr        $this->before = $before;
930b3fd2d3SAndreas Gohr        $this->after = $after;
940b3fd2d3SAndreas Gohr    }
950b3fd2d3SAndreas Gohr
960b3fd2d3SAndreas Gohr    /**
970b3fd2d3SAndreas Gohr     * As a percentage the moveTo, moveForward, moveBackward, and position methods work with numbers 0 - 100 and should
980b3fd2d3SAndreas Gohr     * be interpreted as percentages.
990b3fd2d3SAndreas Gohr     *
1000b3fd2d3SAndreas Gohr     * @param bool $asPercentage
1010b3fd2d3SAndreas Gohr     * @return $this
1020b3fd2d3SAndreas Gohr     */
1030b3fd2d3SAndreas Gohr    public function asPercentage(bool $asPercentage = true)
1040b3fd2d3SAndreas Gohr    {
1050b3fd2d3SAndreas Gohr        $this->asPercentage = $asPercentage;
1060b3fd2d3SAndreas Gohr
1070b3fd2d3SAndreas Gohr        return $this;
1080b3fd2d3SAndreas Gohr    }
1090b3fd2d3SAndreas Gohr
1100b3fd2d3SAndreas Gohr    /**
1110b3fd2d3SAndreas Gohr     * Request to start at a specific offset/percentage of entries.
1120b3fd2d3SAndreas Gohr     *
1130b3fd2d3SAndreas Gohr     * @param int $offset
1140b3fd2d3SAndreas Gohr     * @return $this
1150b3fd2d3SAndreas Gohr     */
1160b3fd2d3SAndreas Gohr    public function startAt(int $offset)
1170b3fd2d3SAndreas Gohr    {
1180b3fd2d3SAndreas Gohr        $this->offset = $offset;
1190b3fd2d3SAndreas Gohr
1200b3fd2d3SAndreas Gohr        return $this;
1210b3fd2d3SAndreas Gohr    }
1220b3fd2d3SAndreas Gohr
1230b3fd2d3SAndreas Gohr    /**
1240b3fd2d3SAndreas Gohr     * Move backward the specified number or percentage from the current position.
1250b3fd2d3SAndreas Gohr     *
1260b3fd2d3SAndreas Gohr     * @param int $size
1270b3fd2d3SAndreas Gohr     * @return $this
1280b3fd2d3SAndreas Gohr     */
1290b3fd2d3SAndreas Gohr    public function moveBackward(int $size)
1300b3fd2d3SAndreas Gohr    {
1310b3fd2d3SAndreas Gohr        $this->offset = ($this->offset - $size < 0) ? 0 : $this->offset - $size;
1320b3fd2d3SAndreas Gohr
1330b3fd2d3SAndreas Gohr        return $this;
1340b3fd2d3SAndreas Gohr    }
1350b3fd2d3SAndreas Gohr
1360b3fd2d3SAndreas Gohr    /**
1370b3fd2d3SAndreas Gohr     * Move forward the specified number or percentage from the current position.
1380b3fd2d3SAndreas Gohr     *
1390b3fd2d3SAndreas Gohr     * @param int $size
1400b3fd2d3SAndreas Gohr     * @return $this
1410b3fd2d3SAndreas Gohr     */
1420b3fd2d3SAndreas Gohr    public function moveForward(int $size)
1430b3fd2d3SAndreas Gohr    {
1440b3fd2d3SAndreas Gohr        $this->offset = ($this->asPercentage && ($this->offset + $size) > 100) ? 100 : $this->offset + $size;
1450b3fd2d3SAndreas Gohr
1460b3fd2d3SAndreas Gohr        return $this;
1470b3fd2d3SAndreas Gohr    }
1480b3fd2d3SAndreas Gohr
1490b3fd2d3SAndreas Gohr    /**
1500b3fd2d3SAndreas Gohr     * Moves the starting entry of the list to a specific position/percentage of the total list. An alias for startAt().
1510b3fd2d3SAndreas Gohr     *
1520b3fd2d3SAndreas Gohr     * @param int $position
1530b3fd2d3SAndreas Gohr     * @return Vlv
1540b3fd2d3SAndreas Gohr     */
1550b3fd2d3SAndreas Gohr    public function moveTo(int $position)
1560b3fd2d3SAndreas Gohr    {
1570b3fd2d3SAndreas Gohr        return $this->startAt($position);
1580b3fd2d3SAndreas Gohr    }
1590b3fd2d3SAndreas Gohr
1600b3fd2d3SAndreas Gohr    /**
1610b3fd2d3SAndreas Gohr     * Retrieve the following number of entries after the position specified.
1620b3fd2d3SAndreas Gohr     *
1630b3fd2d3SAndreas Gohr     * @param int $after
1640b3fd2d3SAndreas Gohr     * @return $this
1650b3fd2d3SAndreas Gohr     */
1660b3fd2d3SAndreas Gohr    public function afterPosition(int $after)
1670b3fd2d3SAndreas Gohr    {
1680b3fd2d3SAndreas Gohr        $this->after = $after;
1690b3fd2d3SAndreas Gohr
1700b3fd2d3SAndreas Gohr        return $this;
1710b3fd2d3SAndreas Gohr    }
1720b3fd2d3SAndreas Gohr
1730b3fd2d3SAndreas Gohr    /**
1740b3fd2d3SAndreas Gohr     * Retrieve the following number of entries before the position specified.
1750b3fd2d3SAndreas Gohr     *
1760b3fd2d3SAndreas Gohr     * @param int $before
1770b3fd2d3SAndreas Gohr     * @return $this
1780b3fd2d3SAndreas Gohr     */
1790b3fd2d3SAndreas Gohr    public function beforePosition(int $before)
1800b3fd2d3SAndreas Gohr    {
1810b3fd2d3SAndreas Gohr        $this->before = $before;
1820b3fd2d3SAndreas Gohr
1830b3fd2d3SAndreas Gohr        return $this;
1840b3fd2d3SAndreas Gohr    }
1850b3fd2d3SAndreas Gohr
1860b3fd2d3SAndreas Gohr    /**
1870b3fd2d3SAndreas Gohr     * Get the servers entry offset of the current list.
1880b3fd2d3SAndreas Gohr     *
1890b3fd2d3SAndreas Gohr     * @return int|null
1900b3fd2d3SAndreas Gohr     */
1910b3fd2d3SAndreas Gohr    public function listOffset(): ?int
1920b3fd2d3SAndreas Gohr    {
1930b3fd2d3SAndreas Gohr        return ($this->control !== null) ? $this->control->getOffset() : null;
1940b3fd2d3SAndreas Gohr    }
1950b3fd2d3SAndreas Gohr
1960b3fd2d3SAndreas Gohr    /**
1970b3fd2d3SAndreas Gohr     * Get the severs estimate, from the last request, that indicates how many entries are in the list.
1980b3fd2d3SAndreas Gohr     *
1990b3fd2d3SAndreas Gohr     * @return int|null
2000b3fd2d3SAndreas Gohr     */
2010b3fd2d3SAndreas Gohr    public function listSize(): ?int
2020b3fd2d3SAndreas Gohr    {
2030b3fd2d3SAndreas Gohr        return ($this->control !== null) ? $this->control->getCount() : null;
2040b3fd2d3SAndreas Gohr    }
2050b3fd2d3SAndreas Gohr
2060b3fd2d3SAndreas Gohr    /**
2070b3fd2d3SAndreas Gohr     * Get the current position in the list. When as percentage was specified this will be expressed as a percentage.
2080b3fd2d3SAndreas Gohr     * Use listOffset for a specific entry offset position.
2090b3fd2d3SAndreas Gohr     *
2100b3fd2d3SAndreas Gohr     * @return int|null
2110b3fd2d3SAndreas Gohr     */
2120b3fd2d3SAndreas Gohr    public function position(): ?int
2130b3fd2d3SAndreas Gohr    {
2140b3fd2d3SAndreas Gohr        $control = $this->control;
2150b3fd2d3SAndreas Gohr        $pos = $control === null ? null : $control->getOffset();
2160b3fd2d3SAndreas Gohr        if ($control === null || $pos === null) {
2170b3fd2d3SAndreas Gohr            return null;
2180b3fd2d3SAndreas Gohr        }
2190b3fd2d3SAndreas Gohr
2200b3fd2d3SAndreas Gohr        if ($this->asPercentage) {
2210b3fd2d3SAndreas Gohr            return (int) round($pos / ((int) $control->getCount() / 100));
2220b3fd2d3SAndreas Gohr        } else {
2230b3fd2d3SAndreas Gohr            return $control->getOffset();
2240b3fd2d3SAndreas Gohr        }
2250b3fd2d3SAndreas Gohr    }
2260b3fd2d3SAndreas Gohr
2270b3fd2d3SAndreas Gohr    /**
2280b3fd2d3SAndreas Gohr     * Whether or not we are at the end of the list.
2290b3fd2d3SAndreas Gohr     *
2300b3fd2d3SAndreas Gohr     * @return bool
2310b3fd2d3SAndreas Gohr     */
2320b3fd2d3SAndreas Gohr    public function isAtEndOfList(): bool
2330b3fd2d3SAndreas Gohr    {
2340b3fd2d3SAndreas Gohr        if ($this->control === null) {
2350b3fd2d3SAndreas Gohr            return false;
2360b3fd2d3SAndreas Gohr        }
2370b3fd2d3SAndreas Gohr
2380b3fd2d3SAndreas Gohr        $control = $this->control;
2390b3fd2d3SAndreas Gohr        if ((((int) $control->getOffset() + $this->after) >= (int) $control->getCount())) {
2400b3fd2d3SAndreas Gohr            return true;
2410b3fd2d3SAndreas Gohr        }
2420b3fd2d3SAndreas Gohr
2430b3fd2d3SAndreas Gohr        return $control->getOffset() === $control->getCount();
2440b3fd2d3SAndreas Gohr    }
2450b3fd2d3SAndreas Gohr
2460b3fd2d3SAndreas Gohr    /**
2470b3fd2d3SAndreas Gohr     * Whether or not we are currently at the start of the list.
2480b3fd2d3SAndreas Gohr     *
2490b3fd2d3SAndreas Gohr     * @return bool
2500b3fd2d3SAndreas Gohr     */
2510b3fd2d3SAndreas Gohr    public function isAtStartOfList(): bool
2520b3fd2d3SAndreas Gohr    {
2530b3fd2d3SAndreas Gohr        if ($this->control === null) {
2540b3fd2d3SAndreas Gohr            return false;
2550b3fd2d3SAndreas Gohr        }
2560b3fd2d3SAndreas Gohr        $control = $this->control;
2570b3fd2d3SAndreas Gohr        if ($this->before !== 0 && ((int) $control->getOffset() - $this->before) <= 1) {
2580b3fd2d3SAndreas Gohr            return true;
2590b3fd2d3SAndreas Gohr        }
2600b3fd2d3SAndreas Gohr
2610b3fd2d3SAndreas Gohr        return $control->getOffset() === 1;
2620b3fd2d3SAndreas Gohr    }
2630b3fd2d3SAndreas Gohr
2640b3fd2d3SAndreas Gohr    /**
2650b3fd2d3SAndreas Gohr     * @return Entries
2660b3fd2d3SAndreas Gohr     * @throws ProtocolException
267*dad993c5SAndreas Gohr     * @throws OperationException
2680b3fd2d3SAndreas Gohr     */
2690b3fd2d3SAndreas Gohr    public function getEntries(): Entries
2700b3fd2d3SAndreas Gohr    {
2710b3fd2d3SAndreas Gohr        return $this->send();
2720b3fd2d3SAndreas Gohr    }
2730b3fd2d3SAndreas Gohr
2740b3fd2d3SAndreas Gohr    /**
275*dad993c5SAndreas Gohr     * @return Entries
276*dad993c5SAndreas Gohr     * @throws OperationException
2770b3fd2d3SAndreas Gohr     */
2780b3fd2d3SAndreas Gohr    protected function send(): Entries
2790b3fd2d3SAndreas Gohr    {
2800b3fd2d3SAndreas Gohr        $contextId = ($this->control !== null) ? $this->control->getContextId() : null;
2810b3fd2d3SAndreas Gohr        $message = $this->client->sendAndReceive($this->search, $this->createVlvControl($contextId), $this->sort);
2820b3fd2d3SAndreas Gohr        $control = $message->controls()->get(Control::OID_VLV_RESPONSE);
2830b3fd2d3SAndreas Gohr        if ($control === null || !$control instanceof VlvResponseControl) {
2840b3fd2d3SAndreas Gohr            throw new ProtocolException('Expected a VLV response control, but received none.');
2850b3fd2d3SAndreas Gohr        }
2860b3fd2d3SAndreas Gohr        $this->control = $control;
2870b3fd2d3SAndreas Gohr        /** @var SearchResponse $response */
2880b3fd2d3SAndreas Gohr        $response = $message->getResponse();
2890b3fd2d3SAndreas Gohr
2900b3fd2d3SAndreas Gohr        return $response->getEntries();
2910b3fd2d3SAndreas Gohr    }
2920b3fd2d3SAndreas Gohr
2930b3fd2d3SAndreas Gohr    /**
2940b3fd2d3SAndreas Gohr     * @return VlvControl
2950b3fd2d3SAndreas Gohr     */
2960b3fd2d3SAndreas Gohr    protected function createVlvControl(?string $contextId): VlvControl
2970b3fd2d3SAndreas Gohr    {
2980b3fd2d3SAndreas Gohr        if ($this->filter !== null) {
2990b3fd2d3SAndreas Gohr            return Controls::vlvFilter($this->before, $this->after, $this->filter, $contextId);
3000b3fd2d3SAndreas Gohr        }
3010b3fd2d3SAndreas Gohr        # An offset of 1 and a content size of zero starts from the beginning entry (server uses its assumed count).
3020b3fd2d3SAndreas Gohr        $count = ($this->control !== null) ? (int) $this->control->getCount() : 0;
3030b3fd2d3SAndreas Gohr
3040b3fd2d3SAndreas Gohr        # In percentage mode start off with an assumed count of 100, as the formula the server uses should give us the
3050b3fd2d3SAndreas Gohr        # expected result
3060b3fd2d3SAndreas Gohr        if ($this->control === null && $this->asPercentage) {
3070b3fd2d3SAndreas Gohr            $count = 100;
3080b3fd2d3SAndreas Gohr        }
3090b3fd2d3SAndreas Gohr
3100b3fd2d3SAndreas Gohr        $offset = $this->offset;
3110b3fd2d3SAndreas Gohr        # Final checks to make sure if we are using a percentage that valid values are used.
3120b3fd2d3SAndreas Gohr        if ($this->asPercentage && $this->offset > 100) {
3130b3fd2d3SAndreas Gohr            $offset = 100;
3140b3fd2d3SAndreas Gohr        } elseif ($this->asPercentage && $this->offset < 0) {
3150b3fd2d3SAndreas Gohr            $offset = 0;
3160b3fd2d3SAndreas Gohr        }
3170b3fd2d3SAndreas Gohr
3180b3fd2d3SAndreas Gohr        if ($this->asPercentage && $this->control !== null) {
3190b3fd2d3SAndreas Gohr            $offset = (int) round(((int) $this->control->getCount() / 100) * $offset);
3200b3fd2d3SAndreas Gohr        }
3210b3fd2d3SAndreas Gohr
3220b3fd2d3SAndreas Gohr        return Controls::vlv($this->before, $this->after, $offset, $count, $contextId);
3230b3fd2d3SAndreas Gohr    }
3240b3fd2d3SAndreas Gohr}
325