xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/Entry/Attribute.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
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