1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Neta\Shopware\SDK\Util; |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of the TYPO3 CMS project. |
7
|
|
|
* |
8
|
|
|
* It is free software; you can redistribute it and/or modify it under |
9
|
|
|
* the terms of the GNU General Public License, either version 2 |
10
|
|
|
* of the License, or any later version. |
11
|
|
|
* |
12
|
|
|
* For the full copyright and license information, please read the |
13
|
|
|
* LICENSE.txt file that was distributed with this source code. |
14
|
|
|
* |
15
|
|
|
* The TYPO3 project - inspiring people to share! |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class with helper functions for array handling. |
20
|
|
|
*/ |
21
|
|
|
class ArrayUtil |
22
|
|
|
{ |
23
|
|
|
/** |
24
|
|
|
* Reduce an array by a search value and keep the array structure. |
25
|
|
|
* |
26
|
|
|
* Comparison is type strict: |
27
|
|
|
* - For a given needle of type string, integer, array or boolean, |
28
|
|
|
* value and value type must match to occur in result array |
29
|
|
|
* - For a given object, an object within the array must be a reference to |
30
|
|
|
* the same object to match (not just different instance of same class) |
31
|
|
|
* |
32
|
|
|
* Example: |
33
|
|
|
* - Needle: 'findMe' |
34
|
|
|
* - Given array: |
35
|
|
|
* array( |
36
|
|
|
* 'foo' => 'noMatch', |
37
|
|
|
* 'bar' => 'findMe', |
38
|
|
|
* 'foobar => array( |
39
|
|
|
* 'foo' => 'findMe', |
40
|
|
|
* ), |
41
|
|
|
* ); |
42
|
|
|
* - Result: |
43
|
|
|
* array( |
44
|
|
|
* 'bar' => 'findMe', |
45
|
|
|
* 'foobar' => array( |
46
|
|
|
* 'foo' => findMe', |
47
|
|
|
* ), |
48
|
|
|
* ); |
49
|
|
|
* |
50
|
|
|
* See the unit tests for more examples and expected behaviour |
51
|
|
|
* |
52
|
|
|
* @param mixed $needle The value to search for |
53
|
|
|
* @param array $haystack The array in which to search |
54
|
|
|
* @return array $haystack array reduced matching $needle values |
55
|
|
|
*/ |
56
|
|
|
public static function filterByValueRecursive($needle = '', array $haystack = []) |
57
|
|
|
{ |
58
|
|
|
$resultArray = []; |
59
|
|
|
// Define a lambda function to be applied to all members of this array dimension |
60
|
|
|
// Call recursive if current value is of type array |
61
|
|
|
// Write to $resultArray (by reference!) if types and value match |
62
|
|
|
$callback = function (&$value, $key) use ($needle, &$resultArray) { |
63
|
|
|
if ($value === $needle) { |
64
|
|
|
($resultArray[$key] = $value); |
65
|
|
|
} elseif (is_array($value)) { |
66
|
|
|
($subArrayMatches = static::filterByValueRecursive($needle, $value)); |
67
|
|
|
if (! empty($subArrayMatches)) { |
68
|
|
|
($resultArray[$key] = $subArrayMatches); |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
}; |
72
|
|
|
// array_walk() is not affected by the internal pointers, no need to reset |
73
|
|
|
array_walk($haystack, $callback); |
74
|
|
|
|
75
|
|
|
// Pointers to result array are reset internally |
76
|
|
|
return $resultArray; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Checks if a given path exists in array. |
81
|
|
|
* |
82
|
|
|
* Example: |
83
|
|
|
* - array: |
84
|
|
|
* array( |
85
|
|
|
* 'foo' => array( |
86
|
|
|
* 'bar' = 'test', |
87
|
|
|
* ) |
88
|
|
|
* ); |
89
|
|
|
* - path: 'foo/bar' |
90
|
|
|
* - return: TRUE |
91
|
|
|
* |
92
|
|
|
* @param array $array Given array |
93
|
|
|
* @param string $path Path to test, 'foo/bar/foobar' |
94
|
|
|
* @param string $delimiter Delimiter for path, default / |
95
|
|
|
* @return bool TRUE if path exists in array |
96
|
|
|
*/ |
97
|
|
|
public static function isValidPath(array $array, $path, $delimiter = '/') |
98
|
|
|
{ |
99
|
|
|
$isValid = true; |
100
|
|
|
try { |
101
|
|
|
// Use late static binding to enable mocking of this call in unit tests |
102
|
|
|
static::getValueByPath($array, $path, $delimiter); |
103
|
|
|
} catch (\RuntimeException $e) { |
104
|
|
|
$isValid = false; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
return $isValid; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Returns a value by given path. |
112
|
|
|
* |
113
|
|
|
* Example |
114
|
|
|
* - array: |
115
|
|
|
* array( |
116
|
|
|
* 'foo' => array( |
117
|
|
|
* 'bar' => array( |
118
|
|
|
* 'baz' => 42 |
119
|
|
|
* ) |
120
|
|
|
* ) |
121
|
|
|
* ); |
122
|
|
|
* - path: foo/bar/baz |
123
|
|
|
* - return: 42 |
124
|
|
|
* |
125
|
|
|
* If a path segments contains a delimiter character, the path segment |
126
|
|
|
* must be enclosed by " (double quote), see unit tests for details |
127
|
|
|
* |
128
|
|
|
* @param array $array Input array |
129
|
|
|
* @param string $path Path within the array |
130
|
|
|
* @param string $delimiter Defined path delimiter, default / |
131
|
|
|
* @return mixed |
132
|
|
|
* @throws \RuntimeException |
133
|
|
|
*/ |
134
|
|
|
public static function getValueByPath(array $array, $path, $delimiter = '/') |
135
|
|
|
{ |
136
|
|
|
if (! is_string($path)) { |
137
|
|
|
throw new \RuntimeException('Path must be a string', 1477699595); |
138
|
|
|
} |
139
|
|
|
if ($path === '') { |
140
|
|
|
throw new \RuntimeException('Path must not be empty', 1341397767); |
141
|
|
|
} |
142
|
|
|
// Extract parts of the path |
143
|
|
|
$path = str_getcsv($path, $delimiter); |
144
|
|
|
// Loop through each part and extract its value |
145
|
|
|
$value = $array; |
146
|
|
|
foreach ($path as $segment) { |
147
|
|
|
if (array_key_exists($segment, $value)) { |
148
|
|
|
// Replace current value with child |
149
|
|
|
$value = $value[$segment]; |
150
|
|
|
} else { |
151
|
|
|
// Fail if key does not exist |
152
|
|
|
throw new \RuntimeException('Path does not exist in array', 1341397869); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
return $value; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Modifies or sets a new value in an array by given path. |
161
|
|
|
* |
162
|
|
|
* Example: |
163
|
|
|
* - array: |
164
|
|
|
* array( |
165
|
|
|
* 'foo' => array( |
166
|
|
|
* 'bar' => 42, |
167
|
|
|
* ), |
168
|
|
|
* ); |
169
|
|
|
* - path: foo/bar |
170
|
|
|
* - value: 23 |
171
|
|
|
* - return: |
172
|
|
|
* array( |
173
|
|
|
* 'foo' => array( |
174
|
|
|
* 'bar' => 23, |
175
|
|
|
* ), |
176
|
|
|
* ); |
177
|
|
|
* |
178
|
|
|
* @param array $array Input array to manipulate |
179
|
|
|
* @param string $path Path in array to search for |
180
|
|
|
* @param mixed $value Value to set at path location in array |
181
|
|
|
* @param string $delimiter Path delimiter |
182
|
|
|
* @return array Modified array |
183
|
|
|
* @throws \RuntimeException |
184
|
|
|
*/ |
185
|
|
|
public static function setValueByPath(array $array, $path, $value, $delimiter = '/') |
186
|
|
|
{ |
187
|
|
|
if (! is_string($path)) { |
188
|
|
|
throw new \RuntimeException('Path must be a string', 1341406402); |
189
|
|
|
} |
190
|
|
|
if ($path === '') { |
191
|
|
|
throw new \RuntimeException('Path must not be empty', 1341406194); |
192
|
|
|
} |
193
|
|
|
// Extract parts of the path |
194
|
|
|
$path = str_getcsv($path, $delimiter); |
195
|
|
|
// Point to the root of the array |
196
|
|
|
$pointer = &$array; |
197
|
|
|
// Find path in given array |
198
|
|
|
foreach ($path as $segment) { |
199
|
|
|
// Fail if the part is empty |
200
|
|
|
if ($segment === '') { |
201
|
|
|
throw new \RuntimeException('Invalid path segment specified', 1341406846); |
202
|
|
|
} |
203
|
|
|
// Create cell if it doesn't exist |
204
|
|
|
if (! array_key_exists($segment, $pointer)) { |
205
|
|
|
$pointer[$segment] = []; |
206
|
|
|
} |
207
|
|
|
// Set pointer to new cell |
208
|
|
|
$pointer = &$pointer[$segment]; |
209
|
|
|
} |
210
|
|
|
// Set value of target cell |
211
|
|
|
$pointer = $value; |
212
|
|
|
|
213
|
|
|
return $array; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Remove a sub part from an array specified by path. |
218
|
|
|
* |
219
|
|
|
* @param array $array Input array to manipulate |
220
|
|
|
* @param string $path Path to remove from array |
221
|
|
|
* @param string $delimiter Path delimiter |
222
|
|
|
* @return array Modified array |
223
|
|
|
* @throws \RuntimeException |
224
|
|
|
*/ |
225
|
|
|
public static function removeByPath(array $array, $path, $delimiter = '/') |
226
|
|
|
{ |
227
|
|
|
if (! is_string($path)) { |
228
|
|
|
throw new \RuntimeException('Path must be a string', 1371757719); |
229
|
|
|
} |
230
|
|
|
if ($path === '') { |
231
|
|
|
throw new \RuntimeException('Path must not be empty', 1371757718); |
232
|
|
|
} |
233
|
|
|
// Extract parts of the path |
234
|
|
|
$path = str_getcsv($path, $delimiter); |
235
|
|
|
$pathDepth = count($path); |
236
|
|
|
$currentDepth = 0; |
237
|
|
|
$pointer = &$array; |
238
|
|
|
// Find path in given array |
239
|
|
|
foreach ($path as $segment) { |
240
|
|
|
$currentDepth++; |
241
|
|
|
// Fail if the part is empty |
242
|
|
|
if ($segment === '') { |
243
|
|
|
throw new \RuntimeException('Invalid path segment specified', 1371757720); |
244
|
|
|
} |
245
|
|
|
if (! array_key_exists($segment, $pointer)) { |
246
|
|
|
throw new \RuntimeException('Path segment ' . $segment . ' does not exist in array', 1371758436); |
247
|
|
|
} |
248
|
|
|
if ($currentDepth === $pathDepth) { |
249
|
|
|
unset($pointer[$segment]); |
250
|
|
|
} else { |
251
|
|
|
$pointer = &$pointer[$segment]; |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
return $array; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Sorts an array recursively by key. |
260
|
|
|
* |
261
|
|
|
* @param array $array Array to sort recursively by key |
262
|
|
|
* @return array Sorted array |
263
|
|
|
*/ |
264
|
|
|
public static function sortByKeyRecursive(array $array) |
265
|
|
|
{ |
266
|
|
|
ksort($array); |
267
|
|
|
foreach ($array as $key => $value) { |
268
|
|
|
if (is_array($value) && ! empty($value)) { |
269
|
|
|
$array[$key] = self::sortByKeyRecursive($value); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
return $array; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Sort an array of arrays by a given key using uasort. |
278
|
|
|
* |
279
|
|
|
* @param array $arrays Array of arrays to sort |
280
|
|
|
* @param string $key Key to sort after |
281
|
|
|
* @param bool $ascending Set to TRUE for ascending order, FALSE for descending order |
282
|
|
|
* @return array Array of sorted arrays |
283
|
|
|
* @throws \RuntimeException |
284
|
|
|
*/ |
285
|
|
|
public static function sortArraysByKey(array $arrays, $key, $ascending = true) |
286
|
|
|
{ |
287
|
|
|
if (empty($arrays)) { |
288
|
|
|
return $arrays; |
289
|
|
|
} |
290
|
|
|
$sortResult = uasort($arrays, function (array $a, array $b) use ($key, $ascending) { |
291
|
|
|
if (! isset($a[$key]) || ! isset($b[$key])) { |
292
|
|
|
throw new \RuntimeException('The specified sorting key "' . $key . '" is not available in the given array.', 1373727309); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
return ($ascending) ? strcasecmp($a[$key], $b[$key]) : strcasecmp($b[$key], $a[$key]); |
296
|
|
|
}); |
297
|
|
|
if (! $sortResult) { |
298
|
|
|
throw new \RuntimeException('The function uasort() failed for unknown reasons.', 1373727329); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
return $arrays; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Exports an array as string. |
306
|
|
|
* Similar to var_export(), but representation follows the PSR-2 and TYPO3 core CGL. |
307
|
|
|
* |
308
|
|
|
* See unit tests for detailed examples |
309
|
|
|
* |
310
|
|
|
* @param array $array Array to export |
311
|
|
|
* @param int $level Internal level used for recursion, do *not* set from outside! |
312
|
|
|
* @return string String representation of array |
313
|
|
|
* @throws \RuntimeException |
314
|
|
|
*/ |
315
|
|
|
public static function arrayExport(array $array = [], $level = 0) |
316
|
|
|
{ |
317
|
|
|
$lines = '[' . LF; |
318
|
|
|
$level++; |
319
|
|
|
$writeKeyIndex = false; |
320
|
|
|
$expectedKeyIndex = 0; |
321
|
|
|
foreach ($array as $key => $value) { |
322
|
|
|
if ($key === $expectedKeyIndex) { |
323
|
|
|
$expectedKeyIndex++; |
324
|
|
|
} else { |
325
|
|
|
// Found a non integer or non consecutive key, so we can break here |
326
|
|
|
$writeKeyIndex = true; |
327
|
|
|
break; |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
foreach ($array as $key => $value) { |
331
|
|
|
// Indention |
332
|
|
|
$lines .= str_repeat(' ', $level); |
333
|
|
|
if ($writeKeyIndex) { |
334
|
|
|
// Numeric / string keys |
335
|
|
|
$lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => '; |
336
|
|
|
} |
337
|
|
|
if (is_array($value)) { |
338
|
|
|
if (! empty($value)) { |
339
|
|
|
$lines .= self::arrayExport($value, $level); |
340
|
|
|
} else { |
341
|
|
|
$lines .= '[],' . LF; |
342
|
|
|
} |
343
|
|
|
} elseif (is_int($value) || is_float($value)) { |
344
|
|
|
$lines .= $value . ',' . LF; |
345
|
|
|
} elseif (is_null($value)) { |
346
|
|
|
$lines .= 'null' . ',' . LF; |
347
|
|
|
} elseif (is_bool($value)) { |
348
|
|
|
$lines .= $value ? 'true' : 'false'; |
349
|
|
|
$lines .= ',' . LF; |
350
|
|
|
} elseif (is_string($value)) { |
351
|
|
|
// Quote \ to \\ |
352
|
|
|
$stringContent = str_replace('\\', '\\\\', $value); |
353
|
|
|
// Quote ' to \' |
354
|
|
|
$stringContent = str_replace('\'', '\\\'', $stringContent); |
355
|
|
|
$lines .= '\'' . $stringContent . '\'' . ',' . LF; |
356
|
|
|
} else { |
357
|
|
|
throw new \RuntimeException('Objects are not supported', 1342294987); |
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
$lines .= str_repeat(' ', ($level - 1)) . ']' . ($level - 1 == 0 ? '' : ',' . LF); |
361
|
|
|
|
362
|
|
|
return $lines; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Converts a multidimensional array to a flat representation. |
367
|
|
|
* |
368
|
|
|
* See unit tests for more details |
369
|
|
|
* |
370
|
|
|
* Example: |
371
|
|
|
* - array: |
372
|
|
|
* array( |
373
|
|
|
* 'first.' => array( |
374
|
|
|
* 'second' => 1 |
375
|
|
|
* ) |
376
|
|
|
* ) |
377
|
|
|
* - result: |
378
|
|
|
* array( |
379
|
|
|
* 'first.second' => 1 |
380
|
|
|
* ) |
381
|
|
|
* |
382
|
|
|
* Example: |
383
|
|
|
* - array: |
384
|
|
|
* array( |
385
|
|
|
* 'first' => array( |
386
|
|
|
* 'second' => 1 |
387
|
|
|
* ) |
388
|
|
|
* ) |
389
|
|
|
* - result: |
390
|
|
|
* array( |
391
|
|
|
* 'first.second' => 1 |
392
|
|
|
* ) |
393
|
|
|
* |
394
|
|
|
* @param array $array The (relative) array to be converted |
395
|
|
|
* @param string $prefix The (relative) prefix to be used (e.g. 'section.') |
396
|
|
|
* @return array |
397
|
|
|
*/ |
398
|
|
|
public static function flatten(array $array, $prefix = '') |
399
|
|
|
{ |
400
|
|
|
$flatArray = []; |
401
|
|
|
foreach ($array as $key => $value) { |
402
|
|
|
// Ensure there is no trailling dot: |
403
|
|
|
$key = rtrim($key, '.'); |
404
|
|
|
if (! is_array($value)) { |
405
|
|
|
$flatArray[$prefix . $key] = $value; |
406
|
|
|
} else { |
407
|
|
|
$flatArray = array_merge($flatArray, self::flatten($value, $prefix . $key . '.')); |
408
|
|
|
} |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
return $flatArray; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Determine the intersections between two arrays, recursively comparing keys |
416
|
|
|
* A complete sub array of $source will be preserved, if the key exists in $mask. |
417
|
|
|
* |
418
|
|
|
* See unit tests for more examples and edge cases. |
419
|
|
|
* |
420
|
|
|
* Example: |
421
|
|
|
* - source: |
422
|
|
|
* array( |
423
|
|
|
* 'key1' => 'bar', |
424
|
|
|
* 'key2' => array( |
425
|
|
|
* 'subkey1' => 'sub1', |
426
|
|
|
* 'subkey2' => 'sub2', |
427
|
|
|
* ), |
428
|
|
|
* 'key3' => 'baz', |
429
|
|
|
* ) |
430
|
|
|
* - mask: |
431
|
|
|
* array( |
432
|
|
|
* 'key1' => NULL, |
433
|
|
|
* 'key2' => array( |
434
|
|
|
* 'subkey1' => exists', |
435
|
|
|
* ), |
436
|
|
|
* ) |
437
|
|
|
* - return: |
438
|
|
|
* array( |
439
|
|
|
* 'key1' => 'bar', |
440
|
|
|
* 'key2' => array( |
441
|
|
|
* 'subkey1' => 'sub1', |
442
|
|
|
* ), |
443
|
|
|
* ) |
444
|
|
|
* |
445
|
|
|
* @param array $source Source array |
446
|
|
|
* @param array $mask Array that has the keys which should be kept in the source array |
447
|
|
|
* @return array Keys which are present in both arrays with values of the source array |
448
|
|
|
*/ |
449
|
|
|
public static function intersectRecursive(array $source, array $mask = []) |
450
|
|
|
{ |
451
|
|
|
$intersection = []; |
452
|
|
|
foreach ($source as $key => $_) { |
453
|
|
|
if (! array_key_exists($key, $mask)) { |
454
|
|
|
continue; |
455
|
|
|
} |
456
|
|
|
if (is_array($source[$key]) && is_array($mask[$key])) { |
457
|
|
|
$value = self::intersectRecursive($source[$key], $mask[$key]); |
458
|
|
|
if (! empty($value)) { |
459
|
|
|
$intersection[$key] = $value; |
460
|
|
|
} |
461
|
|
|
} else { |
462
|
|
|
$intersection[$key] = $source[$key]; |
463
|
|
|
} |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
return $intersection; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* Renumber the keys of an array to avoid leaps if keys are all numeric. |
471
|
|
|
* |
472
|
|
|
* Is called recursively for nested arrays. |
473
|
|
|
* |
474
|
|
|
* Example: |
475
|
|
|
* |
476
|
|
|
* Given |
477
|
|
|
* array(0 => 'Zero' 1 => 'One', 2 => 'Two', 4 => 'Three') |
478
|
|
|
* as input, it will return |
479
|
|
|
* array(0 => 'Zero' 1 => 'One', 2 => 'Two', 3 => 'Three') |
480
|
|
|
* |
481
|
|
|
* Will treat keys string representations of number (ie. '1') equal to the |
482
|
|
|
* numeric value (ie. 1). |
483
|
|
|
* |
484
|
|
|
* Example: |
485
|
|
|
* Given |
486
|
|
|
* array('0' => 'Zero', '1' => 'One' ) |
487
|
|
|
* it will return |
488
|
|
|
* array(0 => 'Zero', 1 => 'One') |
489
|
|
|
* |
490
|
|
|
* @param array $array Input array |
491
|
|
|
* @param int $level Internal level used for recursion, do *not* set from outside! |
492
|
|
|
* @return array |
493
|
|
|
*/ |
494
|
|
|
public static function renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array = [], $level = 0) |
495
|
|
|
{ |
496
|
|
|
$level++; |
497
|
|
|
$allKeysAreNumeric = true; |
498
|
|
|
foreach ($array as $key => $_) { |
499
|
|
|
if (is_numeric($key) === false) { |
500
|
|
|
$allKeysAreNumeric = false; |
501
|
|
|
break; |
502
|
|
|
} |
503
|
|
|
} |
504
|
|
|
$renumberedArray = $array; |
505
|
|
|
if ($allKeysAreNumeric === true) { |
506
|
|
|
$renumberedArray = array_values($array); |
507
|
|
|
} |
508
|
|
|
foreach ($renumberedArray as $key => $value) { |
509
|
|
|
if (is_array($value)) { |
510
|
|
|
$renumberedArray[$key] = self::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($value, $level); |
511
|
|
|
} |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
return $renumberedArray; |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* Merges two arrays recursively and "binary safe" (integer keys are |
519
|
|
|
* overridden as well), overruling similar values in the original array |
520
|
|
|
* with the values of the overrule array. |
521
|
|
|
* In case of identical keys, ie. keeping the values of the overrule array. |
522
|
|
|
* |
523
|
|
|
* This method takes the original array by reference for speed optimization with large arrays |
524
|
|
|
* |
525
|
|
|
* The differences to the existing PHP function array_merge_recursive() are: |
526
|
|
|
* * Keys of the original array can be unset via the overrule array. ($enableUnsetFeature) |
527
|
|
|
* * Much more control over what is actually merged. ($addKeys, $includeEmptyValues) |
528
|
|
|
* * Elements or the original array get overwritten if the same key is present in the overrule array. |
529
|
|
|
* |
530
|
|
|
* @param array $original Original array. It will be *modified* by this method and contains the result afterwards! |
531
|
|
|
* @param array $overrule Overrule array, overruling the original array |
532
|
|
|
* @param bool $addKeys If set to FALSE, keys that are NOT found in $original will not be set. Thus only existing value can/will be overruled from overrule array. |
533
|
|
|
* @param bool $includeEmptyValues If set, values from $overrule will overrule if they are empty or zero. |
534
|
|
|
* @param bool $enableUnsetFeature If set, special values "__UNSET" can be used in the overrule array in order to unset array keys in the original array. |
535
|
|
|
* @return void |
536
|
|
|
*/ |
537
|
|
|
public static function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true) |
538
|
|
|
{ |
539
|
|
|
foreach ($overrule as $key => $_) { |
540
|
|
|
if ($enableUnsetFeature && $overrule[$key] === '__UNSET') { |
541
|
|
|
unset($original[$key]); |
542
|
|
|
continue; |
543
|
|
|
} |
544
|
|
|
if (isset($original[$key]) && is_array($original[$key])) { |
545
|
|
|
if (is_array($overrule[$key])) { |
546
|
|
|
self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature); |
547
|
|
|
} |
548
|
|
|
} elseif ( |
549
|
|
|
($addKeys || isset($original[$key])) |
550
|
|
|
&& ($includeEmptyValues || $overrule[$key]) |
551
|
|
|
) { |
552
|
|
|
$original[$key] = $overrule[$key]; |
553
|
|
|
} |
554
|
|
|
} |
555
|
|
|
// This line is kept for backward compatibility reasons. |
556
|
|
|
reset($original); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Check if an string item exists in an array. |
561
|
|
|
* Please note that the order of function parameters is reverse compared to the PHP function in_array()!!! |
562
|
|
|
* |
563
|
|
|
* Comparison to PHP in_array(): |
564
|
|
|
* -> $array = array(0, 1, 2, 3); |
565
|
|
|
* -> variant_a := \TYPO3\CMS\Core\Utility\ArrayUtility::inArray($array, $needle) |
566
|
|
|
* -> variant_b := in_array($needle, $array) |
567
|
|
|
* -> variant_c := in_array($needle, $array, TRUE) |
568
|
|
|
* +---------+-----------+-----------+-----------+ |
569
|
|
|
* | $needle | variant_a | variant_b | variant_c | |
570
|
|
|
* +---------+-----------+-----------+-----------+ |
571
|
|
|
* | '1a' | FALSE | TRUE | FALSE | |
572
|
|
|
* | '' | FALSE | TRUE | FALSE | |
573
|
|
|
* | '0' | TRUE | TRUE | FALSE | |
574
|
|
|
* | 0 | TRUE | TRUE | TRUE | |
575
|
|
|
* +---------+-----------+-----------+-----------+ |
576
|
|
|
* |
577
|
|
|
* @param array $in_array One-dimensional array of items |
578
|
|
|
* @param string $item Item to check for |
579
|
|
|
* @return bool TRUE if $item is in the one-dimensional array $in_array |
580
|
|
|
*/ |
581
|
|
|
public static function inArray(array $in_array, $item) |
582
|
|
|
{ |
583
|
|
|
foreach ($in_array as $val) { |
584
|
|
|
if (! is_array($val) && (string) $val === (string) $item) { |
585
|
|
|
return true; |
586
|
|
|
} |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
return false; |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
/** |
593
|
|
|
* Removes the value $cmpValue from the $array if found there. Returns the modified array. |
594
|
|
|
* |
595
|
|
|
* @param array $array Array containing the values |
596
|
|
|
* @param string $cmpValue Value to search for and if found remove array entry where found. |
597
|
|
|
* @return array Output array with entries removed if search string is found |
598
|
|
|
*/ |
599
|
|
|
public static function removeArrayEntryByValue(array $array, $cmpValue) |
600
|
|
|
{ |
601
|
|
|
foreach ($array as $k => $v) { |
602
|
|
|
if (is_array($v)) { |
603
|
|
|
$array[$k] = self::removeArrayEntryByValue($v, $cmpValue); |
604
|
|
|
} elseif ((string) $v === (string) $cmpValue) { |
605
|
|
|
unset($array[$k]); |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
return $array; |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
/** |
613
|
|
|
* Filters an array to reduce its elements to match the condition. |
614
|
|
|
* The values in $keepItems can be optionally evaluated by a custom callback function. |
615
|
|
|
* |
616
|
|
|
* Example (arguments used to call this function): |
617
|
|
|
* $array = array( |
618
|
|
|
* array('aa' => array('first', 'second'), |
619
|
|
|
* array('bb' => array('third', 'fourth'), |
620
|
|
|
* array('cc' => array('fifth', 'sixth'), |
621
|
|
|
* ); |
622
|
|
|
* $keepItems = array('third'); |
623
|
|
|
* $getValueFunc = function($value) { return $value[0]; } |
624
|
|
|
* |
625
|
|
|
* Returns: |
626
|
|
|
* array( |
627
|
|
|
* array('bb' => array('third', 'fourth'), |
628
|
|
|
* ) |
629
|
|
|
* |
630
|
|
|
* @param array $array The initial array to be filtered/reduced |
631
|
|
|
* @param mixed $keepItems The items which are allowed/kept in the array - accepts array or csv string |
632
|
|
|
* @param string $getValueFunc (optional) Callback function used to get the value to keep |
633
|
|
|
* @return array The filtered/reduced array with the kept items |
634
|
|
|
*/ |
635
|
|
|
public static function keepItemsInArray(array $array, $keepItems, $getValueFunc = null) |
636
|
|
|
{ |
637
|
|
|
if ($array) { |
|
|
|
|
638
|
|
|
// Convert strings to arrays: |
639
|
|
|
if (is_string($keepItems)) { |
640
|
|
|
$keepItems = GeneralUtility::trimExplode(',', $keepItems); |
641
|
|
|
} |
642
|
|
|
// Check if valueFunc can be executed: |
643
|
|
|
if (! is_callable($getValueFunc)) { |
644
|
|
|
$getValueFunc = null; |
645
|
|
|
} |
646
|
|
|
// Do the filtering: |
647
|
|
|
if (is_array($keepItems) && ! empty($keepItems)) { |
648
|
|
|
foreach ($array as $key => $value) { |
649
|
|
|
// Get the value to compare by using the callback function: |
650
|
|
|
$keepValue = isset($getValueFunc) ? call_user_func($getValueFunc, $value) : $value; |
651
|
|
|
if (! in_array($keepValue, $keepItems)) { |
652
|
|
|
unset($array[$key]); |
653
|
|
|
} |
654
|
|
|
} |
655
|
|
|
} |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
return $array; |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* Rename Array keys with a given mapping table. |
663
|
|
|
* |
664
|
|
|
* @param array $array Array by reference which should be remapped |
665
|
|
|
* @param array $mappingTable Array with remap information, array/$oldKey => $newKey) |
666
|
|
|
*/ |
667
|
|
|
public static function remapArrayKeys(array &$array, array $mappingTable) |
668
|
|
|
{ |
669
|
|
|
foreach ($mappingTable as $old => $new) { |
670
|
|
|
if ($new && isset($array[$old])) { |
671
|
|
|
$array[$new] = $array[$old]; |
672
|
|
|
unset($array[$old]); |
673
|
|
|
} |
674
|
|
|
} |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
/** |
678
|
|
|
* Filters keys off from first array that also exist in second array. Comparison is done by keys. |
679
|
|
|
* This method is a recursive version of php array_diff_assoc(). |
680
|
|
|
* |
681
|
|
|
* @param array $array1 Source array |
682
|
|
|
* @param array $array2 Reduce source array by this array |
683
|
|
|
* @return array Source array reduced by keys also present in second array |
684
|
|
|
*/ |
685
|
|
|
public static function arrayDiffAssocRecursive(array $array1, array $array2) |
686
|
|
|
{ |
687
|
|
|
$differenceArray = []; |
688
|
|
|
foreach ($array1 as $key => $value) { |
689
|
|
|
if (! array_key_exists($key, $array2)) { |
690
|
|
|
$differenceArray[$key] = $value; |
691
|
|
|
} elseif (is_array($value)) { |
692
|
|
|
if (is_array($array2[$key])) { |
693
|
|
|
$differenceArray[$key] = self::arrayDiffAssocRecursive($value, $array2[$key]); |
694
|
|
|
} |
695
|
|
|
} |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
return $differenceArray; |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
/** |
702
|
|
|
* Sorts an array by key recursive - uses natural sort order (aAbB-zZ). |
703
|
|
|
* |
704
|
|
|
* @param array $array array to be sorted recursively, passed by reference |
705
|
|
|
* @return bool always TRUE |
706
|
|
|
*/ |
707
|
|
|
public static function naturalKeySortRecursive(array &$array) |
708
|
|
|
{ |
709
|
|
|
uksort($array, 'strnatcasecmp'); |
710
|
|
|
foreach ($array as $key => &$value) { |
711
|
|
|
if (is_array($value)) { |
712
|
|
|
self::naturalKeySortRecursive($value); |
713
|
|
|
} |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
return true; |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.