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\Entry;
12
13/**
14 * Represents an Entry in LDAP.
15 *
16 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
17 */
18class Entry implements \IteratorAggregate, \Countable
19{
20    /**
21     * @var Attribute[]
22     */
23    protected $attributes;
24
25    /**
26     * @var Dn
27     */
28    protected $dn;
29
30    /**
31     * @var Changes
32     */
33    protected $changes;
34
35    /**
36     * @param string|Dn $dn
37     * @param Attribute ...$attributes
38     */
39    public function __construct($dn, Attribute ...$attributes)
40    {
41        $this->dn = $dn instanceof Dn ? $dn : new Dn($dn);
42        $this->attributes = $attributes;
43        $this->changes = new Changes();
44    }
45
46    /**
47     * Add an attribute and its values.
48     *
49     * @param string|Attribute $attribute
50     * @param string[] ...$values
51     * @return $this
52     */
53    public function add($attribute, ...$values)
54    {
55        $attribute = $attribute instanceof Attribute ? $attribute : new Attribute($attribute, ...$values);
56
57        if (($exists = $this->get($attribute, true)) !== null) {
58            $exists->add(...$attribute->getValues());
59        } else {
60            $this->attributes[] = $attribute;
61        }
62        $this->changes->add(Change::add(clone $attribute));
63
64        return $this;
65    }
66
67    /**
68     * Remove an attribute's value(s).
69     *
70     * @param string|Attribute $attribute
71     * @param array ...$values
72     * @return $this
73     */
74    public function remove($attribute, ...$values)
75    {
76        $attribute = $attribute instanceof Attribute ? $attribute : new Attribute($attribute, ...$values);
77
78        if (\count($attribute->getValues()) !== 0) {
79            if (($exists = $this->get($attribute, true)) !== null) {
80                $exists->remove(...$attribute->getValues());
81            }
82            $this->changes->add(Change::delete(clone $attribute));
83        }
84
85        return $this;
86    }
87
88    /**
89     * Reset an attribute, which removes any values it may have.
90     *
91     * @param string[]|Attribute[] ...$attributes
92     * @return $this
93     */
94    public function reset(...$attributes)
95    {
96        foreach ($attributes as $attribute) {
97            $attribute = $attribute instanceof Attribute ? $attribute : new Attribute($attribute);
98            foreach ($this->attributes as $i => $attr) {
99                if ($attr->equals($attribute, true)) {
100                    unset($this->attributes[$i]);
101                    break;
102                }
103            }
104            $this->changes()->add(Change::reset(clone $attribute));
105        }
106
107        return $this;
108    }
109
110    /**
111     * Set an attribute on the entry, replacing any value(s) that may exist on it.
112     *
113     * @param string|Attribute $attribute
114     * @param array ...$values
115     * @return $this
116     */
117    public function set($attribute, ...$values)
118    {
119        $attribute = $attribute instanceof Attribute ? $attribute : new Attribute($attribute, ...$values);
120
121        $exists = false;
122        foreach ($this->attributes as $i => $attr) {
123            if ($attr->equals($attribute, true)) {
124                $exists = true;
125                $this->attributes[$i] = $attribute;
126                break;
127            }
128        }
129        if (!$exists) {
130            $this->attributes[] = $attribute;
131        }
132        $this->changes->add(Change::replace(clone $attribute));
133
134        return $this;
135    }
136
137    /**
138     * Get a specific attribute by name (or Attribute object).
139     *
140     * @param string|Attribute $attribute
141     * @param bool $strict If set to true, then options on the attribute must also match.
142     * @return null|Attribute
143     */
144    public function get($attribute, bool $strict = false): ?Attribute
145    {
146        $attribute = $attribute instanceof Attribute ? $attribute : new Attribute($attribute);
147
148        foreach ($this->attributes as $attr) {
149            if ($attr->equals($attribute, $strict)) {
150                return $attr;
151            }
152        }
153
154        return null;
155    }
156
157    /**
158     * Check if a specific attribute exists on the entry.
159     *
160     * @param string|Attribute $attribute
161     * @param bool $strict
162     * @return bool
163     */
164    public function has($attribute, bool $strict = false): bool
165    {
166        $attribute = $attribute instanceof Attribute ? $attribute : new Attribute($attribute);
167
168        return (bool) $this->get($attribute, $strict);
169    }
170
171    /**
172     * @return Attribute[]
173     */
174    public function getAttributes(): array
175    {
176        return $this->attributes;
177    }
178
179    /**
180     * @return Dn
181     */
182    public function getDn(): Dn
183    {
184        return $this->dn;
185    }
186
187    /**
188     * Get the changes accumulated for this entry.
189     *
190     * @return Changes
191     */
192    public function changes(): Changes
193    {
194        return $this->changes;
195    }
196
197    /**
198     * Get the entry representation as an associative array.
199     *
200     * @return array
201     */
202    public function toArray(): array
203    {
204        $attributes = [];
205
206        foreach ($this->attributes as $attribute) {
207            $attributes[$attribute->getDescription()] = $attribute->getValues();
208        }
209
210        return $attributes;
211    }
212
213    /**
214     * @return \ArrayIterator
215     */
216    public function getIterator(): \ArrayIterator
217    {
218        return new \ArrayIterator($this->attributes);
219    }
220
221    /**
222     * @return int
223     */
224    public function count(): int
225    {
226        return \count($this->attributes);
227    }
228
229    public function __toString(): string
230    {
231        return $this->dn->toString();
232    }
233
234    public function __get(string $name): ?Attribute
235    {
236        return $this->get($name);
237    }
238
239    /**
240     * @param string[]|string $value
241     */
242    public function __set(string $name, $value): void
243    {
244        $this->set($name, ...(\is_array($value) ? $value : [$value]));
245    }
246
247    public function __isset(string $name): bool
248    {
249        return $this->has($name);
250    }
251
252    public function __unset(string $name): void
253    {
254        $this->reset($name);
255    }
256
257    /**
258     * An alias of fromArray().
259     *
260     * @param string $dn
261     * @param array $attributes
262     * @return Entry
263     */
264    public static function create(string $dn, array $attributes = []): Entry
265    {
266        return self::fromArray($dn, $attributes);
267    }
268
269    /**
270     * Construct an entry from an associative array.
271     *
272     * @param string $dn
273     * @param array $attributes
274     * @return Entry
275     */
276    public static function fromArray(string $dn, array $attributes = []): Entry
277    {
278        /** @var Attribute[] $entryAttr */
279        $entryAttr = [];
280
281        foreach ($attributes as $attribute => $value) {
282            $entryAttr[] = new Attribute($attribute, ...(\is_array($value) ? $value : [$value]));
283        }
284
285        return new self($dn, ...$entryAttr);
286    }
287}
288