3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
29 * Provides a collection of static methods for array manipulation.
32 * @copyright CiviCRM LLC (c) 2004-2014
34 class CRM_Utils_Array
{
37 * Returns $list[$key] if such element exists, or a default value otherwise.
39 * If $list is not actually an array at all, then the default value is
45 * Key value to look up in the array.
47 * Array from which to look up a value.
48 * @param mixed $default
49 * (optional) Value to return $list[$key] does not exist.
52 * Can return any type, since $list might contain anything.
54 static function value($key, $list, $default = NULL) {
55 if (is_array($list)) {
56 return array_key_exists($key, $list) ?
$list[$key] : $default;
62 * Recursively searches an array for a key, returning the first value found.
64 * If $params[$key] does not exist and $params contains arrays, descend into
65 * each array in a depth-first manner, in array iteration order.
67 * @param array $params
68 * The array to be searched.
70 * The key to search for.
73 * The value of the key, or null if the key is not found.
76 static function retrieveValueRecursive(&$params, $key) {
77 if (!is_array($params)) {
80 elseif ($value = CRM_Utils_Array
::value($key, $params)) {
84 foreach ($params as $subParam) {
85 if (is_array($subParam) &&
86 $value = self
::retrieveValueRecursive($subParam, $key)
96 * Wraps and slightly changes the behavior of PHP's array_search().
98 * This function reproduces the behavior of array_search() from PHP prior to
99 * version 4.2.0, which was to return NULL on failure. This function also
100 * checks that $list is an array before attempting to search it.
104 * @param mixed $value
105 * The value to search for.
107 * The array to be searched.
109 * @return int|string|null
110 * Returns the key, which could be an int or a string, or NULL on failure.
112 static function key($value, &$list) {
113 if (is_array($list)) {
114 $key = array_search($value, $list);
116 // array_search returns key if found, false otherwise
117 // it may return values like 0 or empty string which
118 // evaluates to false
119 // hence we must use identical comparison operator
120 return ($key === FALSE) ?
NULL : $key;
126 * Builds an XML fragment representing an array.
128 * Depending on the nature of the keys of the array (and its sub-arrays,
129 * if any) the XML fragment may not be valid.
132 * The array to be serialized.
134 * (optional) Indentation depth counter.
135 * @param string $seperator
136 * (optional) String to be appended after open/close tags.
141 * XML fragment representing $list.
143 static function &xml(&$list, $depth = 1, $seperator = "\n") {
145 foreach ($list as $name => $value) {
146 $xml .= str_repeat(' ', $depth * 4);
147 if (is_array($value)) {
148 $xml .= "<{$name}>{$seperator}";
149 $xml .= self
::xml($value, $depth +
1, $seperator);
150 $xml .= str_repeat(' ', $depth * 4);
151 $xml .= "</{$name}>{$seperator}";
154 // make sure we escape value
155 $value = self
::escapeXML($value);
156 $xml .= "<{$name}>$value</{$name}>{$seperator}";
163 * Sanitizes a string for serialization in CRM_Utils_Array::xml().
165 * Replaces '&', '<', and '>' with their XML escape sequences. Replaces '^A'
168 * @param string $value
169 * String to be sanitized.
172 * Sanitized version of $value.
174 static function escapeXML($value) {
179 $src = array('&', '<', '>', '\ 1');
180 $dst = array('&', '<', '>', ',');
183 return str_replace($src, $dst, $value);
187 * Converts a nested array to a flat array.
189 * The nested structure is preserved in the string values of the keys of the
192 * Example nested array:
217 * Corresponding flattened array:
223 * [asdf.merp] => bleep
224 * [asdf.quack.0] => 1
225 * [asdf.quack.1] => 2
226 * [asdf.quack.2] => 3
231 * Array to be flattened.
234 * @param string $prefix
235 * (optional) String to prepend to keys.
236 * @param string $seperator
237 * (optional) String that separates the concatenated keys.
241 static function flatten(&$list, &$flat, $prefix = '', $seperator = ".") {
242 foreach ($list as $name => $value) {
243 $newPrefix = ($prefix) ?
$prefix . $seperator . $name : $name;
244 if (is_array($value)) {
245 self
::flatten($value, $flat, $newPrefix, $seperator);
248 if (!empty($value)) {
249 $flat[$newPrefix] = $value;
256 * Converts an array with path-like keys into a tree of arrays.
258 * This function is the inverse of CRM_Utils_Array::flatten().
260 * @param string $delim
263 * A one-dimensional array indexed by string keys
270 function unflatten($delim, &$arr) {
272 foreach ($arr as $key => $value) {
273 $path = explode($delim, $key);
275 while (count($path) > 1) {
276 $key = array_shift($path);
277 if (!isset($node[$key])) {
278 $node[$key] = array();
280 $node = &$node[$key];
283 $key = array_shift($path);
284 $node[$key] = $value;
292 * If $a1[foo] and $a2[foo] both exist and are both arrays, the merge
293 * process recurses into those sub-arrays. If $a1[foo] and $a2[foo] both
294 * exist but they are not both arrays, the value from $a1 overrides the
295 * value from $a2 and the value from $a2 is discarded.
298 * First array to be merged.
300 * Second array to be merged.
306 static function crmArrayMerge($a1, $a2) {
316 foreach ($a1 as $key => $value) {
317 if (array_key_exists($key, $a2) &&
318 is_array($a2[$key]) && is_array($a1[$key])
320 $a3[$key] = array_merge($a1[$key], $a2[$key]);
323 $a3[$key] = $a1[$key];
327 foreach ($a2 as $key => $value) {
328 if (array_key_exists($key, $a1)) {
329 // already handled in above loop
332 $a3[$key] = $a2[$key];
339 * Determines whether an array contains any sub-arrays.
342 * The array to inspect.
345 * True if $list contains at least one sub-array, false otherwise.
348 static function isHierarchical(&$list) {
349 foreach ($list as $n => $v) {
360 * @return bool TRUE if $subset is a subset of $superset
362 static function isSubset($subset, $superset) {
363 foreach ($subset as $expected) {
364 if (!in_array($expected, $superset)) {
372 * Recursively copies all values of an array into a new array.
374 * If the recursion depth limit is exceeded, the deep copy appears to
375 * succeed, but the copy process past the depth limit will be shallow.
377 * @params array $array
379 * @params int $maxdepth
380 * (optional) Recursion depth limit.
382 * (optional) Current recursion depth.
385 * @param int $maxdepth
389 * The new copy of $array.
393 static function array_deep_copy(&$array, $maxdepth = 50, $depth = 0) {
394 if ($depth > $maxdepth) {
398 foreach ($array as $key => $value) {
399 if (is_array($value)) {
400 array_deep_copy($value, $copy[$key], $maxdepth, ++
$depth);
403 $copy[$key] = $value;
410 * Makes a shallow copy of a variable, returning the copy by value.
412 * In some cases, functions return an array by reference, but we really don't
413 * want to receive a reference.
415 * @param $array mixed
416 * Something to return a copy of.
421 static function breakReference($array) {
427 * Removes a portion of an array.
429 * This function is similar to PHP's array_splice(), with some differences:
430 * - Array keys that are not removed are preserved. The PHP built-in
431 * function only preserves values.
432 * - The portion of the array to remove is specified by start and end
433 * index rather than offset and length.
434 * - There is no ability to specify data to replace the removed portion.
436 * The behavior given an associative array would probably not be useful.
438 * @param array $params
439 * Array to manipulate.
441 * First index to remove.
443 * Last index to remove.
447 static function crmArraySplice(&$params, $start, $end) {
448 // verify start and end index
452 if ($end > count($params)) {
453 $end = count($params);
458 // procees unset operation
459 foreach ($params as $key => $value) {
460 if ($i >= $start && $i < $end) {
461 unset($params[$key]);
468 * Searches an array recursively in an optionally case-insensitive manner.
470 * @param string $value
471 * Value to search for.
472 * @param array $params
473 * Array to search within.
474 * @param bool $caseInsensitive
475 * (optional) Whether to search in a case-insensitive manner.
478 * True if $value was found, false otherwise.
482 static function crmInArray($value, $params, $caseInsensitive = TRUE) {
483 foreach ($params as $item) {
484 if (is_array($item)) {
485 $ret = crmInArray($value, $item, $caseInsensitive);
488 $ret = ($caseInsensitive) ?
strtolower($item) == strtolower($value) : $item == $value;
498 * This function is used to convert associative array names to values
501 * This function is used by both the web form layer and the api. Note that
502 * the api needs the name => value conversion, also the view layer typically
503 * requires value => name conversion
505 static function lookupValue(&$defaults, $property, $lookup, $reverse) {
506 $id = $property . '_id';
508 $src = $reverse ?
$property : $id;
509 $dst = $reverse ?
$id : $property;
511 if (!array_key_exists(strtolower($src), array_change_key_case($defaults, CASE_LOWER
))) {
515 $look = $reverse ?
array_flip($lookup) : $lookup;
517 //trim lookup array, ignore . ( fix for CRM-1514 ), eg for prefix/suffix make sure Dr. and Dr both are valid
519 foreach ($look as $k => $v) {
520 $newLook[trim($k, ".")] = $v;
525 if (is_array($look)) {
526 if (!array_key_exists(trim(strtolower($defaults[strtolower($src)]), '.'), array_change_key_case($look, CASE_LOWER
))) {
531 $tempLook = array_change_key_case($look, CASE_LOWER
);
533 $defaults[$dst] = $tempLook[trim(strtolower($defaults[strtolower($src)]), '.')];
538 * Checks whether an array is empty.
540 * An array is empty if its values consist only of NULL and empty sub-arrays.
541 * Containing a non-NULL value or non-empty array makes an array non-empty.
543 * If something other than an array is passed, it is considered to be empty.
545 * If nothing is passed at all, the default value provided is empty.
547 * @param array $array
548 * (optional) Array to be checked for emptiness.
551 * True if the array is empty.
554 static function crmIsEmptyArray($array = array()) {
555 if (!is_array($array)) {
558 foreach ($array as $element) {
559 if (is_array($element)) {
560 if (!self
::crmIsEmptyArray($element)) {
564 elseif (isset($element)) {
572 * Determines the maximum depth of nested arrays in a multidimensional array.
574 * The mechanism for determining depth will be confused if the array
575 * contains keys or values with the left brace '{' character. This will
576 * cause the depth to be over-reported.
578 * @param array $array
579 * The array to examine.
582 * The maximum nested array depth found.
586 static function getLevelsArray($array) {
587 if (!is_array($array)) {
590 $jsonString = json_encode($array);
591 $parts = explode("}", $jsonString);
593 foreach ($parts as $part) {
594 $countLevels = substr_count($part, "{");
595 if ($countLevels > $max) {
603 * Sorts an associative array of arrays by an attribute using strnatcmp().
605 * @param array $array
606 * Array to be sorted.
607 * @param string $field
608 * Name of the attribute used for sorting.
613 static function crmArraySortByField($array, $field) {
614 $code = "return strnatcmp(\$a['$field'], \$b['$field']);";
615 uasort($array, create_function('$a,$b', $code));
620 * Recursively removes duplicate values from a multi-dimensional array.
622 * @param array $array
623 * The input array possibly containing duplicate values.
626 * The input array with duplicate values removed.
628 static function crmArrayUnique($array) {
629 $result = array_map("unserialize", array_unique(array_map("serialize", $array)));
630 foreach ($result as $key => $value) {
631 if (is_array($value)) {
632 $result[$key] = self
::crmArrayUnique($value);
639 * Sorts an array and maintains index association (with localization).
641 * Uses Collate from the PECL "intl" package, if available, for UTF-8
642 * sorting (e.g. list of countries). Otherwise calls PHP's asort().
644 * On Debian/Ubuntu: apt-get install php5-intl
646 * @param array $array
647 * (optional) Array to be sorted.
652 static function asort($array = array()) {
653 $lcMessages = CRM_Utils_System
::getUFLocale();
655 if ($lcMessages && $lcMessages != 'en_US' && class_exists('Collator')) {
656 $collator = new Collator($lcMessages . '.utf8');
657 $collator->asort($array);
660 // This calls PHP's built-in asort().
668 * Unsets an arbitrary list of array elements from an associative array.
670 * @param array $items
671 * The array from which to remove items.
673 * @internal param string|\string[] $key When passed a string, unsets $items[$key].* When passed a string, unsets $items[$key].
674 * When passed an array of strings, unsets $items[$k] for each string $k
677 static function remove(&$items) {
678 foreach (func_get_args() as $n => $key) {
679 // Skip argument 0 ($items) by testing $n for truth.
680 if ($n && is_array($key)) {
681 foreach($key as $k) {
692 * Builds an array-tree which indexes the records in an array.
694 * @param string[] $keys
695 * Properties by which to index.
696 * @param object|array $records
699 * Multi-dimensional array, with one layer for each key.
701 static function index($keys, $records) {
702 $final_key = array_pop($keys);
705 foreach ($records as $record) {
707 foreach ($keys as $key) {
708 if (is_array($record)) {
709 $keyvalue = isset($record[$key]) ?
$record[$key] : NULL;
711 $keyvalue = isset($record->{$key}) ?
$record->{$key} : NULL;
713 if (isset($node[$keyvalue]) && !is_array($node[$keyvalue])) {
714 $node[$keyvalue] = array();
716 $node = &$node[$keyvalue];
718 if (is_array($record)) {
719 $node[ $record[$final_key] ] = $record;
721 $node[ $record->{$final_key} ] = $record;
728 * Iterates over a list of records and returns the value of some property.
730 * @param string $prop
731 * Property to retrieve.
732 * @param array|object $records
736 * Keys are the original keys of $records; values are the $prop values.
738 static function collect($prop, $records) {
740 if (is_array($records)) {
741 foreach ($records as $key => $record) {
742 if (is_object($record)) {
743 $result[$key] = $record->{$prop};
745 $result[$key] = $record[$prop];
753 * Generate a string representation of an array.
755 * @param array $pairs
756 * Array to stringify.
757 * @param string $l1Delim
758 * String to use to separate key/value pairs from one another.
759 * @param string $l2Delim
760 * String to use to separate keys from values within each key/value pair.
765 static function implodeKeyValue($l1Delim, $l2Delim, $pairs) {
767 foreach ($pairs as $key => $value) {
768 $exprs[] = $key . $l2Delim . $value;
770 return implode($l1Delim, $exprs);
774 * Trims delimiters from a string and then splits it using explode().
776 * This method works mostly like PHP's built-in explode(), except that
777 * surrounding delimiters are trimmed before explode() is called.
779 * Also, if an array or NULL is passed as the $values parameter, the value is
780 * returned unmodified rather than being passed to explode().
782 * @param array|null|string $values
783 * The input string (or an array, or NULL).
784 * @param string $delim
785 * (optional) The boundary string.
788 * An array of strings produced by explode(), or the unmodified input
791 static function explodePadded($values, $delim = CRM_Core_DAO
::VALUE_SEPARATOR
) {
792 if ($values === NULL) {
795 // If we already have an array, no need to continue
796 if (is_array($values)) {
799 return explode($delim, trim((string) $values, $delim));
803 * Joins array elements with a string, adding surrounding delimiters.
805 * This method works mostly like PHP's built-in implode(), but the generated
806 * string is surrounded by delimiter characters. Also, if NULL is passed as
807 * the $values parameter, NULL is returned.
809 * @param mixed $values
810 * Array to be imploded. If a non-array is passed, it will be cast to an
812 * @param string $delim
813 * Delimiter to be used for implode() and which will surround the output
816 * @return string|NULL
817 * The generated string, or NULL if NULL was passed as $values parameter.
819 static function implodePadded($values, $delim = CRM_Core_DAO
::VALUE_SEPARATOR
) {
820 if ($values === NULL) {
823 // If we already have a string, strip $delim off the ends so it doesn't get added twice
824 if (is_string($values)) {
825 $values = trim($values, $delim);
827 return $delim . implode($delim, (array) $values) . $delim;
831 * Modifies a key in an array while preserving the key order.
833 * By default when an element is added to an array, it is added to the end.
834 * This method allows for changing an existing key while preserving its
835 * position in the array.
837 * The array is both modified in-place and returned.
839 * @param array $elementArray
840 * Array to manipulate.
841 * @param string $oldKey
842 * Old key to be replaced.
843 * @param string $newKey
844 * Replacement key string.
847 * Throws a generic Exception if $oldKey is not found in $elementArray.
850 * The manipulated array.
852 static function crmReplaceKey(&$elementArray, $oldKey, $newKey) {
853 $keys = array_keys($elementArray);
854 if (FALSE === $index = array_search($oldKey, $keys)) {
855 throw new Exception(sprintf('key "%s" does not exit', $oldKey));
857 $keys[$index] = $newKey;
858 $elementArray = array_combine($keys, array_values($elementArray));
859 return $elementArray;
863 * Searches array keys by regex, returning the value of the first match.
865 * Given a regular expression and an array, this method searches the keys
866 * of the array using the regular expression. The first match is then used
867 * to index into the array, and the associated value is retrieved and
868 * returned. If no matches are found, or if something other than an array
869 * is passed, then a default value is returned. Unless otherwise specified,
870 * the default value is NULL.
872 * @param string $regexKey
873 * The regular expression to use when searching for matching keys.
875 * The array whose keys will be searched.
876 * @param mixed $default
877 * (optional) The default value to return if the regex does not match an
878 * array key, or if something other than an array is passed.
886 * @param null $default
890 static function valueByRegexKey($regexKey, $list, $default = NULL) {
891 if (is_array($list) && $regexKey) {
892 $matches = preg_grep($regexKey, array_keys($list));
893 $key = reset($matches);
894 return ($key && array_key_exists($key, $list)) ?
$list[$key] : $default;
900 * Generates the Cartesian product of zero or more vectors.
902 * @param array $dimensions
903 * List of dimensions to multiply.
904 * Each key is a dimension name; each value is a vector.
905 * @param array $template
906 * (optional) A base set of values included in every output.
909 * Each item is a distinct combination of values from $dimensions.
911 * For example, the product of
914 * bg => {white, black}
918 * {fg => red, bg => white},
919 * {fg => red, bg => black},
920 * {fg => blue, bg => white},
921 * {fg => blue, bg => black}
924 static function product($dimensions, $template = array()) {
925 if (empty($dimensions)) {
926 return array($template);
929 foreach ($dimensions as $key => $value) {
931 $firstValues = $value;
934 unset($dimensions[$key]);
937 foreach ($firstValues as $firstValue) {
938 foreach (self
::product($dimensions, $template) as $result) {
939 $result[$firstKey] = $firstValue;
940 $results[] = $result;
948 * Get the first elemnet of an array
950 * @param array $array
953 static function first($array) {
954 foreach ($array as $value) {