1 <?php
2 /**
3 * @package FrameworkOnFramework
4 * @subpackage utils
5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE.txt
7 */
8
9 defined('FOF_INCLUDED') or die;
10
11 /**
12 * A utility class to handle array manipulation.
13 *
14 * Based on the JArrayHelper class as found in Joomla! 3.2.0
15 */
16 abstract class FOFUtilsArray
17 {
18 /**
19 * Option to perform case-sensitive sorts.
20 *
21 * @var mixed Boolean or array of booleans.
22 */
23 protected static $sortCase;
24
25 /**
26 * Option to set the sort direction.
27 *
28 * @var mixed Integer or array of integers.
29 */
30 protected static $sortDirection;
31
32 /**
33 * Option to set the object key to sort on.
34 *
35 * @var string
36 */
37 protected static $sortKey;
38
39 /**
40 * Option to perform a language aware sort.
41 *
42 * @var mixed Boolean or array of booleans.
43 */
44 protected static $sortLocale;
45
46 /**
47 * Function to convert array to integer values
48 *
49 * @param array &$array The source array to convert
50 * @param mixed $default A default value (int|array) to assign if $array is not an array
51 *
52 * @return void
53 */
54 public static function toInteger(&$array, $default = null)
55 {
56 if (is_array($array))
57 {
58 foreach ($array as $i => $v)
59 {
60 $array[$i] = (int) $v;
61 }
62 }
63 else
64 {
65 if ($default === null)
66 {
67 $array = array();
68 }
69 elseif (is_array($default))
70 {
71 self::toInteger($default, null);
72 $array = $default;
73 }
74 else
75 {
76 $array = array((int) $default);
77 }
78 }
79 }
80
81 /**
82 * Utility function to map an array to a stdClass object.
83 *
84 * @param array &$array The array to map.
85 * @param string $class Name of the class to create
86 *
87 * @return object The object mapped from the given array
88 */
89 public static function toObject(&$array, $class = 'stdClass')
90 {
91 $obj = null;
92
93 if (is_array($array))
94 {
95 $obj = new $class;
96
97 foreach ($array as $k => $v)
98 {
99 if (is_array($v))
100 {
101 $obj->$k = self::toObject($v, $class);
102 }
103 else
104 {
105 $obj->$k = $v;
106 }
107 }
108 }
109 return $obj;
110 }
111
112 /**
113 * Utility function to map an array to a string.
114 *
115 * @param array $array The array to map.
116 * @param string $inner_glue The glue (optional, defaults to '=') between the key and the value.
117 * @param string $outer_glue The glue (optional, defaults to ' ') between array elements.
118 * @param boolean $keepOuterKey True if final key should be kept.
119 *
120 * @return string The string mapped from the given array
121 */
122 public static function toString($array = null, $inner_glue = '=', $outer_glue = ' ', $keepOuterKey = false)
123 {
124 $output = array();
125
126 if (is_array($array))
127 {
128 foreach ($array as $key => $item)
129 {
130 if (is_array($item))
131 {
132 if ($keepOuterKey)
133 {
134 $output[] = $key;
135 }
136 // This is value is an array, go and do it again!
137 $output[] = self::toString($item, $inner_glue, $outer_glue, $keepOuterKey);
138 }
139 else
140 {
141 $output[] = $key . $inner_glue . '"' . $item . '"';
142 }
143 }
144 }
145
146 return implode($outer_glue, $output);
147 }
148
149 /**
150 * Utility function to map an object to an array
151 *
152 * @param object $p_obj The source object
153 * @param boolean $recurse True to recurse through multi-level objects
154 * @param string $regex An optional regular expression to match on field names
155 *
156 * @return array The array mapped from the given object
157 */
158 public static function fromObject($p_obj, $recurse = true, $regex = null)
159 {
160 if (is_object($p_obj))
161 {
162 return self::_fromObject($p_obj, $recurse, $regex);
163 }
164 else
165 {
166 return null;
167 }
168 }
169
170 /**
171 * Utility function to map an object or array to an array
172 *
173 * @param mixed $item The source object or array
174 * @param boolean $recurse True to recurse through multi-level objects
175 * @param string $regex An optional regular expression to match on field names
176 *
177 * @return array The array mapped from the given object
178 */
179 protected static function _fromObject($item, $recurse, $regex)
180 {
181 if (is_object($item))
182 {
183 $result = array();
184
185 foreach (get_object_vars($item) as $k => $v)
186 {
187 if (!$regex || preg_match($regex, $k))
188 {
189 if ($recurse)
190 {
191 $result[$k] = self::_fromObject($v, $recurse, $regex);
192 }
193 else
194 {
195 $result[$k] = $v;
196 }
197 }
198 }
199 }
200 elseif (is_array($item))
201 {
202 $result = array();
203
204 foreach ($item as $k => $v)
205 {
206 $result[$k] = self::_fromObject($v, $recurse, $regex);
207 }
208 }
209 else
210 {
211 $result = $item;
212 }
213 return $result;
214 }
215
216 /**
217 * Extracts a column from an array of arrays or objects
218 *
219 * @param array &$array The source array
220 * @param string $index The index of the column or name of object property
221 *
222 * @return array Column of values from the source array
223 */
224 public static function getColumn(&$array, $index)
225 {
226 $result = array();
227
228 if (is_array($array))
229 {
230 foreach ($array as &$item)
231 {
232 if (is_array($item) && isset($item[$index]))
233 {
234 $result[] = $item[$index];
235 }
236 elseif (is_object($item) && isset($item->$index))
237 {
238 $result[] = $item->$index;
239 }
240 // Else ignore the entry
241 }
242 }
243 return $result;
244 }
245
246 /**
247 * Utility function to return a value from a named array or a specified default
248 *
249 * @param array &$array A named array
250 * @param string $name The key to search for
251 * @param mixed $default The default value to give if no key found
252 * @param string $type Return type for the variable (INT, FLOAT, STRING, WORD, BOOLEAN, ARRAY)
253 *
254 * @return mixed The value from the source array
255 */
256 public static function getValue(&$array, $name, $default = null, $type = '')
257 {
258 $result = null;
259
260 if (isset($array[$name]))
261 {
262 $result = $array[$name];
263 }
264
265 // Handle the default case
266 if (is_null($result))
267 {
268 $result = $default;
269 }
270
271 // Handle the type constraint
272 switch (strtoupper($type))
273 {
274 case 'INT':
275 case 'INTEGER':
276 // Only use the first integer value
277 @preg_match('/-?[0-9]+/', $result, $matches);
278 $result = @(int) $matches[0];
279 break;
280
281 case 'FLOAT':
282 case 'DOUBLE':
283 // Only use the first floating point value
284 @preg_match('/-?[0-9]+(\.[0-9]+)?/', $result, $matches);
285 $result = @(float) $matches[0];
286 break;
287
288 case 'BOOL':
289 case 'BOOLEAN':
290 $result = (bool) $result;
291 break;
292
293 case 'ARRAY':
294 if (!is_array($result))
295 {
296 $result = array($result);
297 }
298 break;
299
300 case 'STRING':
301 $result = (string) $result;
302 break;
303
304 case 'WORD':
305 $result = (string) preg_replace('#\W#', '', $result);
306 break;
307
308 case 'NONE':
309 default:
310 // No casting necessary
311 break;
312 }
313 return $result;
314 }
315
316 /**
317 * Takes an associative array of arrays and inverts the array keys to values using the array values as keys.
318 *
319 * Example:
320 * $input = array(
321 * 'New' => array('1000', '1500', '1750'),
322 * 'Used' => array('3000', '4000', '5000', '6000')
323 * );
324 * $output = FOFUtilsArray::invert($input);
325 *
326 * Output would be equal to:
327 * $output = array(
328 * '1000' => 'New',
329 * '1500' => 'New',
330 * '1750' => 'New',
331 * '3000' => 'Used',
332 * '4000' => 'Used',
333 * '5000' => 'Used',
334 * '6000' => 'Used'
335 * );
336 *
337 * @param array $array The source array.
338 *
339 * @return array The inverted array.
340 */
341 public static function invert($array)
342 {
343 $return = array();
344
345 foreach ($array as $base => $values)
346 {
347 if (!is_array($values))
348 {
349 continue;
350 }
351
352 foreach ($values as $key)
353 {
354 // If the key isn't scalar then ignore it.
355 if (is_scalar($key))
356 {
357 $return[$key] = $base;
358 }
359 }
360 }
361 return $return;
362 }
363
364 /**
365 * Method to determine if an array is an associative array.
366 *
367 * @param array $array An array to test.
368 *
369 * @return boolean True if the array is an associative array.
370 */
371 public static function isAssociative($array)
372 {
373 if (is_array($array))
374 {
375 foreach (array_keys($array) as $k => $v)
376 {
377 if ($k !== $v)
378 {
379 return true;
380 }
381 }
382 }
383
384 return false;
385 }
386
387 /**
388 * Pivots an array to create a reverse lookup of an array of scalars, arrays or objects.
389 *
390 * @param array $source The source array.
391 * @param string $key Where the elements of the source array are objects or arrays, the key to pivot on.
392 *
393 * @return array An array of arrays pivoted either on the value of the keys, or an individual key of an object or array.
394 */
395 public static function pivot($source, $key = null)
396 {
397 $result = array();
398 $counter = array();
399
400 foreach ($source as $index => $value)
401 {
402 // Determine the name of the pivot key, and its value.
403 if (is_array($value))
404 {
405 // If the key does not exist, ignore it.
406 if (!isset($value[$key]))
407 {
408 continue;
409 }
410
411 $resultKey = $value[$key];
412 $resultValue = &$source[$index];
413 }
414 elseif (is_object($value))
415 {
416 // If the key does not exist, ignore it.
417 if (!isset($value->$key))
418 {
419 continue;
420 }
421
422 $resultKey = $value->$key;
423 $resultValue = &$source[$index];
424 }
425 else
426 {
427 // Just a scalar value.
428 $resultKey = $value;
429 $resultValue = $index;
430 }
431
432 // The counter tracks how many times a key has been used.
433 if (empty($counter[$resultKey]))
434 {
435 // The first time around we just assign the value to the key.
436 $result[$resultKey] = $resultValue;
437 $counter[$resultKey] = 1;
438 }
439 elseif ($counter[$resultKey] == 1)
440 {
441 // If there is a second time, we convert the value into an array.
442 $result[$resultKey] = array(
443 $result[$resultKey],
444 $resultValue,
445 );
446 $counter[$resultKey]++;
447 }
448 else
449 {
450 // After the second time, no need to track any more. Just append to the existing array.
451 $result[$resultKey][] = $resultValue;
452 }
453 }
454
455 unset($counter);
456
457 return $result;
458 }
459
460 /**
461 * Utility function to sort an array of objects on a given field
462 *
463 * @param array &$a An array of objects
464 * @param mixed $k The key (string) or a array of key to sort on
465 * @param mixed $direction Direction (integer) or an array of direction to sort in [1 = Ascending] [-1 = Descending]
466 * @param mixed $caseSensitive Boolean or array of booleans to let sort occur case sensitive or insensitive
467 * @param mixed $locale Boolean or array of booleans to let sort occur using the locale language or not
468 *
469 * @return array The sorted array of objects
470 */
471 public static function sortObjects(&$a, $k, $direction = 1, $caseSensitive = true, $locale = false)
472 {
473 if (!is_array($locale) || !is_array($locale[0]))
474 {
475 $locale = array($locale);
476 }
477
478 self::$sortCase = (array) $caseSensitive;
479 self::$sortDirection = (array) $direction;
480 self::$sortKey = (array) $k;
481 self::$sortLocale = $locale;
482
483 usort($a, array(__CLASS__, '_sortObjects'));
484
485 self::$sortCase = null;
486 self::$sortDirection = null;
487 self::$sortKey = null;
488 self::$sortLocale = null;
489
490 return $a;
491 }
492
493 /**
494 * Callback function for sorting an array of objects on a key
495 *
496 * @param array &$a An array of objects
497 * @param array &$b An array of objects
498 *
499 * @return integer Comparison status
500 *
501 * @see FOFUtilsArray::sortObjects()
502 */
503 protected static function _sortObjects(&$a, &$b)
504 {
505 $key = self::$sortKey;
506
507 for ($i = 0, $count = count($key); $i < $count; $i++)
508 {
509 if (isset(self::$sortDirection[$i]))
510 {
511 $direction = self::$sortDirection[$i];
512 }
513
514 if (isset(self::$sortCase[$i]))
515 {
516 $caseSensitive = self::$sortCase[$i];
517 }
518
519 if (isset(self::$sortLocale[$i]))
520 {
521 $locale = self::$sortLocale[$i];
522 }
523
524 $va = $a->{$key[$i]};
525 $vb = $b->{$key[$i]};
526
527 if ((is_bool($va) || is_numeric($va)) && (is_bool($vb) || is_numeric($vb)))
528 {
529 $cmp = $va - $vb;
530 }
531 elseif ($caseSensitive)
532 {
533 $cmp = JString::strcmp($va, $vb, $locale);
534 }
535 else
536 {
537 $cmp = JString::strcasecmp($va, $vb, $locale);
538 }
539
540 if ($cmp > 0)
541 {
542
543 return $direction;
544 }
545
546 if ($cmp < 0)
547 {
548 return -$direction;
549 }
550 }
551
552 return 0;
553 }
554
555 /**
556 * Multidimensional array safe unique test
557 *
558 * @param array $myArray The array to make unique.
559 *
560 * @return array
561 *
562 * @see http://php.net/manual/en/function.array-unique.php
563 */
564 public static function arrayUnique($myArray)
565 {
566 if (!is_array($myArray))
567 {
568 return $myArray;
569 }
570
571 foreach ($myArray as &$myvalue)
572 {
573 $myvalue = serialize($myvalue);
574 }
575
576 $myArray = array_unique($myArray);
577
578 foreach ($myArray as &$myvalue)
579 {
580 $myvalue = unserialize($myvalue);
581 }
582
583 return $myArray;
584 }
585 }
586