Merge pull request #3516 from jitendrapurohit/CRM-14869
[civicrm-core.git] / CRM / Utils / Array.php
index 648ca4153b424f614209e5e8c2924eccce62c8c8..af0900da1e12e71fe8cf363b082d477612d39bd7 100644 (file)
 */
 
 /**
+ * Provides a collection of static methods for array manipulation.
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2014
- * $Id$
- *
  */
 class CRM_Utils_Array {
 
   /**
-   * if the key exists in the list returns the associated value
+   * Returns $list[$key] if such element exists, or a default value otherwise.
+   *
+   * If $list is not actually an array at all, then the default value is
+   * returned.
    *
    * @access public
    *
-   * @param string $key   the key value
-   * @param array $list  the array to be searched
+   * @param string $key
+   *   Key value to look up in the array.
+   * @param array $list
+   *   Array from which to look up a value.
    * @param mixed $default
+   *   (optional) Value to return $list[$key] does not exist.
    *
-   * @return mixed value if exists else $default
-   * @static
+   * @return mixed
+   *   Can return any type, since $list might contain anything.
    */
   static function value($key, $list, $default = NULL) {
     if (is_array($list)) {
@@ -54,15 +59,19 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Given a parameter array and a key to search for,
-   * search recursively for that key's value.
+   * Recursively searches an array for a key, returning the first value found.
+   *
+   * If $params[$key] does not exist and $params contains arrays, descend into
+   * each array in a depth-first manner, in array iteration order.
    *
-   * @param array $values     The parameter array
-   * @param string $key       The key to search for
+   * @param array $params
+   *   The array to be searched.
+   * @param string $key
+   *   The key to search for.
    *
-   * @return mixed            The value of the key, or null.
+   * @return mixed
+   *   The value of the key, or null if the key is not found.
    * @access public
-   * @static
    */
   static function retrieveValueRecursive(&$params, $key) {
     if (!is_array($params)) {
@@ -84,17 +93,21 @@ class CRM_Utils_Array {
   }
 
   /**
-   * if the value exists in the list returns the associated key
-   *
-   * @access public
+   * Wraps and slightly changes the behavior of PHP's array_search().
    *
-   * @param list  the array to be searched
-   * @param value the search value
+   * This function reproduces the behavior of array_search() from PHP prior to
+   * version 4.2.0, which was to return NULL on failure. This function also
+   * checks that $list is an array before attempting to search it.
    *
-   * @return key if exists else null
-   * @static
    * @access public
    *
+   * @param mixed $value
+   *   The value to search for.
+   * @param array $list
+   *   The array to be searched.
+   *
+   * @return int|string|null
+   *   Returns the key, which could be an int or a string, or NULL on failure.
    */
   static function key($value, &$list) {
     if (is_array($list)) {
@@ -109,6 +122,24 @@ class CRM_Utils_Array {
     return NULL;
   }
 
+  /**
+   * Builds an XML fragment representing an array.
+   *
+   * Depending on the nature of the keys of the array (and its sub-arrays,
+   * if any) the XML fragment may not be valid.
+   *
+   * @param array $list
+   *   The array to be serialized.
+   * @param int $depth
+   *   (optional) Indentation depth counter.
+   * @param string $seperator
+   *   (optional) String to be appended after open/close tags.
+   *
+   * @access public
+   *
+   * @return string
+   *   XML fragment representing $list.
+   */
   static function &xml(&$list, $depth = 1, $seperator = "\n") {
     $xml = '';
     foreach ($list as $name => $value) {
@@ -128,6 +159,18 @@ class CRM_Utils_Array {
     return $xml;
   }
 
+  /**
+   * Sanitizes a string for serialization in CRM_Utils_Array::xml().
+   *
+   * Replaces '&', '<', and '>' with their XML escape sequences. Replaces '^A'
+   * with a comma.
+   *
+   * @param string $value
+   *   String to be sanitized.
+   *
+   * @return string
+   *   Sanitized version of $value.
+   */
   static function escapeXML($value) {
     static $src = NULL;
     static $dst = NULL;
@@ -141,12 +184,59 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Convert an array-tree to a flat array
-   *
-   * @param array $list the original, tree-shaped list
-   * @param array $flat the flat list to which items will be copied
+   * Converts a nested array to a flat array.
+   *
+   * The nested structure is preserved in the string values of the keys of the
+   * flat array.
+   *
+   * Example nested array:
+   * Array
+   * (
+   *     [foo] => Array
+   *         (
+   *             [0] => bar
+   *             [1] => baz
+   *             [2] => 42
+   *         )
+   *
+   *     [asdf] => Array
+   *         (
+   *             [merp] => bleep
+   *             [quack] => Array
+   *                 (
+   *                     [0] => 1
+   *                     [1] => 2
+   *                     [2] => 3
+   *                 )
+   *
+   *         )
+   *
+   *     [quux] => 999
+   * )
+   *
+   * Corresponding flattened array:
+   * Array
+   * (
+   *     [foo.0] => bar
+   *     [foo.1] => baz
+   *     [foo.2] => 42
+   *     [asdf.merp] => bleep
+   *     [asdf.quack.0] => 1
+   *     [asdf.quack.1] => 2
+   *     [asdf.quack.2] => 3
+   *     [quux] => 999
+   * )
+   *
+   * @param array $list
+   *   Array to be flattened.
+   * @param array $flat
+   *   Destination array.
    * @param string $prefix
+   *   (optional) String to prepend to keys.
    * @param string $seperator
+   *   (optional) String that separates the concatenated keys.
+   *
+   * @access public
    */
   static function flatten(&$list, &$flat, $prefix = '', $seperator = ".") {
     foreach ($list as $name => $value) {
@@ -163,12 +253,19 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Convert an array with path-like keys into a tree of arrays
+   * Converts an array with path-like keys into a tree of arrays.
    *
-   * @param $delim A path delimiter
-   * @param $arr A one-dimensional array indexed by string keys
+   * This function is the inverse of CRM_Utils_Array::flatten().
    *
-   * @return array-encoded tree
+   * @param string $delim
+   *   A path delimiter
+   * @param array $arr
+   *   A one-dimensional array indexed by string keys
+   *
+   * @return array
+   *   Array-encoded tree
+   *
+   * @access public
    */
   function unflatten($delim, &$arr) {
     $result = array();
@@ -190,13 +287,21 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Funtion to merge to two arrays recursively
+   * Merges two arrays.
+   *
+   * If $a1[foo] and $a2[foo] both exist and are both arrays, the merge
+   * process recurses into those sub-arrays. If $a1[foo] and $a2[foo] both
+   * exist but they are not both arrays, the value from $a1 overrides the
+   * value from $a2 and the value from $a2 is discarded.
    *
    * @param array $a1
+   *   First array to be merged.
    * @param array $a2
+   *   Second array to be merged.
    *
-   * @return  $a3
-   * @static
+   * @return array
+   *   The merged array.
+   * @access public
    */
   static function crmArrayMerge($a1, $a2) {
     if (empty($a1)) {
@@ -230,6 +335,16 @@ class CRM_Utils_Array {
     return $a3;
   }
 
+  /**
+   * Determines whether an array contains any sub-arrays.
+   *
+   * @param array $list
+   *   The array to inspect.
+   *
+   * @return bool
+   *   True if $list contains at least one sub-array, false otherwise.
+   * @access public
+   */
   static function isHierarchical(&$list) {
     foreach ($list as $n => $v) {
       if (is_array($v)) {
@@ -240,15 +355,39 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Array deep copy
+   * @param $subset
+   * @param $superset
+   * @return bool TRUE if $subset is a subset of $superset
+   */
+  static function isSubset($subset, $superset) {
+    foreach ($subset as $expected) {
+      if (!in_array($expected, $superset)) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Recursively copies all values of an array into a new array.
+   *
+   * If the recursion depth limit is exceeded, the deep copy appears to
+   * succeed, but the copy process past the depth limit will be shallow.
    *
-   * @params  array  $array
-   * @params  int    $maxdepth
-   * @params  int    $depth
+   * @params array $array
+   *   The array to copy.
+   * @params int $maxdepth
+   *   (optional) Recursion depth limit.
+   * @params int $depth
+   *   (optional) Current recursion depth.
    *
-   * @return  array  copy of the array
+   * @param $array
+   * @param int $maxdepth
+   * @param int $depth
+   *
+   * @return array
+   *   The new copy of $array.
    *
-   * @static
    * @access public
    */
   static function array_deep_copy(&$array, $maxdepth = 50, $depth = 0) {
@@ -268,11 +407,16 @@ class CRM_Utils_Array {
   }
 
   /**
+   * Makes a shallow copy of a variable, returning the copy by value.
+   *
    * In some cases, functions return an array by reference, but we really don't
    * want to receive a reference.
    *
-   * @param $array
+   * @param $array mixed
+   *   Something to return a copy of.
    * @return mixed
+   *   The copy.
+   * @access public
    */
   static function breakReference($array) {
     $copy = $array;
@@ -280,19 +424,28 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Array splice function that preserves associative keys
-   * defauly php array_splice function doesnot preserve keys
-   * So specify start and end of the array that you want to remove
+   * Removes a portion of an array.
+   *
+   * This function is similar to PHP's array_splice(), with some differences:
+   *   - Array keys that are not removed are preserved. The PHP built-in
+   *     function only preserves values.
+   *   - The portion of the array to remove is specified by start and end
+   *     index rather than offset and length.
+   *   - There is no ability to specify data to replace the removed portion.
    *
-   * @param  array    $params  array to slice
-   * @param  Integer  $start
-   * @param  Integer  $end
+   * The behavior given an associative array would probably not be useful.
    *
-   * @return  void
-   * @static
+   * @param array $params
+   *   Array to manipulate.
+   * @param int $start
+   *   First index to remove.
+   * @param int $end
+   *   Last index to remove.
+   *
+   * @access public
    */
   static function crmArraySplice(&$params, $start, $end) {
-    // verify start and end date
+    // verify start and end index
     if ($start < 0) {
       $start = 0;
     }
@@ -312,13 +465,19 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Function for case insensitive in_array search
+   * Searches an array recursively in an optionally case-insensitive manner.
+   *
+   * @param string $value
+   *   Value to search for.
+   * @param array $params
+   *   Array to search within.
+   * @param bool $caseInsensitive
+   *   (optional) Whether to search in a case-insensitive manner.
    *
-   * @param $value             value or search string
-   * @param $params            array that need to be searched
-   * @param $caseInsensitive   boolean true or false
+   * @return bool
+   *   True if $value was found, false otherwise.
    *
-   * @static
+   * @access public
    */
   static function crmInArray($value, $params, $caseInsensitive = TRUE) {
     foreach ($params as $item) {
@@ -376,11 +535,21 @@ class CRM_Utils_Array {
   }
 
   /**
-   *  Function to check if give array is empty
-   *  @param array $array array to check for empty condition
+   * Checks whether an array is empty.
+   *
+   * An array is empty if its values consist only of NULL and empty sub-arrays.
+   * Containing a non-NULL value or non-empty array makes an array non-empty.
+   *
+   * If something other than an array is passed, it is considered to be empty.
+   *
+   * If nothing is passed at all, the default value provided is empty.
+   *
+   * @param array $array
+   *   (optional) Array to be checked for emptiness.
    *
-   *  @return boolean true is array is empty else false
-   *  @static
+   * @return boolean
+   *   True if the array is empty.
+   * @access public
    */
   static function crmIsEmptyArray($array = array()) {
     if (!is_array($array)) {
@@ -400,12 +569,19 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Function to determine how many levels in array for multidimensional arrays
+   * Determines the maximum depth of nested arrays in a multidimensional array.
+   *
+   * The mechanism for determining depth will be confused if the array
+   * contains keys or values with the left brace '{' character. This will
+   * cause the depth to be over-reported.
    *
    * @param array $array
+   *   The array to examine.
+   *
+   * @return integer
+   *   The maximum nested array depth found.
    *
-   * @return integer $levels containing number of levels in array
-   * @static
+   * @access public
    */
   static function getLevelsArray($array) {
     if (!is_array($array)) {
@@ -424,13 +600,15 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Function to sort an associative array of arrays by an attribute using natural string compare
+   * Sorts an associative array of arrays by an attribute using strnatcmp().
    *
-   * @param array $array Array to be sorted
-   * @param string $field Name of the attribute you want to sort by
+   * @param array $array
+   *   Array to be sorted.
+   * @param string $field
+   *   Name of the attribute used for sorting.
    *
-   * @return array $array Sorted array
-   * @static
+   * @return array
+   *   Sorted array
    */
   static function crmArraySortByField($array, $field) {
     $code = "return strnatcmp(\$a['$field'], \$b['$field']);";
@@ -439,12 +617,13 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Recursively removes duplicate values from an multi-dimensional array.
+   * Recursively removes duplicate values from a multi-dimensional array.
    *
-   * @param array $array The input array possibly containing duplicate values.
+   * @param array $array
+   *   The input array possibly containing duplicate values.
    *
-   * @return array $array The array with duplicate values removed.
-   * @static
+   * @return array
+   *   The input array with duplicate values removed.
    */
   static function crmArrayUnique($array) {
     $result = array_map("unserialize", array_unique(array_map("serialize", $array)));
@@ -457,14 +636,18 @@ class CRM_Utils_Array {
   }
 
   /**
-   *  Sort an array and maintain index association, use Collate from the
-   *  PECL "intl" package, if available, for UTF-8 sorting (ex: list of countries).
-   *  On Debian/Ubuntu: apt-get install php5-intl
+   * Sorts an array and maintains index association (with localization).
+   *
+   * Uses Collate from the PECL "intl" package, if available, for UTF-8
+   * sorting (e.g. list of countries). Otherwise calls PHP's asort().
    *
-   *  @param array $array array of values
+   * On Debian/Ubuntu: apt-get install php5-intl
    *
-   *  @return  array  Sorted array
-   *  @static
+   * @param array $array
+   *   (optional) Array to be sorted.
+   *
+   * @return array
+   *   Sorted array.
    */
   static function asort($array = array()) {
     $lcMessages = CRM_Utils_System::getUFLocale();
@@ -474,6 +657,7 @@ class CRM_Utils_Array {
       $collator->asort($array);
     }
     else {
+      // This calls PHP's built-in asort().
       asort($array);
     }
 
@@ -481,14 +665,18 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Convenient way to unset a bunch of items from an array
+   * Unsets an arbitrary list of array elements from an associative array.
    *
-   * @param array $items (reference)
-   * @param string/int/array $itemN: other params to this function will be treated as keys
-   * (or arrays of keys) to unset
+   * @param array $items
+   *   The array from which to remove items.
+   *
+   * @internal param string|\string[] $key When passed a string, unsets $items[$key].*   When passed a string, unsets $items[$key].
+   *   When passed an array of strings, unsets $items[$k] for each string $k
+   *   in the array.
    */
    static function remove(&$items) {
      foreach (func_get_args() as $n => $key) {
+       // Skip argument 0 ($items) by testing $n for truth.
        if ($n && is_array($key)) {
          foreach($key as $k) {
            unset($items[$k]);
@@ -501,11 +689,14 @@ class CRM_Utils_Array {
    }
 
   /**
-   * Build an array-tree which indexes the records in an array
+   * Builds an array-tree which indexes the records in an array.
+   *
+   * @param string[] $keys
+   *   Properties by which to index.
+   * @param object|array $records
    *
-   * @param $keys array of string (properties by which to index)
-   * @param $records array of records (objects or assoc-arrays)
-   * @return array; multi-dimensional, with one layer for each key
+   * @return array
+   *   Multi-dimensional array, with one layer for each key.
    */
   static function index($keys, $records) {
     $final_key = array_pop($keys);
@@ -515,9 +706,9 @@ class CRM_Utils_Array {
       $node = &$result;
       foreach ($keys as $key) {
         if (is_array($record)) {
-          $keyvalue = $record[$key];
+          $keyvalue = isset($record[$key]) ? $record[$key] : NULL;
         } else {
-          $keyvalue = $record->{$key};
+          $keyvalue = isset($record->{$key}) ? $record->{$key} : NULL;
         }
         if (isset($node[$keyvalue]) && !is_array($node[$keyvalue])) {
           $node[$keyvalue] = array();
@@ -534,11 +725,15 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Iterate through a list of records and grab the value of some property
+   * Iterates over a list of records and returns the value of some property.
    *
    * @param string $prop
-   * @param array $records a list of records (object|array)
-   * @return array keys are the original keys of $records; values are the $prop values
+   *   Property to retrieve.
+   * @param array|object $records
+   *   A list of records.
+   *
+   * @return array
+   *   Keys are the original keys of $records; values are the $prop values.
    */
   static function collect($prop, $records) {
     $result = array();
@@ -553,11 +748,17 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Given a list of key-value pairs, combine thme into a single string
-   * @param array $pairs e.g. array('a' => '1', 'b' => '2')
-   * @param string $l1Delim e.g. ','
-   * @param string $l2Delim e.g. '='
-   * @return string e.g. 'a=1,b=2'
+   * Generate a string representation of an array.
+   *
+   * @param array $pairs
+   *   Array to stringify.
+   * @param string $l1Delim
+   *   String to use to separate key/value pairs from one another.
+   * @param string $l2Delim
+   *   String to use to separate keys from values within each key/value pair.
+   *
+   * @return string
+   *   Generated string.
    */
   static function implodeKeyValue($l1Delim, $l2Delim, $pairs) {
     $exprs = array();
@@ -568,11 +769,22 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Like explode() but assumes that the $value is padded with $delim on left and right
+   * Trims delimiters from a string and then splits it using explode().
    *
-   * @param mixed $values
+   * This method works mostly like PHP's built-in explode(), except that
+   * surrounding delimiters are trimmed before explode() is called.
+   *
+   * Also, if an array or NULL is passed as the $values parameter, the value is
+   * returned unmodified rather than being passed to explode().
+   *
+   * @param array|null|string $values
+   *   The input string (or an array, or NULL).
    * @param string $delim
-   * @return array|NULL
+   *   (optional) The boundary string.
+   *
+   * @return array|null
+   *   An array of strings produced by explode(), or the unmodified input
+   *   array, or NULL.
    */
   static function explodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) {
     if ($values === NULL) {
@@ -586,11 +798,21 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Like implode() but creates a string that is padded with $delim on left and right
+   * Joins array elements with a string, adding surrounding delimiters.
+   *
+   * This method works mostly like PHP's built-in implode(), but the generated
+   * string is surrounded by delimiter characters. Also, if NULL is passed as
+   * the $values parameter, NULL is returned.
    *
    * @param mixed $values
+   *   Array to be imploded. If a non-array is passed, it will be cast to an
+   *   array.
    * @param string $delim
+   *   Delimiter to be used for implode() and which will surround the output
+   *   string.
+   *
    * @return string|NULL
+   *   The generated string, or NULL if NULL was passed as $values parameter.
    */
   static function implodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) {
     if ($values === NULL) {
@@ -604,14 +826,26 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Function to modify the key in an array without actually changing the order
-   * By default when you add an element it is added at the end
+   * Modifies a key in an array while preserving the key order.
+   *
+   * By default when an element is added to an array, it is added to the end.
+   * This method allows for changing an existing key while preserving its
+   * position in the array.
+   *
+   * The array is both modified in-place and returned.
    *
-   * @param array  $elementArray associated array element
-   * @param string $oldKey       old key
-   * @param string $newKey       new key
+   * @param array $elementArray
+   *   Array to manipulate.
+   * @param string $oldKey
+   *   Old key to be replaced.
+   * @param string $newKey
+   *   Replacement key string.
+   *
+   * @throws Exception
+   *   Throws a generic Exception if $oldKey is not found in $elementArray.
    *
    * @return array
+   *   The manipulated array.
    */
   static function crmReplaceKey(&$elementArray, $oldKey, $newKey) {
     $keys = array_keys($elementArray);
@@ -624,8 +858,32 @@ class CRM_Utils_Array {
   }
 
   /*
-   * function to get value of first matched
-   * regex key element of an array
+   * Searches array keys by regex, returning the value of the first match.
+   *
+   * Given a regular expression and an array, this method searches the keys
+   * of the array using the regular expression. The first match is then used
+   * to index into the array, and the associated value is retrieved and
+   * returned. If no matches are found, or if something other than an array
+   * is passed, then a default value is returned. Unless otherwise specified,
+   * the default value is NULL.
+   *
+   * @param string $regexKey
+   *   The regular expression to use when searching for matching keys.
+   * @param array $list
+   *   The array whose keys will be searched.
+   * @param mixed $default
+   *   (optional) The default value to return if the regex does not match an
+   *   array key, or if something other than an array is passed.
+   *
+   * @return mixed
+   *   The value found.
+   */
+  /**
+   * @param $regexKey
+   * @param $list
+   * @param null $default
+   *
+   * @return null
    */
   static function valueByRegexKey($regexKey, $list, $default = NULL) {
     if (is_array($list) && $regexKey) {
@@ -637,11 +895,16 @@ class CRM_Utils_Array {
   }
 
   /**
-   * Generate the Cartesian product of zero or more vectors
+   * Generates the Cartesian product of zero or more vectors.
+   *
+   * @param array $dimensions
+   *   List of dimensions to multiply.
+   *   Each key is a dimension name; each value is a vector.
+   * @param array $template
+   *   (optional) A base set of values included in every output.
    *
-   * @param array $dimensions list of dimensions to multiply; each key is a dimension name; each value is a vector
-   * @param array $template a base set of values included in every output
-   * @return array each item is a distinct combination of values from $dimensions
+   * @return array
+   *   Each item is a distinct combination of values from $dimensions.
    *
    * For example, the product of
    * {