blob: 2235bbe2dc7d7a761377e8deb1334464ad687841 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace Adldap\Models\Concerns;
4
5use Illuminate\Support\Arr;
6
7trait HasAttributes
8{
9 /**
10 * The default output date format for all time related methods.
11 *
12 * Default format is suited for MySQL timestamps.
13 *
14 * @var string
15 */
16 public $dateFormat = 'Y-m-d H:i:s';
17
18 /**
19 * The format that is used to convert timestamps to unix timestamps.
20 *
21 * Currently set for compatibility with Active Directory.
22 *
23 * @var string
24 */
25 protected $timestampFormat = 'YmdHis.0Z';
26
27 /**
28 * The models attributes.
29 *
30 * @var array
31 */
32 protected $attributes = [];
33
34 /**
35 * The models original attributes.
36 *
37 * @var array
38 */
39 protected $original = [];
40
41 /**
42 * Dynamically retrieve attributes on the object.
43 *
44 * @param mixed $key
45 *
46 * @return bool
47 */
48 public function __get($key)
49 {
50 return $this->getAttribute($key);
51 }
52
53 /**
54 * Dynamically set attributes on the object.
55 *
56 * @param mixed $key
57 * @param mixed $value
58 *
59 * @return $this
60 */
61 public function __set($key, $value)
62 {
63 return $this->setAttribute($key, $value);
64 }
65
66 /**
67 * Synchronizes the models original attributes
68 * with the model's current attributes.
69 *
70 * @return $this
71 */
72 public function syncOriginal()
73 {
74 $this->original = $this->attributes;
75
76 return $this;
77 }
78
79 /**
80 * Returns the models attribute with the specified key.
81 *
82 * If a sub-key is specified, it will try and
83 * retrieve it from the parent keys array.
84 *
85 * @param int|string $key
86 * @param int|string $subKey
87 *
88 * @return mixed
89 */
90 public function getAttribute($key, $subKey = null)
91 {
92 if (!$key) {
93 return;
94 }
95
96 // We'll normalize the given key to prevent case sensitivity issues.
97 $key = $this->normalizeAttributeKey($key);
98
99 if (is_null($subKey) && $this->hasAttribute($key)) {
100 return $this->attributes[$key];
101 } elseif ($this->hasAttribute($key, $subKey)) {
102 return $this->attributes[$key][$subKey];
103 }
104 }
105
106 /**
107 * Returns the first attribute by the specified key.
108 *
109 * @param string $key
110 *
111 * @return mixed
112 */
113 public function getFirstAttribute($key)
114 {
115 return $this->getAttribute($key, 0);
116 }
117
118 /**
119 * Returns all of the models attributes.
120 *
121 * @return array
122 */
123 public function getAttributes()
124 {
125 return $this->attributes;
126 }
127
128 /**
129 * Fills the entry with the supplied attributes.
130 *
131 * @param array $attributes
132 *
133 * @return $this
134 */
135 public function fill(array $attributes = [])
136 {
137 foreach ($attributes as $key => $value) {
138 $this->setAttribute($key, $value);
139 }
140
141 return $this;
142 }
143
144 /**
145 * Sets an attributes value by the specified key and sub-key.
146 *
147 * @param int|string $key
148 * @param mixed $value
149 * @param int|string $subKey
150 *
151 * @return $this
152 */
153 public function setAttribute($key, $value, $subKey = null)
154 {
155 // Normalize key.
156 $key = $this->normalizeAttributeKey($key);
157
158 // If the key is equal to 'dn', we'll automatically
159 // change it to the full attribute name.
160 $key = ($key == 'dn' ? $this->schema->distinguishedName() : $key);
161
162 if (is_null($subKey)) {
163 // We need to ensure all attributes are set as arrays so all
164 // of our model methods retrieve attributes correctly.
165 $this->attributes[$key] = is_array($value) ? $value : [$value];
166 } else {
167 $this->attributes[$key][$subKey] = $value;
168 }
169
170 return $this;
171 }
172
173 /**
174 * Sets the first attributes value by the specified key.
175 *
176 * @param int|string $key
177 * @param mixed $value
178 *
179 * @return $this
180 */
181 public function setFirstAttribute($key, $value)
182 {
183 return $this->setAttribute($key, $value, 0);
184 }
185
186 /**
187 * Sets the attributes property.
188 *
189 * Used when constructing an existing LDAP record.
190 *
191 * @param array $attributes
192 *
193 * @return $this
194 */
195 public function setRawAttributes(array $attributes = [])
196 {
197 // We'll filter out those annoying 'count' keys returned with LDAP results,
198 // and lowercase all root array keys to prevent any casing issues.
199 $this->attributes = array_change_key_case($this->filterRawAttributes($attributes), CASE_LOWER);
200
201 // We'll pull out the distinguished name from our raw attributes
202 // and set it into our attributes array with the full attribute
203 // definition. This allows us to normalize distinguished
204 // names across different LDAP variants.
205 if ($dn = Arr::get($attributes, 'dn')) {
206 // In some LDAP variants, the distinguished
207 // name is returned as an array.
208 if (is_array($dn)) {
209 $dn = Arr::first($dn);
210 }
211
212 $this->setDistinguishedName($dn);
213 }
214
215 $this->syncOriginal();
216
217 // Set exists to true since raw attributes are only
218 // set in the case of attributes being loaded by
219 // query results.
220 $this->exists = true;
221
222 return $this;
223 }
224
225 /**
226 * Filters the count key recursively from raw LDAP attributes.
227 *
228 * @param array $attributes
229 * @param array|string $keys
230 *
231 * @return array
232 */
233 public function filterRawAttributes(array $attributes = [], $keys = ['count', 'dn'])
234 {
235 $attributes = Arr::except($attributes, $keys);
236
237 array_walk($attributes, function (&$value) use ($keys) {
238 $value = is_array($value) ?
239 $this->filterRawAttributes($value, $keys) :
240 $value;
241 });
242
243 return $attributes;
244 }
245
246 /**
247 * Returns true / false if the specified attribute
248 * exists in the attributes array.
249 *
250 * @param int|string $key
251 * @param int|string $subKey
252 *
253 * @return bool
254 */
255 public function hasAttribute($key, $subKey = null)
256 {
257 // Normalize key.
258 $key = $this->normalizeAttributeKey($key);
259
260 if (is_null($subKey)) {
261 return Arr::has($this->attributes, $key);
262 }
263
264 return Arr::has($this->attributes, "$key.$subKey");
265 }
266
267 /**
268 * Returns the number of attributes inside
269 * the attributes property.
270 *
271 * @return int
272 */
273 public function countAttributes()
274 {
275 return count($this->getAttributes());
276 }
277
278 /**
279 * Returns the models original attributes.
280 *
281 * @return array
282 */
283 public function getOriginal()
284 {
285 return $this->original;
286 }
287
288 /**
289 * Get the attributes that have been changed since last sync.
290 *
291 * @return array
292 */
293 public function getDirty()
294 {
295 $dirty = [];
296
297 foreach ($this->attributes as $key => $value) {
298 if (!$this->originalIsEquivalent($key)) {
299 // We need to reset the array's indices using array_values due to
300 // LDAP requiring consecutive indices (0, 1, 2 etc.)
301 $dirty[$key] = array_values($value);
302 }
303 }
304
305 return $dirty;
306 }
307
308 /**
309 * Returns a normalized attribute key.
310 *
311 * @param string $key
312 *
313 * @return string
314 */
315 protected function normalizeAttributeKey($key)
316 {
317 return strtolower($key);
318 }
319
320 /**
321 * Determine if the new and old values for a given key are equivalent.
322 *
323 * @param string $key
324 *
325 * @return bool
326 */
327 protected function originalIsEquivalent($key)
328 {
329 if (!array_key_exists($key, $this->original)) {
330 return false;
331 }
332
333 $current = $this->attributes[$key];
334
335 $original = $this->original[$key];
336
337 if ($current === $original) {
338 return true;
339 }
340
341 return is_numeric($current) &&
342 is_numeric($original) &&
343 strcmp((string) $current, (string) $original) === 0;
344 }
345}