[BUGFIX] Followup Fluid Template Fallback paths
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Utility / ArrayUtility.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30
31 /**
32 * The array functions from good old GeneralUtility plus new code.
33 *
34 * @api
35 */
36 class ArrayUtility {
37
38 /**
39 * Explodes a $string delimited by $delimeter and passes each item in the array through intval().
40 * Corresponds to explode(), but with conversion to integers for all values.
41 *
42 * @param string $delimiter Delimiter string to explode with
43 * @param string $string The string to explode
44 * @return array Exploded values, all converted to integers
45 * @api
46 */
47 static public function integerExplode($delimiter, $string) {
48 $explodedValues = self::trimExplode($delimiter, $string);
49 return array_map('intval', $explodedValues);
50 }
51
52 /**
53 * Explodes a string and trims all values for whitespace in the ends.
54 * If $onlyNonEmptyValues is set, then all blank ('') values are removed.
55 *
56 * @param string $delimiter Delimiter string to explode with
57 * @param string $string The string to explode
58 * @param boolean $onlyNonEmptyValues If set, all empty values (='') will NOT be set in output
59 * @return array Exploded values
60 * @api
61 */
62 static public function trimExplode($delimiter, $string, $onlyNonEmptyValues = FALSE) {
63 $chunksArr = explode($delimiter, $string);
64 $newChunksArr = array();
65 foreach ($chunksArr as $value) {
66 if ($onlyNonEmptyValues === FALSE || strcmp('', trim($value))) {
67 $newChunksArr[] = trim($value);
68 }
69 }
70 reset($newChunksArr);
71 return $newChunksArr;
72 }
73
74 /**
75 * Merges two arrays recursively and "binary safe" (integer keys are overridden as well), overruling similar values in the first array ($firstArray) with the values of the second array ($secondArray)
76 * In case of identical keys, ie. keeping the values of the second.
77 *
78 * @param array $firstArray First array
79 * @param array $secondArray Second array, overruling the first array
80 * @param boolean $dontAddNewKeys If set, keys that are NOT found in $firstArray (first array) will not be set. Thus only existing value can/will be overruled from second array.
81 * @param boolean $emptyValuesOverride If set (which is the default), values from $secondArray will overrule if they are empty (according to PHP's empty() function)
82 * @return array Resulting array where $secondArray values has overruled $firstArray values
83 * @api
84 */
85 static public function arrayMergeRecursiveOverrule(array $firstArray, array $secondArray, $dontAddNewKeys = FALSE, $emptyValuesOverride = TRUE) {
86 foreach ($secondArray as $key => $value) {
87 if (array_key_exists($key, $firstArray) && is_array($firstArray[$key])) {
88 if (is_array($secondArray[$key])) {
89 $firstArray[$key] = self::arrayMergeRecursiveOverrule($firstArray[$key], $secondArray[$key], $dontAddNewKeys, $emptyValuesOverride);
90 } else {
91 $firstArray[$key] = $secondArray[$key];
92 }
93 } else {
94 if ($dontAddNewKeys) {
95 if (array_key_exists($key, $firstArray)) {
96 if ($emptyValuesOverride || !empty($value)) {
97 $firstArray[$key] = $value;
98 }
99 }
100 } else {
101 if ($emptyValuesOverride || !empty($value)) {
102 $firstArray[$key] = $value;
103 }
104 }
105 }
106 }
107 reset($firstArray);
108 return $firstArray;
109 }
110
111 /**
112 * Randomizes the order of array values. The array should not be an associative array
113 * as the key-value relations will be lost.
114 *
115 * @param array $array Array to reorder
116 * @return array The array with randomly ordered values
117 * @api
118 */
119 static public function randomizeArrayOrder(array $array) {
120 $reorderedArray = array();
121 if (count($array) > 1) {
122 $keysInRandomOrder = array_rand($array, count($array));
123 foreach ($keysInRandomOrder as $key) {
124 $reorderedArray[] = $array[$key];
125 }
126 } else {
127 $reorderedArray = $array;
128 }
129 return $reorderedArray;
130 }
131
132 /**
133 * Returns TRUE if the given array contains elements of varying types
134 *
135 * @param array $array
136 * @return boolean
137 * @api
138 */
139 static public function containsMultipleTypes(array $array) {
140 if (count($array) > 0) {
141 foreach ($array as $value) {
142 if (!isset($previousType)) {
143 $previousType = gettype($value);
144 } elseif ($previousType !== gettype($value)) {
145 return TRUE;
146 }
147 }
148 }
149 return FALSE;
150 }
151
152 /**
153 * Replacement for array_reduce that allows any type for $initial (instead
154 * of only integer)
155 *
156 * @param array $array the array to reduce
157 * @param string $function the reduce function with the same order of parameters as in the native array_reduce (i.e. accumulator first, then current array element)
158 * @param mixed $initial the initial accumulator value
159 * @return mixed
160 * @api
161 */
162 static public function array_reduce(array $array, $function, $initial = NULL) {
163 $accumlator = $initial;
164 foreach ($array as $value) {
165 $accumlator = $function($accumlator, $value);
166 }
167 return $accumlator;
168 }
169
170 /**
171 * Returns the value of a nested array by following the specifed path.
172 *
173 * @param array &$array The array to traverse as a reference
174 * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz'
175 * @throws \InvalidArgumentException
176 * @return mixed The value found, NULL if the path didn't exist
177 * @api
178 */
179 static public function getValueByPath(array &$array, $path) {
180 if (is_string($path)) {
181 $path = explode('.', $path);
182 } elseif (!is_array($path)) {
183 throw new \InvalidArgumentException('getValueByPath() expects $path to be string or array, "' . gettype($path) . '" given.', 1304950007);
184 }
185 $key = array_shift($path);
186 if (isset($array[$key])) {
187 if (count($path) > 0) {
188 return is_array($array[$key]) ? self::getValueByPath($array[$key], $path) : NULL;
189 } else {
190 return $array[$key];
191 }
192 } else {
193 return NULL;
194 }
195 }
196
197 /**
198 * Sets the given value in a nested array or object by following the specified path.
199 *
200 * @param array|\ArrayAccess $subject The array or ArrayAccess instance to work on
201 * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz'
202 * @param mixed $value The value to set
203 * @throws \InvalidArgumentException
204 * @return array The modified array or object
205 */
206 static public function setValueByPath($subject, $path, $value) {
207 if (!is_array($subject) && !$subject instanceof \ArrayAccess) {
208 throw new \InvalidArgumentException('setValueByPath() expects $subject to be array or an object implementing \\ArrayAccess, "' . (is_object($subject) ? get_class($subject) : gettype($subject)) . '" given.', 1306424308);
209 }
210 if (is_string($path)) {
211 $path = explode('.', $path);
212 } elseif (!is_array($path)) {
213 throw new \InvalidArgumentException('setValueByPath() expects $path to be string or array, "' . gettype($path) . '" given.', 1305111499);
214 }
215 $key = array_shift($path);
216 if (count($path) === 0) {
217 $subject[$key] = $value;
218 } else {
219 if (!isset($subject[$key]) || !is_array($subject[$key])) {
220 $subject[$key] = array();
221 }
222 $subject[$key] = self::setValueByPath($subject[$key], $path, $value);
223 }
224 return $subject;
225 }
226
227 /**
228 * Unsets an element/part of a nested array by following the specified path.
229 *
230 * @param array $array The array
231 * @param array|string $path The path to follow. Either a simple array of keys or a string in the format 'foo.bar.baz'
232 * @throws \InvalidArgumentException
233 * @return array The modified array
234 */
235 static public function unsetValueByPath(array $array, $path) {
236 if (is_string($path)) {
237 $path = explode('.', $path);
238 } elseif (!is_array($path)) {
239 throw new \InvalidArgumentException('unsetValueByPath() expects $path to be string or array, "' . gettype($path) . '" given.', 1305111513);
240 }
241 $key = array_shift($path);
242 if (count($path) === 0) {
243 unset($array[$key]);
244 } else {
245 if (!isset($array[$key]) || !is_array($array[$key])) {
246 return $array;
247 }
248 $array[$key] = self::unsetValueByPath($array[$key], $path);
249 }
250 return $array;
251 }
252
253 /**
254 * Sorts multidimensional arrays by recursively calling ksort on its elements.
255 *
256 * @param array &$array the array to sort
257 * @param integer $sortFlags may be used to modify the sorting behavior using these values (see http://www.php.net/manual/en/function.sort.php)
258 * @return boolean TRUE on success, FALSE on failure
259 * @see asort()
260 * @api
261 */
262 static public function sortKeysRecursively(array &$array, $sortFlags = NULL) {
263 foreach ($array as &$value) {
264 if (is_array($value)) {
265 if (self::sortKeysRecursively($value, $sortFlags) === FALSE) {
266 return FALSE;
267 }
268 }
269 }
270 return ksort($array, $sortFlags);
271 }
272
273 /**
274 * Recursively convert an object hierarchy into an associative array.
275 *
276 * @param mixed $subject An object or array of objects
277 * @throws \InvalidArgumentException
278 * @return array The subject represented as an array
279 */
280 static public function convertObjectToArray($subject) {
281 if (!is_object($subject) && !is_array($subject)) {
282 throw new \InvalidArgumentException('convertObjectToArray expects either array or object as input, ' . gettype($subject) . ' given.', 1287059709);
283 }
284 if (is_object($subject)) {
285 $subject = (array) $subject;
286 }
287 foreach ($subject as $key => $value) {
288 if (is_array($value) || is_object($value)) {
289 $subject[$key] = self::convertObjectToArray($value);
290 }
291 }
292 return $subject;
293 }
294
295 /**
296 * Recursively removes empty array elements.
297 *
298 * @param array $array
299 * @return array the modified array
300 */
301 static public function removeEmptyElementsRecursively(array $array) {
302 $result = $array;
303 foreach ($result as $key => $value) {
304 if (is_array($value)) {
305 $result[$key] = self::removeEmptyElementsRecursively($value);
306 if ($result[$key] === array()) {
307 unset($result[$key]);
308 }
309 } elseif ($value === NULL) {
310 unset($result[$key]);
311 }
312 }
313 return $result;
314 }
315
316 /**
317 * If the array contains numerical keys only, sort it in ascending order
318 *
319 * @param array $array
320 *
321 * @return array
322 */
323 public static function sortArrayWithIntegerKeys($array) {
324 $containsNumericalKeysOnly = TRUE;
325 array_walk($array, function($value, $key) use (&$containsNumericalKeysOnly) {
326 if (!is_integer($key)) {
327 $containsNumericalKeysOnly = FALSE;
328 return;
329 }
330 });
331 if ($containsNumericalKeysOnly === TRUE) {
332 ksort($array);
333 }
334 return $array;
335 }
336 }