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