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\Entry; 130b3fd2d3SAndreas Gohr 14*dad993c5SAndreas Gohruse ArrayIterator; 15*dad993c5SAndreas Gohruse Countable; 16*dad993c5SAndreas Gohruse IteratorAggregate; 17*dad993c5SAndreas Gohruse Traversable; 18*dad993c5SAndreas Gohruse function array_keys; 19*dad993c5SAndreas Gohruse function array_search; 20*dad993c5SAndreas Gohruse function array_shift; 21*dad993c5SAndreas Gohruse function array_values; 22*dad993c5SAndreas Gohruse function count; 23*dad993c5SAndreas Gohruse function explode; 24*dad993c5SAndreas Gohruse function implode; 25*dad993c5SAndreas Gohruse function str_replace; 26*dad993c5SAndreas Gohruse function strpos; 27*dad993c5SAndreas Gohruse function strtolower; 28*dad993c5SAndreas Gohr 290b3fd2d3SAndreas Gohr/** 300b3fd2d3SAndreas Gohr * Represents an entry attribute and any values. 310b3fd2d3SAndreas Gohr * 320b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com> 330b3fd2d3SAndreas Gohr */ 34*dad993c5SAndreas Gohrclass Attribute implements IteratorAggregate, Countable 350b3fd2d3SAndreas Gohr{ 360b3fd2d3SAndreas Gohr use EscapeTrait; 370b3fd2d3SAndreas Gohr 380b3fd2d3SAndreas Gohr protected const ESCAPE_MAP = [ 390b3fd2d3SAndreas Gohr '\\' => '\5c', 400b3fd2d3SAndreas Gohr '*' => '\2a', 410b3fd2d3SAndreas Gohr '(' => '\28', 420b3fd2d3SAndreas Gohr ')' => '\29', 430b3fd2d3SAndreas Gohr "\x00" => '\00', 440b3fd2d3SAndreas Gohr ]; 450b3fd2d3SAndreas Gohr 460b3fd2d3SAndreas Gohr /** 470b3fd2d3SAndreas Gohr * @var string 480b3fd2d3SAndreas Gohr */ 490b3fd2d3SAndreas Gohr protected $attribute; 500b3fd2d3SAndreas Gohr 510b3fd2d3SAndreas Gohr /** 520b3fd2d3SAndreas Gohr * @var null|string 530b3fd2d3SAndreas Gohr */ 540b3fd2d3SAndreas Gohr protected $lcAttribute; 550b3fd2d3SAndreas Gohr 560b3fd2d3SAndreas Gohr /** 570b3fd2d3SAndreas Gohr * @var mixed[]|string[] 580b3fd2d3SAndreas Gohr */ 590b3fd2d3SAndreas Gohr protected $values = []; 600b3fd2d3SAndreas Gohr 610b3fd2d3SAndreas Gohr /** 620b3fd2d3SAndreas Gohr * @var null|Options 630b3fd2d3SAndreas Gohr */ 640b3fd2d3SAndreas Gohr protected $options; 650b3fd2d3SAndreas Gohr 660b3fd2d3SAndreas Gohr /** 670b3fd2d3SAndreas Gohr * @param string $attribute 68*dad993c5SAndreas Gohr * @param mixed|string ...$values 690b3fd2d3SAndreas Gohr */ 700b3fd2d3SAndreas Gohr public function __construct(string $attribute, ...$values) 710b3fd2d3SAndreas Gohr { 720b3fd2d3SAndreas Gohr $this->attribute = $attribute; 730b3fd2d3SAndreas Gohr $this->values = $values; 740b3fd2d3SAndreas Gohr } 750b3fd2d3SAndreas Gohr 760b3fd2d3SAndreas Gohr /** 770b3fd2d3SAndreas Gohr * Add a value, or values, to the attribute. 780b3fd2d3SAndreas Gohr * 79*dad993c5SAndreas Gohr * @param mixed|string ...$values 800b3fd2d3SAndreas Gohr * @return $this 810b3fd2d3SAndreas Gohr */ 820b3fd2d3SAndreas Gohr public function add(...$values): self 830b3fd2d3SAndreas Gohr { 840b3fd2d3SAndreas Gohr foreach ($values as $value) { 850b3fd2d3SAndreas Gohr $this->values[] = $value; 860b3fd2d3SAndreas Gohr } 870b3fd2d3SAndreas Gohr 880b3fd2d3SAndreas Gohr return $this; 890b3fd2d3SAndreas Gohr } 900b3fd2d3SAndreas Gohr 910b3fd2d3SAndreas Gohr /** 920b3fd2d3SAndreas Gohr * Check if the attribute has a specific value. 930b3fd2d3SAndreas Gohr * 940b3fd2d3SAndreas Gohr * @param mixed|string $value 950b3fd2d3SAndreas Gohr * @return bool 960b3fd2d3SAndreas Gohr */ 970b3fd2d3SAndreas Gohr public function has($value): bool 980b3fd2d3SAndreas Gohr { 99*dad993c5SAndreas Gohr return array_search($value, $this->values, true) !== false; 1000b3fd2d3SAndreas Gohr } 1010b3fd2d3SAndreas Gohr 1020b3fd2d3SAndreas Gohr /** 1030b3fd2d3SAndreas Gohr * Remove a specific value, or values, from an attribute. 1040b3fd2d3SAndreas Gohr * 105*dad993c5SAndreas Gohr * @param mixed|string ...$values 1060b3fd2d3SAndreas Gohr * @return $this 1070b3fd2d3SAndreas Gohr */ 1080b3fd2d3SAndreas Gohr public function remove(...$values): self 1090b3fd2d3SAndreas Gohr { 1100b3fd2d3SAndreas Gohr foreach ($values as $value) { 111*dad993c5SAndreas Gohr if (($i = array_search($value, $this->values, true)) !== false) { 1120b3fd2d3SAndreas Gohr unset($this->values[$i]); 1130b3fd2d3SAndreas Gohr } 1140b3fd2d3SAndreas Gohr } 1150b3fd2d3SAndreas Gohr 1160b3fd2d3SAndreas Gohr return $this; 1170b3fd2d3SAndreas Gohr } 1180b3fd2d3SAndreas Gohr 1190b3fd2d3SAndreas Gohr /** 1200b3fd2d3SAndreas Gohr * Resets the values to any empty array. 1210b3fd2d3SAndreas Gohr * 1220b3fd2d3SAndreas Gohr * @return $this 1230b3fd2d3SAndreas Gohr */ 1240b3fd2d3SAndreas Gohr public function reset(): self 1250b3fd2d3SAndreas Gohr { 1260b3fd2d3SAndreas Gohr $this->values = []; 1270b3fd2d3SAndreas Gohr 1280b3fd2d3SAndreas Gohr return $this; 1290b3fd2d3SAndreas Gohr } 1300b3fd2d3SAndreas Gohr 1310b3fd2d3SAndreas Gohr /** 1320b3fd2d3SAndreas Gohr * Set the values for the attribute. 1330b3fd2d3SAndreas Gohr * 134*dad993c5SAndreas Gohr * @param mixed|string ...$values 1350b3fd2d3SAndreas Gohr * @return $this 1360b3fd2d3SAndreas Gohr */ 1370b3fd2d3SAndreas Gohr public function set(...$values): self 1380b3fd2d3SAndreas Gohr { 1390b3fd2d3SAndreas Gohr $this->values = $values; 1400b3fd2d3SAndreas Gohr 1410b3fd2d3SAndreas Gohr return $this; 1420b3fd2d3SAndreas Gohr } 1430b3fd2d3SAndreas Gohr 1440b3fd2d3SAndreas Gohr /** 1450b3fd2d3SAndreas Gohr * Gets the name (AttributeType) portion of the AttributeDescription, which excludes the options. 1460b3fd2d3SAndreas Gohr * 1470b3fd2d3SAndreas Gohr * @return string 1480b3fd2d3SAndreas Gohr */ 1490b3fd2d3SAndreas Gohr public function getName(): string 1500b3fd2d3SAndreas Gohr { 1510b3fd2d3SAndreas Gohr $this->options(); 1520b3fd2d3SAndreas Gohr 1530b3fd2d3SAndreas Gohr return $this->attribute; 1540b3fd2d3SAndreas Gohr } 1550b3fd2d3SAndreas Gohr 1560b3fd2d3SAndreas Gohr /** 1570b3fd2d3SAndreas Gohr * Gets the full AttributeDescription (RFC 4512, 2.5), which contains the attribute type (name) and options. 1580b3fd2d3SAndreas Gohr * 1590b3fd2d3SAndreas Gohr * @return string 1600b3fd2d3SAndreas Gohr */ 1610b3fd2d3SAndreas Gohr public function getDescription(): string 1620b3fd2d3SAndreas Gohr { 1630b3fd2d3SAndreas Gohr return $this->getName() . ($this->options()->count() > 0 ? ';' . $this->options()->toString() : ''); 1640b3fd2d3SAndreas Gohr } 1650b3fd2d3SAndreas Gohr 1660b3fd2d3SAndreas Gohr /** 1670b3fd2d3SAndreas Gohr * Gets any values associated with the attribute. 1680b3fd2d3SAndreas Gohr * 1690b3fd2d3SAndreas Gohr * @return array 1700b3fd2d3SAndreas Gohr */ 1710b3fd2d3SAndreas Gohr public function getValues(): array 1720b3fd2d3SAndreas Gohr { 1730b3fd2d3SAndreas Gohr return $this->values; 1740b3fd2d3SAndreas Gohr } 1750b3fd2d3SAndreas Gohr 1760b3fd2d3SAndreas Gohr /** 1770b3fd2d3SAndreas Gohr * Retrieve the first value of the attribute. 1780b3fd2d3SAndreas Gohr * 1790b3fd2d3SAndreas Gohr * @return string|mixed|null 1800b3fd2d3SAndreas Gohr */ 1810b3fd2d3SAndreas Gohr public function firstValue() 1820b3fd2d3SAndreas Gohr { 1830b3fd2d3SAndreas Gohr return $this->values[0] ?? null; 1840b3fd2d3SAndreas Gohr } 1850b3fd2d3SAndreas Gohr 1860b3fd2d3SAndreas Gohr /** 1870b3fd2d3SAndreas Gohr * Retrieve the last value of the attribute. 1880b3fd2d3SAndreas Gohr * 1890b3fd2d3SAndreas Gohr * @return string|mixed|null 1900b3fd2d3SAndreas Gohr */ 1910b3fd2d3SAndreas Gohr public function lastValue() 1920b3fd2d3SAndreas Gohr { 1930b3fd2d3SAndreas Gohr $last = end($this->values); 1940b3fd2d3SAndreas Gohr reset($this->values); 1950b3fd2d3SAndreas Gohr 1960b3fd2d3SAndreas Gohr return $last === false ? null : $last; 1970b3fd2d3SAndreas Gohr } 1980b3fd2d3SAndreas Gohr 1990b3fd2d3SAndreas Gohr /** 2000b3fd2d3SAndreas Gohr * Gets the options within the AttributeDescription (semi-colon separated list of options). 2010b3fd2d3SAndreas Gohr * 2020b3fd2d3SAndreas Gohr * @return Options 2030b3fd2d3SAndreas Gohr */ 2040b3fd2d3SAndreas Gohr public function getOptions(): Options 2050b3fd2d3SAndreas Gohr { 2060b3fd2d3SAndreas Gohr return $this->options(); 2070b3fd2d3SAndreas Gohr } 2080b3fd2d3SAndreas Gohr 2090b3fd2d3SAndreas Gohr /** 2100b3fd2d3SAndreas Gohr * @return bool 2110b3fd2d3SAndreas Gohr */ 2120b3fd2d3SAndreas Gohr public function hasOptions(): bool 2130b3fd2d3SAndreas Gohr { 2140b3fd2d3SAndreas Gohr return ($this->options()->count() > 0); 2150b3fd2d3SAndreas Gohr } 2160b3fd2d3SAndreas Gohr 2170b3fd2d3SAndreas Gohr /** 2180b3fd2d3SAndreas Gohr * {@inheritDoc} 2190b3fd2d3SAndreas Gohr */ 220*dad993c5SAndreas Gohr public function getIterator(): Traversable 2210b3fd2d3SAndreas Gohr { 222*dad993c5SAndreas Gohr return new ArrayIterator($this->values); 2230b3fd2d3SAndreas Gohr } 2240b3fd2d3SAndreas Gohr 2250b3fd2d3SAndreas Gohr /** 2260b3fd2d3SAndreas Gohr * {@inheritDoc} 2270b3fd2d3SAndreas Gohr */ 2280b3fd2d3SAndreas Gohr public function count(): int 2290b3fd2d3SAndreas Gohr { 230*dad993c5SAndreas Gohr return count($this->values); 2310b3fd2d3SAndreas Gohr } 2320b3fd2d3SAndreas Gohr 2330b3fd2d3SAndreas Gohr /** 2340b3fd2d3SAndreas Gohr * @param Attribute $attribute 2350b3fd2d3SAndreas Gohr * @param bool $strict If set to true, then options must also match. 2360b3fd2d3SAndreas Gohr * @return bool 2370b3fd2d3SAndreas Gohr */ 2380b3fd2d3SAndreas Gohr public function equals(Attribute $attribute, bool $strict = false): bool 2390b3fd2d3SAndreas Gohr { 2400b3fd2d3SAndreas Gohr $this->options(); 2410b3fd2d3SAndreas Gohr $attribute->options(); 2420b3fd2d3SAndreas Gohr if ($this->lcAttribute === null) { 243*dad993c5SAndreas Gohr $this->lcAttribute = strtolower($this->attribute); 2440b3fd2d3SAndreas Gohr } 2450b3fd2d3SAndreas Gohr if ($attribute->lcAttribute === null) { 246*dad993c5SAndreas Gohr $attribute->lcAttribute = strtolower($attribute->attribute); 2470b3fd2d3SAndreas Gohr } 2480b3fd2d3SAndreas Gohr $nameMatches = ($this->lcAttribute === $attribute->lcAttribute); 2490b3fd2d3SAndreas Gohr 2500b3fd2d3SAndreas Gohr # Only the name of the attribute is checked for by default. 2510b3fd2d3SAndreas Gohr # If strict is selected, or the attribute to be checked has explicit options, then the opposing attribute must too 2520b3fd2d3SAndreas Gohr if ($strict || $attribute->hasOptions()) { 2530b3fd2d3SAndreas Gohr return $nameMatches && ($this->getOptions()->toString(true) === $attribute->getOptions()->toString(true)); 2540b3fd2d3SAndreas Gohr } 2550b3fd2d3SAndreas Gohr 2560b3fd2d3SAndreas Gohr return $nameMatches; 2570b3fd2d3SAndreas Gohr } 2580b3fd2d3SAndreas Gohr 2590b3fd2d3SAndreas Gohr /** 2600b3fd2d3SAndreas Gohr * @return string 2610b3fd2d3SAndreas Gohr */ 2620b3fd2d3SAndreas Gohr public function __toString(): string 2630b3fd2d3SAndreas Gohr { 264*dad993c5SAndreas Gohr return implode(', ', $this->values); 2650b3fd2d3SAndreas Gohr } 2660b3fd2d3SAndreas Gohr 2670b3fd2d3SAndreas Gohr /** 2680b3fd2d3SAndreas Gohr * Escape an attribute value for a filter. 2690b3fd2d3SAndreas Gohr * 2700b3fd2d3SAndreas Gohr * @param string $value 2710b3fd2d3SAndreas Gohr * @return string 2720b3fd2d3SAndreas Gohr */ 2730b3fd2d3SAndreas Gohr public static function escape(string $value): string 2740b3fd2d3SAndreas Gohr { 2750b3fd2d3SAndreas Gohr if (self::shouldNotEscape($value)) { 2760b3fd2d3SAndreas Gohr return $value; 2770b3fd2d3SAndreas Gohr } 278*dad993c5SAndreas Gohr $value = str_replace(array_keys(self::ESCAPE_MAP), array_values(self::ESCAPE_MAP), $value); 2790b3fd2d3SAndreas Gohr 2800b3fd2d3SAndreas Gohr return self::escapeNonPrintable($value); 2810b3fd2d3SAndreas Gohr } 2820b3fd2d3SAndreas Gohr 2830b3fd2d3SAndreas Gohr /** 2840b3fd2d3SAndreas Gohr * A one time check and load of any attribute options. 2850b3fd2d3SAndreas Gohr */ 2860b3fd2d3SAndreas Gohr protected function options(): Options 2870b3fd2d3SAndreas Gohr { 2880b3fd2d3SAndreas Gohr if ($this->options !== null) { 2890b3fd2d3SAndreas Gohr return $this->options; 2900b3fd2d3SAndreas Gohr } 291*dad993c5SAndreas Gohr if (strpos($this->attribute, ';') === false) { 2920b3fd2d3SAndreas Gohr $this->options = new Options(); 2930b3fd2d3SAndreas Gohr 2940b3fd2d3SAndreas Gohr return $this->options; 2950b3fd2d3SAndreas Gohr } 296*dad993c5SAndreas Gohr $options = explode(';', $this->attribute); 297*dad993c5SAndreas Gohr $this->attribute = (string) array_shift($options); 2980b3fd2d3SAndreas Gohr $this->options = new Options(...$options); 2990b3fd2d3SAndreas Gohr 3000b3fd2d3SAndreas Gohr return $this->options; 3010b3fd2d3SAndreas Gohr } 3020b3fd2d3SAndreas Gohr} 303