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