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