commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / CRM / Utils / Array.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Provides a collection of static methods for array manipulation.
30 *
31 * @package CRM
32 * @copyright CiviCRM LLC (c) 2004-2015
33 */
34 class CRM_Utils_Array {
35
36 /**
37 * Returns $list[$key] if such element exists, or a default value otherwise.
38 *
39 * If $list is not actually an array at all, then the default value is
40 * returned.
41 *
42 *
43 * @param string $key
44 * Key value to look up in the array.
45 * @param array $list
46 * Array from which to look up a value.
47 * @param mixed $default
48 * (optional) Value to return $list[$key] does not exist.
49 *
50 * @return mixed
51 * Can return any type, since $list might contain anything.
52 */
53 public static function value($key, $list, $default = NULL) {
54 if (is_array($list)) {
55 return array_key_exists($key, $list) ? $list[$key] : $default;
56 }
57 return $default;
58 }
59
60 /**
61 * Recursively searches an array for a key, returning the first value found.
62 *
63 * If $params[$key] does not exist and $params contains arrays, descend into
64 * each array in a depth-first manner, in array iteration order.
65 *
66 * @param array $params
67 * The array to be searched.
68 * @param string $key
69 * The key to search for.
70 *
71 * @return mixed
72 * The value of the key, or null if the key is not found.
73 */
74 public static function retrieveValueRecursive(&$params, $key) {
75 if (!is_array($params)) {
76 return NULL;
77 }
78 elseif ($value = CRM_Utils_Array::value($key, $params)) {
79 return $value;
80 }
81 else {
82 foreach ($params as $subParam) {
83 if (is_array($subParam) &&
84 $value = self::retrieveValueRecursive($subParam, $key)
85 ) {
86 return $value;
87 }
88 }
89 }
90 return NULL;
91 }
92
93 /**
94 * Wraps and slightly changes the behavior of PHP's array_search().
95 *
96 * This function reproduces the behavior of array_search() from PHP prior to
97 * version 4.2.0, which was to return NULL on failure. This function also
98 * checks that $list is an array before attempting to search it.
99 *
100 *
101 * @param mixed $value
102 * The value to search for.
103 * @param array $list
104 * The array to be searched.
105 *
106 * @return int|string|null
107 * Returns the key, which could be an int or a string, or NULL on failure.
108 */
109 public static function key($value, &$list) {
110 if (is_array($list)) {
111 $key = array_search($value, $list);
112
113 // array_search returns key if found, false otherwise
114 // it may return values like 0 or empty string which
115 // evaluates to false
116 // hence we must use identical comparison operator
117 return ($key === FALSE) ? NULL : $key;
118 }
119 return NULL;
120 }
121
122 /**
123 * Builds an XML fragment representing an array.
124 *
125 * Depending on the nature of the keys of the array (and its sub-arrays,
126 * if any) the XML fragment may not be valid.
127 *
128 * @param array $list
129 * The array to be serialized.
130 * @param int $depth
131 * (optional) Indentation depth counter.
132 * @param string $seperator
133 * (optional) String to be appended after open/close tags.
134 *
135 *
136 * @return string
137 * XML fragment representing $list.
138 */
139 public static function &xml(&$list, $depth = 1, $seperator = "\n") {
140 $xml = '';
141 foreach ($list as $name => $value) {
142 $xml .= str_repeat(' ', $depth * 4);
143 if (is_array($value)) {
144 $xml .= "<{$name}>{$seperator}";
145 $xml .= self::xml($value, $depth + 1, $seperator);
146 $xml .= str_repeat(' ', $depth * 4);
147 $xml .= "</{$name}>{$seperator}";
148 }
149 else {
150 // make sure we escape value
151 $value = self::escapeXML($value);
152 $xml .= "<{$name}>$value</{$name}>{$seperator}";
153 }
154 }
155 return $xml;
156 }
157
158 /**
159 * Sanitizes a string for serialization in CRM_Utils_Array::xml().
160 *
161 * Replaces '&', '<', and '>' with their XML escape sequences. Replaces '^A'
162 * with a comma.
163 *
164 * @param string $value
165 * String to be sanitized.
166 *
167 * @return string
168 * Sanitized version of $value.
169 */
170 public static function escapeXML($value) {
171 static $src = NULL;
172 static $dst = NULL;
173
174 if (!$src) {
175 $src = array('&', '<', '>', '\ 1');
176 $dst = array('&amp;', '&lt;', '&gt;', ',');
177 }
178
179 return str_replace($src, $dst, $value);
180 }
181
182 /**
183 * Converts a nested array to a flat array.
184 *
185 * The nested structure is preserved in the string values of the keys of the
186 * flat array.
187 *
188 * Example nested array:
189 * Array
190 * (
191 * [foo] => Array
192 * (
193 * [0] => bar
194 * [1] => baz
195 * [2] => 42
196 * )
197 *
198 * [asdf] => Array
199 * (
200 * [merp] => bleep
201 * [quack] => Array
202 * (
203 * [0] => 1
204 * [1] => 2
205 * [2] => 3
206 * )
207 *
208 * )
209 *
210 * [quux] => 999
211 * )
212 *
213 * Corresponding flattened array:
214 * Array
215 * (
216 * [foo.0] => bar
217 * [foo.1] => baz
218 * [foo.2] => 42
219 * [asdf.merp] => bleep
220 * [asdf.quack.0] => 1
221 * [asdf.quack.1] => 2
222 * [asdf.quack.2] => 3
223 * [quux] => 999
224 * )
225 *
226 * @param array $list
227 * Array to be flattened.
228 * @param array $flat
229 * Destination array.
230 * @param string $prefix
231 * (optional) String to prepend to keys.
232 * @param string $seperator
233 * (optional) String that separates the concatenated keys.
234 */
235 public static function flatten(&$list, &$flat, $prefix = '', $seperator = ".") {
236 foreach ($list as $name => $value) {
237 $newPrefix = ($prefix) ? $prefix . $seperator . $name : $name;
238 if (is_array($value)) {
239 self::flatten($value, $flat, $newPrefix, $seperator);
240 }
241 else {
242 if (!empty($value)) {
243 $flat[$newPrefix] = $value;
244 }
245 }
246 }
247 }
248
249 /**
250 * Converts an array with path-like keys into a tree of arrays.
251 *
252 * This function is the inverse of CRM_Utils_Array::flatten().
253 *
254 * @param string $delim
255 * A path delimiter
256 * @param array $arr
257 * A one-dimensional array indexed by string keys
258 *
259 * @return array
260 * Array-encoded tree
261 */
262 public function unflatten($delim, &$arr) {
263 $result = array();
264 foreach ($arr as $key => $value) {
265 $path = explode($delim, $key);
266 $node = &$result;
267 while (count($path) > 1) {
268 $key = array_shift($path);
269 if (!isset($node[$key])) {
270 $node[$key] = array();
271 }
272 $node = &$node[$key];
273 }
274 // last part of path
275 $key = array_shift($path);
276 $node[$key] = $value;
277 }
278 return $result;
279 }
280
281 /**
282 * Merges two arrays.
283 *
284 * If $a1[foo] and $a2[foo] both exist and are both arrays, the merge
285 * process recurses into those sub-arrays. If $a1[foo] and $a2[foo] both
286 * exist but they are not both arrays, the value from $a1 overrides the
287 * value from $a2 and the value from $a2 is discarded.
288 *
289 * @param array $a1
290 * First array to be merged.
291 * @param array $a2
292 * Second array to be merged.
293 *
294 * @return array
295 * The merged array.
296 */
297 public static function crmArrayMerge($a1, $a2) {
298 if (empty($a1)) {
299 return $a2;
300 }
301
302 if (empty($a2)) {
303 return $a1;
304 }
305
306 $a3 = array();
307 foreach ($a1 as $key => $value) {
308 if (array_key_exists($key, $a2) &&
309 is_array($a2[$key]) && is_array($a1[$key])
310 ) {
311 $a3[$key] = array_merge($a1[$key], $a2[$key]);
312 }
313 else {
314 $a3[$key] = $a1[$key];
315 }
316 }
317
318 foreach ($a2 as $key => $value) {
319 if (array_key_exists($key, $a1)) {
320 // already handled in above loop
321 continue;
322 }
323 $a3[$key] = $a2[$key];
324 }
325
326 return $a3;
327 }
328
329 /**
330 * Determines whether an array contains any sub-arrays.
331 *
332 * @param array $list
333 * The array to inspect.
334 *
335 * @return bool
336 * True if $list contains at least one sub-array, false otherwise.
337 */
338 public static function isHierarchical(&$list) {
339 foreach ($list as $n => $v) {
340 if (is_array($v)) {
341 return TRUE;
342 }
343 }
344 return FALSE;
345 }
346
347 /**
348 * @param $subset
349 * @param $superset
350 * @return bool
351 * TRUE if $subset is a subset of $superset
352 */
353 public static function isSubset($subset, $superset) {
354 foreach ($subset as $expected) {
355 if (!in_array($expected, $superset)) {
356 return FALSE;
357 }
358 }
359 return TRUE;
360 }
361
362 /**
363 * Searches an array recursively in an optionally case-insensitive manner.
364 *
365 * @param string $value
366 * Value to search for.
367 * @param array $params
368 * Array to search within.
369 * @param bool $caseInsensitive
370 * (optional) Whether to search in a case-insensitive manner.
371 *
372 * @return bool
373 * True if $value was found, false otherwise.
374 */
375 public static function crmInArray($value, $params, $caseInsensitive = TRUE) {
376 foreach ($params as $item) {
377 if (is_array($item)) {
378 $ret = crmInArray($value, $item, $caseInsensitive);
379 }
380 else {
381 $ret = ($caseInsensitive) ? strtolower($item) == strtolower($value) : $item == $value;
382 if ($ret) {
383 return $ret;
384 }
385 }
386 }
387 return FALSE;
388 }
389
390 /**
391 * convert associative array names to values.
392 * and vice-versa.
393 *
394 * This function is used by both the web form layer and the api. Note that
395 * the api needs the name => value conversion, also the view layer typically
396 * requires value => name conversion
397 */
398 public static function lookupValue(&$defaults, $property, $lookup, $reverse) {
399 $id = $property . '_id';
400
401 $src = $reverse ? $property : $id;
402 $dst = $reverse ? $id : $property;
403
404 if (!array_key_exists(strtolower($src), array_change_key_case($defaults, CASE_LOWER))) {
405 return FALSE;
406 }
407
408 $look = $reverse ? array_flip($lookup) : $lookup;
409
410 //trim lookup array, ignore . ( fix for CRM-1514 ), eg for prefix/suffix make sure Dr. and Dr both are valid
411 $newLook = array();
412 foreach ($look as $k => $v) {
413 $newLook[trim($k, ".")] = $v;
414 }
415
416 $look = $newLook;
417
418 if (is_array($look)) {
419 if (!array_key_exists(trim(strtolower($defaults[strtolower($src)]), '.'), array_change_key_case($look, CASE_LOWER))) {
420 return FALSE;
421 }
422 }
423
424 $tempLook = array_change_key_case($look, CASE_LOWER);
425
426 $defaults[$dst] = $tempLook[trim(strtolower($defaults[strtolower($src)]), '.')];
427 return TRUE;
428 }
429
430 /**
431 * Checks whether an array is empty.
432 *
433 * An array is empty if its values consist only of NULL and empty sub-arrays.
434 * Containing a non-NULL value or non-empty array makes an array non-empty.
435 *
436 * If something other than an array is passed, it is considered to be empty.
437 *
438 * If nothing is passed at all, the default value provided is empty.
439 *
440 * @param array $array
441 * (optional) Array to be checked for emptiness.
442 *
443 * @return bool
444 * True if the array is empty.
445 */
446 public static function crmIsEmptyArray($array = array()) {
447 if (!is_array($array)) {
448 return TRUE;
449 }
450 foreach ($array as $element) {
451 if (is_array($element)) {
452 if (!self::crmIsEmptyArray($element)) {
453 return FALSE;
454 }
455 }
456 elseif (isset($element)) {
457 return FALSE;
458 }
459 }
460 return TRUE;
461 }
462
463 /**
464 * Sorts an associative array of arrays by an attribute using strnatcmp().
465 *
466 * @param array $array
467 * Array to be sorted.
468 * @param string $field
469 * Name of the attribute used for sorting.
470 *
471 * @return array
472 * Sorted array
473 */
474 public static function crmArraySortByField($array, $field) {
475 $code = "return strnatcmp(\$a['$field'], \$b['$field']);";
476 uasort($array, create_function('$a,$b', $code));
477 return $array;
478 }
479
480 /**
481 * Recursively removes duplicate values from a multi-dimensional array.
482 *
483 * @param array $array
484 * The input array possibly containing duplicate values.
485 *
486 * @return array
487 * The input array with duplicate values removed.
488 */
489 public static function crmArrayUnique($array) {
490 $result = array_map("unserialize", array_unique(array_map("serialize", $array)));
491 foreach ($result as $key => $value) {
492 if (is_array($value)) {
493 $result[$key] = self::crmArrayUnique($value);
494 }
495 }
496 return $result;
497 }
498
499 /**
500 * Sorts an array and maintains index association (with localization).
501 *
502 * Uses Collate from the PECL "intl" package, if available, for UTF-8
503 * sorting (e.g. list of countries). Otherwise calls PHP's asort().
504 *
505 * On Debian/Ubuntu: apt-get install php5-intl
506 *
507 * @param array $array
508 * (optional) Array to be sorted.
509 *
510 * @return array
511 * Sorted array.
512 */
513 public static function asort($array = array()) {
514 $lcMessages = CRM_Utils_System::getUFLocale();
515
516 if ($lcMessages && $lcMessages != 'en_US' && class_exists('Collator')) {
517 $collator = new Collator($lcMessages . '.utf8');
518 $collator->asort($array);
519 }
520 else {
521 // This calls PHP's built-in asort().
522 asort($array);
523 }
524
525 return $array;
526 }
527
528 /**
529 * Unsets an arbitrary list of array elements from an associative array.
530 *
531 * @param array $items
532 * The array from which to remove items.
533 *
534 * Additional params:
535 * When passed a string, unsets $items[$key].
536 * When passed an array of strings, unsets $items[$k] for each string $k in the array.
537 */
538 public static function remove(&$items) {
539 foreach (func_get_args() as $n => $key) {
540 // Skip argument 0 ($items) by testing $n for truth.
541 if ($n && is_array($key)) {
542 foreach ($key as $k) {
543 unset($items[$k]);
544 }
545 }
546 elseif ($n) {
547 unset($items[$key]);
548 }
549 }
550 }
551
552 /**
553 * Builds an array-tree which indexes the records in an array.
554 *
555 * @param string[] $keys
556 * Properties by which to index.
557 * @param object|array $records
558 *
559 * @return array
560 * Multi-dimensional array, with one layer for each key.
561 */
562 public static function index($keys, $records) {
563 $final_key = array_pop($keys);
564
565 $result = array();
566 foreach ($records as $record) {
567 $node = &$result;
568 foreach ($keys as $key) {
569 if (is_array($record)) {
570 $keyvalue = isset($record[$key]) ? $record[$key] : NULL;
571 }
572 else {
573 $keyvalue = isset($record->{$key}) ? $record->{$key} : NULL;
574 }
575 if (isset($node[$keyvalue]) && !is_array($node[$keyvalue])) {
576 $node[$keyvalue] = array();
577 }
578 $node = &$node[$keyvalue];
579 }
580 if (is_array($record)) {
581 $node[$record[$final_key]] = $record;
582 }
583 else {
584 $node[$record->{$final_key}] = $record;
585 }
586 }
587 return $result;
588 }
589
590 /**
591 * Iterates over a list of records and returns the value of some property.
592 *
593 * @param string $prop
594 * Property to retrieve.
595 * @param array|object $records
596 * A list of records.
597 *
598 * @return array
599 * Keys are the original keys of $records; values are the $prop values.
600 */
601 public static function collect($prop, $records) {
602 $result = array();
603 if (is_array($records)) {
604 foreach ($records as $key => $record) {
605 if (is_object($record)) {
606 $result[$key] = $record->{$prop};
607 }
608 else {
609 $result[$key] = $record[$prop];
610 }
611 }
612 }
613 return $result;
614 }
615
616 /**
617 * Trims delimiters from a string and then splits it using explode().
618 *
619 * This method works mostly like PHP's built-in explode(), except that
620 * surrounding delimiters are trimmed before explode() is called.
621 *
622 * Also, if an array or NULL is passed as the $values parameter, the value is
623 * returned unmodified rather than being passed to explode().
624 *
625 * @param array|null|string $values
626 * The input string (or an array, or NULL).
627 * @param string $delim
628 * (optional) The boundary string.
629 *
630 * @return array|null
631 * An array of strings produced by explode(), or the unmodified input
632 * array, or NULL.
633 */
634 public static function explodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) {
635 if ($values === NULL) {
636 return NULL;
637 }
638 // If we already have an array, no need to continue
639 if (is_array($values)) {
640 return $values;
641 }
642 // Empty string -> empty array
643 if ($values === '') {
644 return array();
645 }
646 return explode($delim, trim((string) $values, $delim));
647 }
648
649 /**
650 * Joins array elements with a string, adding surrounding delimiters.
651 *
652 * This method works mostly like PHP's built-in implode(), but the generated
653 * string is surrounded by delimiter characters. Also, if NULL is passed as
654 * the $values parameter, NULL is returned.
655 *
656 * @param mixed $values
657 * Array to be imploded. If a non-array is passed, it will be cast to an
658 * array.
659 * @param string $delim
660 * Delimiter to be used for implode() and which will surround the output
661 * string.
662 *
663 * @return string|NULL
664 * The generated string, or NULL if NULL was passed as $values parameter.
665 */
666 public static function implodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) {
667 if ($values === NULL) {
668 return NULL;
669 }
670 // If we already have a string, strip $delim off the ends so it doesn't get added twice
671 if (is_string($values)) {
672 $values = trim($values, $delim);
673 }
674 return $delim . implode($delim, (array) $values) . $delim;
675 }
676
677 /**
678 * Modifies a key in an array while preserving the key order.
679 *
680 * By default when an element is added to an array, it is added to the end.
681 * This method allows for changing an existing key while preserving its
682 * position in the array.
683 *
684 * The array is both modified in-place and returned.
685 *
686 * @param array $elementArray
687 * Array to manipulate.
688 * @param string $oldKey
689 * Old key to be replaced.
690 * @param string $newKey
691 * Replacement key string.
692 *
693 * @throws Exception
694 * Throws a generic Exception if $oldKey is not found in $elementArray.
695 *
696 * @return array
697 * The manipulated array.
698 */
699 public static function crmReplaceKey(&$elementArray, $oldKey, $newKey) {
700 $keys = array_keys($elementArray);
701 if (FALSE === $index = array_search($oldKey, $keys)) {
702 throw new Exception(sprintf('key "%s" does not exit', $oldKey));
703 }
704 $keys[$index] = $newKey;
705 $elementArray = array_combine($keys, array_values($elementArray));
706 return $elementArray;
707 }
708
709 /**
710 * Searches array keys by regex, returning the value of the first match.
711 *
712 * Given a regular expression and an array, this method searches the keys
713 * of the array using the regular expression. The first match is then used
714 * to index into the array, and the associated value is retrieved and
715 * returned. If no matches are found, or if something other than an array
716 * is passed, then a default value is returned. Unless otherwise specified,
717 * the default value is NULL.
718 *
719 * @param string $regexKey
720 * The regular expression to use when searching for matching keys.
721 * @param array $list
722 * The array whose keys will be searched.
723 * @param mixed $default
724 * (optional) The default value to return if the regex does not match an
725 * array key, or if something other than an array is passed.
726 *
727 * @return mixed
728 * The value found.
729 */
730 public static function valueByRegexKey($regexKey, $list, $default = NULL) {
731 if (is_array($list) && $regexKey) {
732 $matches = preg_grep($regexKey, array_keys($list));
733 $key = reset($matches);
734 return ($key && array_key_exists($key, $list)) ? $list[$key] : $default;
735 }
736 return $default;
737 }
738
739 /**
740 * Generates the Cartesian product of zero or more vectors.
741 *
742 * @param array $dimensions
743 * List of dimensions to multiply.
744 * Each key is a dimension name; each value is a vector.
745 * @param array $template
746 * (optional) A base set of values included in every output.
747 *
748 * @return array
749 * Each item is a distinct combination of values from $dimensions.
750 *
751 * For example, the product of
752 * {
753 * fg => {red, blue},
754 * bg => {white, black}
755 * }
756 * would be
757 * {
758 * {fg => red, bg => white},
759 * {fg => red, bg => black},
760 * {fg => blue, bg => white},
761 * {fg => blue, bg => black}
762 * }
763 */
764 public static function product($dimensions, $template = array()) {
765 if (empty($dimensions)) {
766 return array($template);
767 }
768
769 foreach ($dimensions as $key => $value) {
770 $firstKey = $key;
771 $firstValues = $value;
772 break;
773 }
774 unset($dimensions[$key]);
775
776 $results = array();
777 foreach ($firstValues as $firstValue) {
778 foreach (self::product($dimensions, $template) as $result) {
779 $result[$firstKey] = $firstValue;
780 $results[] = $result;
781 }
782 }
783
784 return $results;
785 }
786
787 /**
788 * Get the first element of an array.
789 *
790 * @param array $array
791 * @return mixed|NULL
792 */
793 public static function first($array) {
794 foreach ($array as $value) {
795 return $value;
796 }
797 return NULL;
798 }
799
800 /**
801 * Extract any $keys from $array and copy to a new array.
802 *
803 * Note: If a $key does not appear in $array, then it will
804 * not appear in the result.
805 *
806 * @param array $array
807 * @param array $keys
808 * List of keys to copy.
809 * @return array
810 */
811 public static function subset($array, $keys) {
812 $result = array();
813 foreach ($keys as $key) {
814 if (isset($array[$key])) {
815 $result[$key] = $array[$key];
816 }
817 }
818 return $result;
819 }
820
821 /**
822 * Transform an associative array of key=>value pairs into a non-associative array of arrays.
823 * This is necessary to preserve sort order when sending an array through json_encode.
824 *
825 * @param array $associative
826 * @param string $keyName
827 * @param string $valueName
828 * @return array
829 */
830 public static function makeNonAssociative($associative, $keyName = 'key', $valueName = 'value') {
831 $output = array();
832 foreach ($associative as $key => $val) {
833 $output[] = array($keyName => $key, $valueName => $val);
834 }
835 return $output;
836 }
837
838 /**
839 * Diff multidimensional arrays
840 * ( array_diff does not support multidimensional array)
841 *
842 * @param array $array1
843 * @param array $array2
844 * @return array
845 */
846 public static function multiArrayDiff($array1, $array2) {
847 $arrayDiff = array();
848 foreach ($array1 as $mKey => $mValue) {
849 if (array_key_exists($mKey, $array2)) {
850 if (is_array($mValue)) {
851 $recursiveDiff = self::multiArrayDiff($mValue, $array2[$mKey]);
852 if (count($recursiveDiff)) {
853 $arrayDiff[$mKey] = $recursiveDiff;
854 }
855 }
856 else {
857 if ($mValue != $array2[$mKey]) {
858 $arrayDiff[$mKey] = $mValue;
859 }
860 }
861 }
862 else {
863 $arrayDiff[$mKey] = $mValue;
864 }
865 }
866 return $arrayDiff;
867 }
868
869 /**
870 * Given a 2-dimensional matrix, create a new matrix with a restricted list of columns.
871 *
872 * @param array $matrix
873 * All matrix data, as a list of rows.
874 * @param array $columns
875 * List of column names.
876 * @return array
877 */
878 public static function filterColumns($matrix, $columns) {
879 $newRows = array();
880 foreach ($matrix as $pos => $oldRow) {
881 $newRow = array();
882 foreach ($columns as $column) {
883 $newRow[$column] = CRM_Utils_Array::value($column, $oldRow);
884 }
885 $newRows[$pos] = $newRow;
886 }
887 return $newRows;
888 }
889
890 /**
891 * Rewrite the keys in an array by filtering through a function.
892 *
893 * @param array $array
894 * @param callable $func
895 * Function($key, $value). Returns the new key.
896 * @return array
897 */
898 public static function rekey($array, $func) {
899 $result = array();
900 foreach ($array as $key => $value) {
901 $newKey = $func($key, $value);
902 $result[$newKey] = $value;
903 }
904 return $result;
905 }
906
907 /**
908 * Copy all properties of $other into $array (recursively).
909 *
910 * @param array|ArrayAccess $array
911 * @param array $other
912 */
913 public static function extend(&$array, $other) {
914 foreach ($other as $key => $value) {
915 if (is_array($value)) {
916 self::extend($array[$key], $value);
917 }
918 else {
919 $array[$key] = $value;
920 }
921 }
922 }
923
924 /**
925 * Get a single value from an array-tre.
926 *
927 * @param array $arr
928 * Ex: array('foo'=>array('bar'=>123)).
929 * @param array $pathParts
930 * Ex: array('foo',bar').
931 * @return mixed|NULL
932 * Ex 123.
933 */
934 public static function pathGet($arr, $pathParts) {
935 $r = $arr;
936 foreach ($pathParts as $part) {
937 if (!isset($r[$part])) {
938 return NULL;
939 }
940 $r = $r[$part];
941 }
942 return $r;
943 }
944
945 /**
946 * Set a single value in an array tree.
947 *
948 * @param array $arr
949 * Ex: array('foo'=>array('bar'=>123)).
950 * @param array $pathParts
951 * Ex: array('foo',bar').
952 * @param $value
953 * Ex: 456.
954 */
955 public static function pathSet(&$arr, $pathParts, $value) {
956 $r = &$arr;
957 $last = array_pop($pathParts);
958 foreach ($pathParts as $part) {
959 if (!isset($r[$part])) {
960 $r[$part] = array();
961 }
962 $r = &$r[$part];
963 }
964 $r[$last] = $value;
965 }
966
967 /**
968 * Convert array where key(s) holds the actual value and value(s) as 1 into array of actual values
969 * Ex: array('foobar' => 1, 4 => 1) formatted into array('foobar', 4)
970 *
971 * @deprecated use convertCheckboxInputToArray instead (after testing)
972 * https://github.com/civicrm/civicrm-core/pull/8169
973 *
974 * @param array $array
975 */
976 public static function formatArrayKeys(&$array) {
977 $keys = array_keys($array, 1);
978 if (count($keys) > 1 ||
979 (count($keys) == 1 &&
980 (current($keys) > 1 ||
981 is_string(current($keys)) ||
982 (current($keys) == 1 && $array[1] == 1) // handle (0 => 4), (1 => 1)
983 )
984 )
985 ) {
986 $array = $keys;
987 }
988 }
989
990 /**
991 * Convert the data format coming in from checkboxes to an array of values.
992 *
993 * The input format from check boxes looks like
994 * array('value1' => 1, 'value2' => 1). This function converts those values to
995 * array(''value1', 'value2).
996 *
997 * The function will only alter the array if all values are equal to 1.
998 *
999 * @param array $input
1000 *
1001 * @return array
1002 */
1003 public static function convertCheckboxFormatToArray($input) {
1004 if (isset($input[0])) {
1005 return $input;
1006 }
1007 $keys = array_keys($input, 1);
1008 if ((count($keys) == count($input))) {
1009 return $keys;
1010 }
1011 return $input;
1012 }
1013
1014 }