blob: c092173e9b4761321e35a9027733c0366e94bfd0 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace LdapRecord\Models\Attributes;
4
5use LdapRecord\EscapesValues;
6use LdapRecord\Support\Arr;
7
8class DistinguishedName
9{
10 use EscapesValues;
11
12 /**
13 * The underlying raw value.
14 *
15 * @var string|null
16 */
17 protected $value;
18
19 /**
20 * Constructor.
21 *
22 * @param string|null $value
23 */
24 public function __construct($value = null)
25 {
26 $this->value = trim($value);
27 }
28
29 /**
30 * Get the distinguished name value.
31 *
32 * @return string
33 */
34 public function __toString()
35 {
36 return (string) $this->value;
37 }
38
39 /**
40 * Alias of the "build" method.
41 *
42 * @param string|null $value
43 *
44 * @return DistinguishedNameBuilder
45 */
46 public static function of($value = null)
47 {
48 return static::build($value);
49 }
50
51 /**
52 * Get a new DN builder object from the given DN.
53 *
54 * @param string|null $value
55 *
56 * @return DistinguishedNameBuilder
57 */
58 public static function build($value = null)
59 {
60 return new DistinguishedNameBuilder($value);
61 }
62
63 /**
64 * Make a new distinguished name instance.
65 *
66 * @param string|null $value
67 *
68 * @return static
69 */
70 public static function make($value = null)
71 {
72 return new static($value);
73 }
74
75 /**
76 * Explode a distinguished name into relative distinguished names.
77 *
78 * @param string $dn
79 *
80 * @return array
81 */
82 public static function explode($dn)
83 {
84 $dn = ldap_explode_dn($dn, $withoutAttributes = false);
85
86 if (! is_array($dn)) {
87 return [];
88 }
89
90 if (! array_key_exists('count', $dn)) {
91 return [];
92 }
93
94 unset($dn['count']);
95
96 return $dn;
97 }
98
99 /**
100 * Un-escapes a hexadecimal string into its original string representation.
101 *
102 * @param string $value
103 *
104 * @return string
105 */
106 public static function unescape($value)
107 {
108 return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/', function ($matches) {
109 return chr(hexdec($matches[1]));
110 }, $value);
111 }
112
113 /**
114 * Explode the RDN into an attribute and value.
115 *
116 * @param string $rdn
117 *
118 * @return array
119 */
120 public static function explodeRdn($rdn)
121 {
122 return explode('=', $rdn, $limit = 2);
123 }
124
125 /**
126 * Implode the component attribute and value into an RDN.
127 *
128 * @param string $rdn
129 *
130 * @return string
131 */
132 public static function makeRdn(array $component)
133 {
134 return implode('=', $component);
135 }
136
137 /**
138 * Get the underlying value.
139 *
140 * @return string|null
141 */
142 public function get()
143 {
144 return $this->value;
145 }
146
147 /**
148 * Set the underlying value.
149 *
150 * @param string|null $value
151 *
152 * @return $this
153 */
154 public function set($value)
155 {
156 $this->value = $value;
157
158 return $this;
159 }
160
161 /**
162 * Get the distinguished name values without attributes.
163 *
164 * @return array
165 */
166 public function values()
167 {
168 $values = [];
169
170 foreach ($this->multi() as [, $value]) {
171 $values[] = static::unescape($value);
172 }
173
174 return $values;
175 }
176
177 /**
178 * Get the distinguished name attributes without values.
179 *
180 * @return array
181 */
182 public function attributes()
183 {
184 $attributes = [];
185
186 foreach ($this->multi() as [$attribute]) {
187 $attributes[] = $attribute;
188 }
189
190 return $attributes;
191 }
192
193 /**
194 * Get the distinguished name components with attributes.
195 *
196 * @return array
197 */
198 public function components()
199 {
200 $components = [];
201
202 foreach ($this->multi() as [$attribute, $value]) {
203 // When a distinguished name is exploded, the values are automatically
204 // escaped. This cannot be opted out of. Here we will unescape
205 // the attribute value, then re-escape it to its original
206 // representation from the server using the "dn" flag.
207 $value = $this->escape(static::unescape($value))->dn();
208
209 $components[] = static::makeRdn([$attribute, $value]);
210 }
211
212 return $components;
213 }
214
215 /**
216 * Convert the distinguished name into an associative array.
217 *
218 * @return array
219 */
220 public function assoc()
221 {
222 $map = [];
223
224 foreach ($this->multi() as [$attribute, $value]) {
225 $attribute = $this->normalize($attribute);
226
227 array_key_exists($attribute, $map)
228 ? $map[$attribute][] = $value
229 : $map[$attribute] = [$value];
230 }
231
232 return $map;
233 }
234
235 /**
236 * Split the RDNs into a multi-dimensional array.
237 *
238 * @return array
239 */
240 public function multi()
241 {
242 return array_map(function ($rdn) {
243 return static::explodeRdn($rdn);
244 }, $this->rdns());
245 }
246
247 /**
248 * Split the distinguished name into an array of unescaped RDN's.
249 *
250 * @return array
251 */
252 public function rdns()
253 {
254 return static::explode($this->value);
255 }
256
257 /**
258 * Get the first RDNs value.
259 *
260 * @return string|null
261 */
262 public function name()
263 {
264 return Arr::first($this->values());
265 }
266
267 /**
268 * Get the first RDNs attribute.
269 *
270 * @return string|null
271 */
272 public function head()
273 {
274 return Arr::first($this->attributes());
275 }
276
277 /**
278 * Get the relative distinguished name.
279 *
280 * @return string|null
281 */
282 public function relative()
283 {
284 return Arr::first($this->components());
285 }
286
287 /**
288 * Alias of relative().
289 *
290 * Get the first RDN from the distinguished name.
291 *
292 * @return string|null
293 */
294 public function first()
295 {
296 return $this->relative();
297 }
298
299 /**
300 * Get the parent distinguished name.
301 *
302 * @return string|null
303 */
304 public function parent()
305 {
306 $components = $this->components();
307
308 array_shift($components);
309
310 return implode(',', $components) ?: null;
311 }
312
313 /**
314 * Determine if the current distinguished name is a parent of the given child.
315 *
316 * @param DistinguishedName $child
317 *
318 * @return bool
319 */
320 public function isParentOf(self $child)
321 {
322 return $child->isChildOf($this);
323 }
324
325 /**
326 * Determine if the current distinguished name is a child of the given parent.
327 *
328 * @param DistinguishedName $parent
329 *
330 * @return bool
331 */
332 public function isChildOf(self $parent)
333 {
334 if (
335 empty($components = $this->components()) ||
336 empty($parentComponents = $parent->components())
337 ) {
338 return false;
339 }
340
341 array_shift($components);
342
343 return $this->compare($components, $parentComponents);
344 }
345
346 /**
347 * Determine if the current distinguished name is an ancestor of the descendant.
348 *
349 * @param DistinguishedName $descendant
350 *
351 * @return bool
352 */
353 public function isAncestorOf(self $descendant)
354 {
355 return $descendant->isDescendantOf($this);
356 }
357
358 /**
359 * Determine if the current distinguished name is a descendant of the ancestor.
360 *
361 * @param DistinguishedName $ancestor
362 *
363 * @return bool
364 */
365 public function isDescendantOf(self $ancestor)
366 {
367 if (
368 empty($components = $this->components()) ||
369 empty($ancestorComponents = $ancestor->components())
370 ) {
371 return false;
372 }
373
374 if (! $length = count($components) - count($ancestorComponents)) {
375 return false;
376 }
377
378 array_splice($components, $offset = 0, $length);
379
380 return $this->compare($components, $ancestorComponents);
381 }
382
383 /**
384 * Compare whether the two distinguished name values are equal.
385 *
386 * @param array $values
387 * @param array $other
388 *
389 * @return bool
390 */
391 protected function compare(array $values, array $other)
392 {
393 return $this->recase($values) == $this->recase($other);
394 }
395
396 /**
397 * Recase the array values.
398 *
399 * @param array $values
400 *
401 * @return array
402 */
403 protected function recase(array $values)
404 {
405 return array_map([$this, 'normalize'], $values);
406 }
407
408 /**
409 * Normalize the string value.
410 *
411 * @param string $value
412 *
413 * @return string
414 */
415 protected function normalize($value)
416 {
417 return strtolower($value);
418 }
419}