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\Entry\Attribute; 14use FreeDSx\Ldap\Entry\Dn; 15use FreeDSx\Ldap\Entry\Entry; 16use FreeDSx\Ldap\Entry\Option; 17use FreeDSx\Ldap\Exception\RuntimeException; 18use FreeDSx\Ldap\LdapClient; 19 20/** 21 * Provides simple helper APIs for retrieving ranged results for an entry attribute. 22 * 23 * @see https://docs.microsoft.com/en-us/windows/desktop/adsi/attribute-range-retrieval 24 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 25 */ 26class RangeRetrieval 27{ 28 /** 29 * @var LdapClient 30 */ 31 protected $client; 32 33 /** 34 * @param LdapClient $client 35 */ 36 public function __construct(LdapClient $client) 37 { 38 $this->client = $client; 39 } 40 41 /** 42 * Get a specific ranged attribute by name from an entry. If it does not exist it will return null. 43 * 44 * @param string|Attribute $attribute 45 */ 46 public function getRanged(Entry $entry, $attribute): ?Attribute 47 { 48 $attribute = $attribute instanceof Attribute ? new Attribute($attribute->getName()) : new Attribute($attribute); 49 50 foreach ($this->getAllRanged($entry) as $rangedAttribute) { 51 if ($rangedAttribute->equals($attribute)) { 52 return $rangedAttribute; 53 } 54 } 55 56 return null; 57 } 58 59 /** 60 * Get all ranged attributes as an array from a entry. 61 * 62 * @param Entry $entry 63 * @return Attribute[] 64 */ 65 public function getAllRanged(Entry $entry): array 66 { 67 $ranged = []; 68 69 foreach ($entry->getAttributes() as $attribute) { 70 if (!$attribute->hasOptions()) { 71 continue; 72 } 73 /** @var Option $option */ 74 foreach ($attribute->getOptions() as $option) { 75 if ($option->isRange()) { 76 $ranged[] = $attribute; 77 break; 78 } 79 } 80 } 81 82 return $ranged; 83 } 84 85 /** 86 * A simple check to determine if an entry contains any ranged attributes. Optionally pass an attribute 87 * 88 * @param Entry $entry 89 * @param Attribute|string|null $attribute 90 * @return bool 91 */ 92 public function hasRanged(Entry $entry, $attribute = null): bool 93 { 94 return (bool) ($attribute !== null ? $this->getRanged($entry, $attribute) : $this->getAllRanged($entry)); 95 } 96 97 /** 98 * Check if an attribute has more range values that can be queried. 99 * 100 * @param Attribute $attribute 101 * @return bool 102 */ 103 public function hasMoreValues(Attribute $attribute): bool 104 { 105 if (($range = $this->getRangeOption($attribute)) === null) { 106 return false; 107 } 108 109 return $range->getHighRange() !== '*'; 110 } 111 112 /** 113 * Given a specific Entry/DN and an attribute, get the next set of ranged values available. Optionally pass a third 114 * parameter to control how many values to grab next. 115 * 116 * @param Entry|Dn|string $entry 117 * @param Attribute $attribute 118 * @param string|int $amount 119 * @return Attribute 120 * @throws \FreeDSx\Ldap\Exception\OperationException 121 */ 122 public function getMoreValues($entry, Attribute $attribute, $amount = '*'): Attribute 123 { 124 if (($range = $this->getRangeOption($attribute)) === null || !$this->hasMoreValues($attribute)) { 125 return new Attribute($attribute->getName()); 126 } 127 if ($amount !== '*') { 128 $amount = (int) $amount + (int) $range->getHighRange(); 129 } 130 $attrReq = new Attribute($attribute->getName()); 131 $startAt = (int) $range->getHighRange() + 1; 132 $attrReq->getOptions()->set(Option::fromRange((string) $startAt, (string) $amount)); 133 $result = $this->client->readOrFail($entry, [$attrReq]); 134 135 $attrResult = $result->get($attribute->getName()); 136 if ($attrResult === null) { 137 throw new RuntimeException(sprintf( 138 'The attribute %s was not returned from LDAP', 139 $attribute->getName() 140 )); 141 } 142 if (($range = $this->getRangeOption($attrResult)) === null) { 143 throw new RuntimeException(sprintf( 144 'No ranged option received for attribute "%s" on "%s".', 145 $attribute->getName(), 146 $result->getDn()->toString() 147 )); 148 } 149 150 return $attrResult; 151 } 152 153 /** 154 * Given a specific entry and attribute, range retrieve all values of the attribute. 155 * 156 * @param Entry|Dn|string $entry 157 * @param string|Attribute $attribute 158 * @return Attribute 159 * @throws \FreeDSx\Ldap\Exception\OperationException 160 */ 161 public function getAllValues($entry, $attribute): Attribute 162 { 163 $attrResult = $attribute instanceof Attribute ? new Attribute($attribute->getName()) : new Attribute($attribute); 164 $attrResult->getOptions()->set(Option::fromRange('0')); 165 166 $entry = $this->client->readOrFail($entry, [$attrResult]); 167 $attribute = $this->getRanged($entry, $attrResult); 168 if ($attribute === null) { 169 throw new RuntimeException(sprintf( 170 'No ranged result received for "%s" on entry "%s".', 171 $attrResult->getName(), 172 $entry->getDn()->toString() 173 )); 174 } 175 176 $attrResult->add(...$attribute->getValues()); 177 while ($this->hasMoreValues($attribute)) { 178 $attribute = $this->getMoreValues($entry, $attribute); 179 $attrResult->add(...$attribute->getValues()); 180 } 181 182 return $attrResult; 183 } 184 185 /** 186 * @param Attribute $attribute 187 * @return Option|null 188 */ 189 protected function getRangeOption(Attribute $attribute): ?Option 190 { 191 /** @var Option $option */ 192 foreach ($attribute->getOptions() as $option) { 193 if ($option->isRange()) { 194 return $option; 195 } 196 } 197 198 return null; 199 } 200} 201