* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace FreeDSx\Ldap\Search; use FreeDSx\Ldap\Control\Control; use FreeDSx\Ldap\Control\Sorting\SortingControl; use FreeDSx\Ldap\Control\Sorting\SortKey; use FreeDSx\Ldap\Control\Vlv\VlvControl; use FreeDSx\Ldap\Control\Vlv\VlvResponseControl; use FreeDSx\Ldap\Controls; use FreeDSx\Ldap\Entry\Entries; use FreeDSx\Ldap\Exception\OperationException; use FreeDSx\Ldap\Exception\ProtocolException; use FreeDSx\Ldap\LdapClient; use FreeDSx\Ldap\Operation\Request\SearchRequest; use FreeDSx\Ldap\Operation\Response\SearchResponse; use FreeDSx\Ldap\Search\Filter\GreaterThanOrEqualFilter; /** * Provides a simple wrapper around a VLV (Virtual List View) search operation. * * @author Chad Sikorra */ class Vlv { /** * @var LdapClient */ protected $client; /** * @var SearchRequest */ protected $search; /** * @var VlvResponseControl|null */ protected $control; /** * @var int */ protected $before; /** * @var int */ protected $after; /** * @var int */ protected $offset = 1; /** * @var GreaterThanOrEqualFilter */ protected $filter; /** * @var SortingControl */ protected $sort; /** * @var bool */ protected $asPercentage = false; /** * @param LdapClient $client * @param SearchRequest $search * @param SortingControl|SortKey|string $sort * @param int $before * @param int $after */ public function __construct(LdapClient $client, SearchRequest $search, $sort, int $after = 100, int $before = 0) { $this->client = $client; $this->search = $search; $this->sort = $sort instanceof SortingControl ? $sort : Controls::sort($sort); $this->before = $before; $this->after = $after; } /** * As a percentage the moveTo, moveForward, moveBackward, and position methods work with numbers 0 - 100 and should * be interpreted as percentages. * * @param bool $asPercentage * @return $this */ public function asPercentage(bool $asPercentage = true) { $this->asPercentage = $asPercentage; return $this; } /** * Request to start at a specific offset/percentage of entries. * * @param int $offset * @return $this */ public function startAt(int $offset) { $this->offset = $offset; return $this; } /** * Move backward the specified number or percentage from the current position. * * @param int $size * @return $this */ public function moveBackward(int $size) { $this->offset = ($this->offset - $size < 0) ? 0 : $this->offset - $size; return $this; } /** * Move forward the specified number or percentage from the current position. * * @param int $size * @return $this */ public function moveForward(int $size) { $this->offset = ($this->asPercentage && ($this->offset + $size) > 100) ? 100 : $this->offset + $size; return $this; } /** * Moves the starting entry of the list to a specific position/percentage of the total list. An alias for startAt(). * * @param int $position * @return Vlv */ public function moveTo(int $position) { return $this->startAt($position); } /** * Retrieve the following number of entries after the position specified. * * @param int $after * @return $this */ public function afterPosition(int $after) { $this->after = $after; return $this; } /** * Retrieve the following number of entries before the position specified. * * @param int $before * @return $this */ public function beforePosition(int $before) { $this->before = $before; return $this; } /** * Get the servers entry offset of the current list. * * @return int|null */ public function listOffset(): ?int { return ($this->control !== null) ? $this->control->getOffset() : null; } /** * Get the severs estimate, from the last request, that indicates how many entries are in the list. * * @return int|null */ public function listSize(): ?int { return ($this->control !== null) ? $this->control->getCount() : null; } /** * Get the current position in the list. When as percentage was specified this will be expressed as a percentage. * Use listOffset for a specific entry offset position. * * @return int|null */ public function position(): ?int { $control = $this->control; $pos = $control === null ? null : $control->getOffset(); if ($control === null || $pos === null) { return null; } if ($this->asPercentage) { return (int) round($pos / ((int) $control->getCount() / 100)); } else { return $control->getOffset(); } } /** * Whether or not we are at the end of the list. * * @return bool */ public function isAtEndOfList(): bool { if ($this->control === null) { return false; } $control = $this->control; if ((((int) $control->getOffset() + $this->after) >= (int) $control->getCount())) { return true; } return $control->getOffset() === $control->getCount(); } /** * Whether or not we are currently at the start of the list. * * @return bool */ public function isAtStartOfList(): bool { if ($this->control === null) { return false; } $control = $this->control; if ($this->before !== 0 && ((int) $control->getOffset() - $this->before) <= 1) { return true; } return $control->getOffset() === 1; } /** * @return Entries * @throws ProtocolException * @throws OperationException */ public function getEntries(): Entries { return $this->send(); } /** * @return Entries * @throws OperationException */ protected function send(): Entries { $contextId = ($this->control !== null) ? $this->control->getContextId() : null; $message = $this->client->sendAndReceive($this->search, $this->createVlvControl($contextId), $this->sort); $control = $message->controls()->get(Control::OID_VLV_RESPONSE); if ($control === null || !$control instanceof VlvResponseControl) { throw new ProtocolException('Expected a VLV response control, but received none.'); } $this->control = $control; /** @var SearchResponse $response */ $response = $message->getResponse(); return $response->getEntries(); } /** * @return VlvControl */ protected function createVlvControl(?string $contextId): VlvControl { if ($this->filter !== null) { return Controls::vlvFilter($this->before, $this->after, $this->filter, $contextId); } # An offset of 1 and a content size of zero starts from the beginning entry (server uses its assumed count). $count = ($this->control !== null) ? (int) $this->control->getCount() : 0; # In percentage mode start off with an assumed count of 100, as the formula the server uses should give us the # expected result if ($this->control === null && $this->asPercentage) { $count = 100; } $offset = $this->offset; # Final checks to make sure if we are using a percentage that valid values are used. if ($this->asPercentage && $this->offset > 100) { $offset = 100; } elseif ($this->asPercentage && $this->offset < 0) { $offset = 0; } if ($this->asPercentage && $this->control !== null) { $offset = (int) round(((int) $this->control->getCount() / 100) * $offset); } return Controls::vlv($this->before, $this->after, $offset, $count, $contextId); } }