1<?php 2/** 3 * This file is part of the FreeDSx LDAP package. 4 * 5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FreeDSx\Ldap\Search; 12 13use FreeDSx\Ldap\Control\Control; 14use FreeDSx\Ldap\Control\Sorting\SortingControl; 15use FreeDSx\Ldap\Control\Vlv\VlvControl; 16use FreeDSx\Ldap\Control\Vlv\VlvResponseControl; 17use FreeDSx\Ldap\Controls; 18use FreeDSx\Ldap\Entry\Entries; 19use FreeDSx\Ldap\Exception\ProtocolException; 20use FreeDSx\Ldap\LdapClient; 21use FreeDSx\Ldap\Operation\Request\SearchRequest; 22use FreeDSx\Ldap\Operation\Response\SearchResponse; 23use FreeDSx\Ldap\Search\Filter\GreaterThanOrEqualFilter; 24 25/** 26 * Provides a simple wrapper around a VLV (Virtual List View) search operation. 27 * 28 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 29 */ 30class Vlv 31{ 32 /** 33 * @var LdapClient 34 */ 35 protected $client; 36 37 /** 38 * @var SearchRequest 39 */ 40 protected $search; 41 42 /** 43 * @var VlvResponseControl|null 44 */ 45 protected $control; 46 47 /** 48 * @var int 49 */ 50 protected $before; 51 52 /** 53 * @var int 54 */ 55 protected $after; 56 57 /** 58 * @var int 59 */ 60 protected $offset = 1; 61 62 /** 63 * @var GreaterThanOrEqualFilter 64 */ 65 protected $filter; 66 67 /** 68 * @var SortingControl 69 */ 70 protected $sort; 71 72 /** 73 * @var bool 74 */ 75 protected $asPercentage = false; 76 77 /** 78 * @param LdapClient $client 79 * @param SearchRequest $search 80 * @param SortingControl|\FreeDSx\Ldap\Control\Sorting\SortKey|string $sort 81 * @param int $before 82 * @param int $after 83 */ 84 public function __construct(LdapClient $client, SearchRequest $search, $sort, int $after = 100, int $before = 0) 85 { 86 $this->client = $client; 87 $this->search = $search; 88 $this->sort = $sort instanceof SortingControl ? $sort : Controls::sort($sort); 89 $this->before = $before; 90 $this->after = $after; 91 } 92 93 /** 94 * As a percentage the moveTo, moveForward, moveBackward, and position methods work with numbers 0 - 100 and should 95 * be interpreted as percentages. 96 * 97 * @param bool $asPercentage 98 * @return $this 99 */ 100 public function asPercentage(bool $asPercentage = true) 101 { 102 $this->asPercentage = $asPercentage; 103 104 return $this; 105 } 106 107 /** 108 * Request to start at a specific offset/percentage of entries. 109 * 110 * @param int $offset 111 * @return $this 112 */ 113 public function startAt(int $offset) 114 { 115 $this->offset = $offset; 116 117 return $this; 118 } 119 120 /** 121 * Move backward the specified number or percentage from the current position. 122 * 123 * @param int $size 124 * @return $this 125 */ 126 public function moveBackward(int $size) 127 { 128 $this->offset = ($this->offset - $size < 0) ? 0 : $this->offset - $size; 129 130 return $this; 131 } 132 133 /** 134 * Move forward the specified number or percentage from the current position. 135 * 136 * @param int $size 137 * @return $this 138 */ 139 public function moveForward(int $size) 140 { 141 $this->offset = ($this->asPercentage && ($this->offset + $size) > 100) ? 100 : $this->offset + $size; 142 143 return $this; 144 } 145 146 /** 147 * Moves the starting entry of the list to a specific position/percentage of the total list. An alias for startAt(). 148 * 149 * @param int $position 150 * @return Vlv 151 */ 152 public function moveTo(int $position) 153 { 154 return $this->startAt($position); 155 } 156 157 /** 158 * Retrieve the following number of entries after the position specified. 159 * 160 * @param int $after 161 * @return $this 162 */ 163 public function afterPosition(int $after) 164 { 165 $this->after = $after; 166 167 return $this; 168 } 169 170 /** 171 * Retrieve the following number of entries before the position specified. 172 * 173 * @param int $before 174 * @return $this 175 */ 176 public function beforePosition(int $before) 177 { 178 $this->before = $before; 179 180 return $this; 181 } 182 183 /** 184 * Get the servers entry offset of the current list. 185 * 186 * @return int|null 187 */ 188 public function listOffset(): ?int 189 { 190 return ($this->control !== null) ? $this->control->getOffset() : null; 191 } 192 193 /** 194 * Get the severs estimate, from the last request, that indicates how many entries are in the list. 195 * 196 * @return int|null 197 */ 198 public function listSize(): ?int 199 { 200 return ($this->control !== null) ? $this->control->getCount() : null; 201 } 202 203 /** 204 * Get the current position in the list. When as percentage was specified this will be expressed as a percentage. 205 * Use listOffset for a specific entry offset position. 206 * 207 * @return int|null 208 */ 209 public function position(): ?int 210 { 211 $control = $this->control; 212 $pos = $control === null ? null : $control->getOffset(); 213 if ($control === null || $pos === null) { 214 return null; 215 } 216 217 if ($this->asPercentage) { 218 return (int) round($pos / ((int) $control->getCount() / 100)); 219 } else { 220 return $control->getOffset(); 221 } 222 } 223 224 /** 225 * Whether or not we are at the end of the list. 226 * 227 * @return bool 228 */ 229 public function isAtEndOfList(): bool 230 { 231 if ($this->control === null) { 232 return false; 233 } 234 235 $control = $this->control; 236 if ((((int) $control->getOffset() + $this->after) >= (int) $control->getCount())) { 237 return true; 238 } 239 240 return $control->getOffset() === $control->getCount(); 241 } 242 243 /** 244 * Whether or not we are currently at the start of the list. 245 * 246 * @return bool 247 */ 248 public function isAtStartOfList(): bool 249 { 250 if ($this->control === null) { 251 return false; 252 } 253 $control = $this->control; 254 if ($this->before !== 0 && ((int) $control->getOffset() - $this->before) <= 1) { 255 return true; 256 } 257 258 return $control->getOffset() === 1; 259 } 260 261 /** 262 * @return Entries 263 * @throws ProtocolException 264 */ 265 public function getEntries(): Entries 266 { 267 return $this->send(); 268 } 269 270 /** 271 * @throws ProtocolException 272 */ 273 protected function send(): Entries 274 { 275 $contextId = ($this->control !== null) ? $this->control->getContextId() : null; 276 $message = $this->client->sendAndReceive($this->search, $this->createVlvControl($contextId), $this->sort); 277 $control = $message->controls()->get(Control::OID_VLV_RESPONSE); 278 if ($control === null || !$control instanceof VlvResponseControl) { 279 throw new ProtocolException('Expected a VLV response control, but received none.'); 280 } 281 $this->control = $control; 282 /** @var SearchResponse $response */ 283 $response = $message->getResponse(); 284 285 return $response->getEntries(); 286 } 287 288 /** 289 * @return VlvControl 290 */ 291 protected function createVlvControl(?string $contextId): VlvControl 292 { 293 if ($this->filter !== null) { 294 return Controls::vlvFilter($this->before, $this->after, $this->filter, $contextId); 295 } 296 # An offset of 1 and a content size of zero starts from the beginning entry (server uses its assumed count). 297 $count = ($this->control !== null) ? (int) $this->control->getCount() : 0; 298 299 # In percentage mode start off with an assumed count of 100, as the formula the server uses should give us the 300 # expected result 301 if ($this->control === null && $this->asPercentage) { 302 $count = 100; 303 } 304 305 $offset = $this->offset; 306 # Final checks to make sure if we are using a percentage that valid values are used. 307 if ($this->asPercentage && $this->offset > 100) { 308 $offset = 100; 309 } elseif ($this->asPercentage && $this->offset < 0) { 310 $offset = 0; 311 } 312 313 if ($this->asPercentage && $this->control !== null) { 314 $offset = (int) round(((int) $this->control->getCount() / 100) * $offset); 315 } 316 317 return Controls::vlv($this->before, $this->after, $offset, $count, $contextId); 318 } 319} 320