Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
232624b1 | 4 | | CiviCRM version 4.4 | |
6a488035 TO |
5 | +--------------------------------------------------------------------+ |
6 | | Copyright CiviCRM LLC (c) 2004-2013 | | |
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 | * | |
30 | * @package CRM | |
31 | * @copyright CiviCRM LLC (c) 2004-2013 | |
32 | * $Id$ | |
33 | * | |
34 | */ | |
35 | class CRM_Utils_Array { | |
36 | ||
37 | /** | |
38 | * if the key exists in the list returns the associated value | |
39 | * | |
40 | * @access public | |
41 | * | |
6a488035 | 42 | * @param string $key the key value |
4d63cfde CW |
43 | * @param array $list the array to be searched |
44 | * @param mixed $default | |
6a488035 | 45 | * |
4d63cfde | 46 | * @return mixed value if exists else $default |
6a488035 | 47 | * @static |
6a488035 TO |
48 | */ |
49 | static function value($key, $list, $default = NULL) { | |
50 | if (is_array($list)) { | |
51 | return array_key_exists($key, $list) ? $list[$key] : $default; | |
52 | } | |
53 | return $default; | |
54 | } | |
55 | ||
56 | /** | |
57 | * Given a parameter array and a key to search for, | |
58 | * search recursively for that key's value. | |
59 | * | |
60 | * @param array $values The parameter array | |
61 | * @param string $key The key to search for | |
62 | * | |
63 | * @return mixed The value of the key, or null. | |
64 | * @access public | |
65 | * @static | |
66 | */ | |
67 | static function retrieveValueRecursive(&$params, $key) { | |
68 | if (!is_array($params)) { | |
69 | return NULL; | |
70 | } | |
71 | elseif ($value = CRM_Utils_Array::value($key, $params)) { | |
72 | return $value; | |
73 | } | |
74 | else { | |
75 | foreach ($params as $subParam) { | |
76 | if (is_array($subParam) && | |
77 | $value = self::retrieveValueRecursive($subParam, $key) | |
78 | ) { | |
79 | return $value; | |
80 | } | |
81 | } | |
82 | } | |
83 | return NULL; | |
84 | } | |
85 | ||
86 | /** | |
87 | * if the value exists in the list returns the associated key | |
88 | * | |
89 | * @access public | |
90 | * | |
91 | * @param list the array to be searched | |
92 | * @param value the search value | |
93 | * | |
94 | * @return key if exists else null | |
95 | * @static | |
96 | * @access public | |
97 | * | |
98 | */ | |
99 | static function key($value, &$list) { | |
100 | if (is_array($list)) { | |
101 | $key = array_search($value, $list); | |
102 | ||
103 | // array_search returns key if found, false otherwise | |
104 | // it may return values like 0 or empty string which | |
105 | // evaluates to false | |
106 | // hence we must use identical comparison operator | |
107 | return ($key === FALSE) ? NULL : $key; | |
108 | } | |
109 | return NULL; | |
110 | } | |
111 | ||
112 | static function &xml(&$list, $depth = 1, $seperator = "\n") { | |
113 | $xml = ''; | |
114 | foreach ($list as $name => $value) { | |
115 | $xml .= str_repeat(' ', $depth * 4); | |
116 | if (is_array($value)) { | |
117 | $xml .= "<{$name}>{$seperator}"; | |
118 | $xml .= self::xml($value, $depth + 1, $seperator); | |
119 | $xml .= str_repeat(' ', $depth * 4); | |
120 | $xml .= "</{$name}>{$seperator}"; | |
121 | } | |
122 | else { | |
123 | // make sure we escape value | |
124 | $value = self::escapeXML($value); | |
125 | $xml .= "<{$name}>$value</{$name}>{$seperator}"; | |
126 | } | |
127 | } | |
128 | return $xml; | |
129 | } | |
130 | ||
131 | static function escapeXML($value) { | |
132 | static $src = NULL; | |
133 | static $dst = NULL; | |
134 | ||
135 | if (!$src) { | |
136 | $src = array('&', '<', '>', '\ 1'); | |
137 | $dst = array('&', '<', '>', ','); | |
138 | } | |
139 | ||
140 | return str_replace($src, $dst, $value); | |
141 | } | |
142 | ||
143 | /** | |
144 | * Convert an array-tree to a flat array | |
145 | * | |
146 | * @param array $list the original, tree-shaped list | |
147 | * @param array $flat the flat list to which items will be copied | |
148 | * @param string $prefix | |
149 | * @param string $seperator | |
150 | */ | |
151 | static function flatten(&$list, &$flat, $prefix = '', $seperator = ".") { | |
152 | foreach ($list as $name => $value) { | |
153 | $newPrefix = ($prefix) ? $prefix . $seperator . $name : $name; | |
154 | if (is_array($value)) { | |
155 | self::flatten($value, $flat, $newPrefix, $seperator); | |
156 | } | |
157 | else { | |
158 | if (!empty($value)) { | |
159 | $flat[$newPrefix] = $value; | |
160 | } | |
161 | } | |
162 | } | |
163 | } | |
164 | ||
165 | /** | |
166 | * Convert an array with path-like keys into a tree of arrays | |
167 | * | |
168 | * @param $delim A path delimiter | |
169 | * @param $arr A one-dimensional array indexed by string keys | |
170 | * | |
171 | * @return array-encoded tree | |
172 | */ | |
173 | function unflatten($delim, &$arr) { | |
174 | $result = array(); | |
175 | foreach ($arr as $key => $value) { | |
176 | $path = explode($delim, $key); | |
177 | $node = &$result; | |
178 | while (count($path) > 1) { | |
179 | $key = array_shift($path); | |
180 | if (!isset($node[$key])) { | |
181 | $node[$key] = array(); | |
182 | } | |
183 | $node = &$node[$key]; | |
184 | } | |
185 | // last part of path | |
186 | $key = array_shift($path); | |
187 | $node[$key] = $value; | |
188 | } | |
189 | return $result; | |
190 | } | |
191 | ||
192 | /** | |
193 | * Funtion to merge to two arrays recursively | |
194 | * | |
195 | * @param array $a1 | |
196 | * @param array $a2 | |
197 | * | |
198 | * @return $a3 | |
199 | * @static | |
200 | */ | |
201 | static function crmArrayMerge($a1, $a2) { | |
202 | if (empty($a1)) { | |
203 | return $a2; | |
204 | } | |
205 | ||
206 | if (empty($a2)) { | |
207 | return $a1; | |
208 | } | |
209 | ||
210 | $a3 = array(); | |
211 | foreach ($a1 as $key => $value) { | |
212 | if (array_key_exists($key, $a2) && | |
213 | is_array($a2[$key]) && is_array($a1[$key]) | |
214 | ) { | |
215 | $a3[$key] = array_merge($a1[$key], $a2[$key]); | |
216 | } | |
217 | else { | |
218 | $a3[$key] = $a1[$key]; | |
219 | } | |
220 | } | |
221 | ||
222 | foreach ($a2 as $key => $value) { | |
223 | if (array_key_exists($key, $a1)) { | |
224 | // already handled in above loop | |
225 | continue; | |
226 | } | |
227 | $a3[$key] = $a2[$key]; | |
228 | } | |
229 | ||
230 | return $a3; | |
231 | } | |
232 | ||
233 | static function isHierarchical(&$list) { | |
234 | foreach ($list as $n => $v) { | |
235 | if (is_array($v)) { | |
236 | return TRUE; | |
237 | } | |
238 | } | |
239 | return FALSE; | |
240 | } | |
241 | ||
242 | /** | |
243 | * Array deep copy | |
244 | * | |
245 | * @params array $array | |
246 | * @params int $maxdepth | |
247 | * @params int $depth | |
248 | * | |
249 | * @return array copy of the array | |
250 | * | |
251 | * @static | |
252 | * @access public | |
253 | */ | |
254 | static function array_deep_copy(&$array, $maxdepth = 50, $depth = 0) { | |
255 | if ($depth > $maxdepth) { | |
256 | return $array; | |
257 | } | |
258 | $copy = array(); | |
259 | foreach ($array as $key => $value) { | |
260 | if (is_array($value)) { | |
261 | array_deep_copy($value, $copy[$key], $maxdepth, ++$depth); | |
262 | } | |
263 | else { | |
264 | $copy[$key] = $value; | |
265 | } | |
266 | } | |
267 | return $copy; | |
268 | } | |
269 | ||
270 | /** | |
271 | * In some cases, functions return an array by reference, but we really don't | |
272 | * want to receive a reference. | |
273 | * | |
274 | * @param $array | |
275 | * @return mixed | |
276 | */ | |
277 | static function breakReference($array) { | |
278 | $copy = $array; | |
279 | return $copy; | |
280 | } | |
281 | ||
282 | /** | |
283 | * Array splice function that preserves associative keys | |
284 | * defauly php array_splice function doesnot preserve keys | |
285 | * So specify start and end of the array that you want to remove | |
286 | * | |
287 | * @param array $params array to slice | |
288 | * @param Integer $start | |
289 | * @param Integer $end | |
290 | * | |
291 | * @return void | |
292 | * @static | |
293 | */ | |
294 | static function crmArraySplice(&$params, $start, $end) { | |
295 | // verify start and end date | |
296 | if ($start < 0) { | |
297 | $start = 0; | |
298 | } | |
299 | if ($end > count($params)) { | |
300 | $end = count($params); | |
301 | } | |
302 | ||
303 | $i = 0; | |
304 | ||
305 | // procees unset operation | |
306 | foreach ($params as $key => $value) { | |
307 | if ($i >= $start && $i < $end) { | |
308 | unset($params[$key]); | |
309 | } | |
310 | $i++; | |
311 | } | |
312 | } | |
313 | ||
314 | /** | |
315 | * Function for case insensitive in_array search | |
316 | * | |
317 | * @param $value value or search string | |
318 | * @param $params array that need to be searched | |
319 | * @param $caseInsensitive boolean true or false | |
320 | * | |
321 | * @static | |
322 | */ | |
323 | static function crmInArray($value, $params, $caseInsensitive = TRUE) { | |
324 | foreach ($params as $item) { | |
325 | if (is_array($item)) { | |
326 | $ret = crmInArray($value, $item, $caseInsensitive); | |
327 | } | |
328 | else { | |
329 | $ret = ($caseInsensitive) ? strtolower($item) == strtolower($value) : $item == $value; | |
330 | if ($ret) { | |
331 | return $ret; | |
332 | } | |
333 | } | |
334 | } | |
335 | return FALSE; | |
336 | } | |
337 | ||
338 | /** | |
339 | * This function is used to convert associative array names to values | |
340 | * and vice-versa. | |
341 | * | |
342 | * This function is used by both the web form layer and the api. Note that | |
343 | * the api needs the name => value conversion, also the view layer typically | |
344 | * requires value => name conversion | |
345 | */ | |
346 | static function lookupValue(&$defaults, $property, $lookup, $reverse) { | |
347 | $id = $property . '_id'; | |
348 | ||
349 | $src = $reverse ? $property : $id; | |
350 | $dst = $reverse ? $id : $property; | |
351 | ||
352 | if (!array_key_exists(strtolower($src), array_change_key_case($defaults, CASE_LOWER))) { | |
353 | return FALSE; | |
354 | } | |
355 | ||
356 | $look = $reverse ? array_flip($lookup) : $lookup; | |
357 | ||
358 | //trim lookup array, ignore . ( fix for CRM-1514 ), eg for prefix/suffix make sure Dr. and Dr both are valid | |
359 | $newLook = array(); | |
360 | foreach ($look as $k => $v) { | |
361 | $newLook[trim($k, ".")] = $v; | |
362 | } | |
363 | ||
364 | $look = $newLook; | |
365 | ||
366 | if (is_array($look)) { | |
367 | if (!array_key_exists(trim(strtolower($defaults[strtolower($src)]), '.'), array_change_key_case($look, CASE_LOWER))) { | |
368 | return FALSE; | |
369 | } | |
370 | } | |
371 | ||
372 | $tempLook = array_change_key_case($look, CASE_LOWER); | |
373 | ||
374 | $defaults[$dst] = $tempLook[trim(strtolower($defaults[strtolower($src)]), '.')]; | |
375 | return TRUE; | |
376 | } | |
377 | ||
378 | /** | |
379 | * Function to check if give array is empty | |
6ce01b02 | 380 | * @param array $array array to check for empty condition |
6a488035 TO |
381 | * |
382 | * @return boolean true is array is empty else false | |
383 | * @static | |
384 | */ | |
6ce01b02 | 385 | static function crmIsEmptyArray($array = array()) { |
6a488035 TO |
386 | if (!is_array($array)) { |
387 | return TRUE; | |
388 | } | |
389 | foreach ($array as $element) { | |
390 | if (is_array($element)) { | |
391 | if (!self::crmIsEmptyArray($element)) { | |
392 | return FALSE; | |
393 | } | |
394 | } | |
395 | elseif (isset($element)) { | |
396 | return FALSE; | |
397 | } | |
398 | } | |
399 | return TRUE; | |
400 | } | |
401 | ||
402 | /** | |
403 | * Function to determine how many levels in array for multidimensional arrays | |
404 | * | |
405 | * @param array $array | |
406 | * | |
407 | * @return integer $levels containing number of levels in array | |
408 | * @static | |
409 | */ | |
410 | static function getLevelsArray($array) { | |
411 | if (!is_array($array)) { | |
412 | return 0; | |
413 | } | |
414 | $jsonString = json_encode($array); | |
415 | $parts = explode("}", $jsonString); | |
416 | $max = 0; | |
417 | foreach ($parts as $part) { | |
418 | $countLevels = substr_count($part, "{"); | |
419 | if ($countLevels > $max) { | |
420 | $max = $countLevels; | |
421 | } | |
422 | } | |
423 | return $max; | |
424 | } | |
425 | ||
426 | /** | |
427 | * Function to sort an associative array of arrays by an attribute using natural string compare | |
428 | * | |
429 | * @param array $array Array to be sorted | |
430 | * @param string $field Name of the attribute you want to sort by | |
431 | * | |
432 | * @return array $array Sorted array | |
433 | * @static | |
434 | */ | |
435 | static function crmArraySortByField($array, $field) { | |
436 | $code = "return strnatcmp(\$a['$field'], \$b['$field']);"; | |
437 | uasort($array, create_function('$a,$b', $code)); | |
438 | return $array; | |
439 | } | |
440 | ||
441 | /** | |
442 | * Recursively removes duplicate values from an multi-dimensional array. | |
443 | * | |
444 | * @param array $array The input array possibly containing duplicate values. | |
445 | * | |
446 | * @return array $array The array with duplicate values removed. | |
447 | * @static | |
448 | */ | |
449 | static function crmArrayUnique($array) { | |
450 | $result = array_map("unserialize", array_unique(array_map("serialize", $array))); | |
451 | foreach ($result as $key => $value) { | |
452 | if (is_array($value)) { | |
453 | $result[$key] = self::crmArrayUnique($value); | |
454 | } | |
455 | } | |
456 | return $result; | |
457 | } | |
458 | ||
459 | /** | |
460 | * Sort an array and maintain index association, use Collate from the | |
461 | * PECL "intl" package, if available, for UTF-8 sorting (ex: list of countries). | |
462 | * On Debian/Ubuntu: apt-get install php5-intl | |
463 | * | |
464 | * @param array $array array of values | |
465 | * | |
466 | * @return array Sorted array | |
467 | * @static | |
468 | */ | |
469 | static function asort($array = array()) { | |
470 | $lcMessages = CRM_Utils_System::getUFLocale(); | |
471 | ||
472 | if ($lcMessages && $lcMessages != 'en_US' && class_exists('Collator')) { | |
473 | $collator = new Collator($lcMessages . '.utf8'); | |
474 | $collator->asort($array); | |
475 | } | |
476 | else { | |
477 | asort($array); | |
478 | } | |
479 | ||
480 | return $array; | |
481 | } | |
482 | ||
483 | /** | |
484 | * Convenient way to unset a bunch of items from an array | |
485 | * | |
486 | * @param array $items (reference) | |
6ce01b02 CW |
487 | * @param string/int/array $itemN: other params to this function will be treated as keys |
488 | * (or arrays of keys) to unset | |
6a488035 TO |
489 | */ |
490 | static function remove(&$items) { | |
491 | foreach (func_get_args() as $n => $key) { | |
492 | if ($n && is_array($key)) { | |
493 | foreach($key as $k) { | |
494 | unset($items[$k]); | |
495 | } | |
496 | } | |
497 | elseif ($n) { | |
498 | unset($items[$key]); | |
499 | } | |
500 | } | |
501 | } | |
502 | ||
503 | /** | |
504 | * Build an array-tree which indexes the records in an array | |
505 | * | |
506 | * @param $keys array of string (properties by which to index) | |
507 | * @param $records array of records (objects or assoc-arrays) | |
508 | * @return array; multi-dimensional, with one layer for each key | |
509 | */ | |
510 | static function index($keys, $records) { | |
511 | $final_key = array_pop($keys); | |
512 | ||
513 | $result = array(); | |
514 | foreach ($records as $record) { | |
515 | $node = &$result; | |
516 | foreach ($keys as $key) { | |
517 | if (is_array($record)) { | |
518 | $keyvalue = $record[$key]; | |
519 | } else { | |
520 | $keyvalue = $record->{$key}; | |
521 | } | |
522 | if (!is_array($node[$keyvalue])) { | |
523 | $node[$keyvalue] = array(); | |
524 | } | |
525 | $node = &$node[$keyvalue]; | |
526 | } | |
527 | if (is_array($record)) { | |
528 | $node[ $record[$final_key] ] = $record; | |
529 | } else { | |
530 | $node[ $record->{$final_key} ] = $record; | |
531 | } | |
532 | } | |
533 | return $result; | |
534 | } | |
535 | ||
536 | /** | |
537 | * Iterate through a list of records and grab the value of some property | |
538 | * | |
539 | * @param string $prop | |
540 | * @param array $records a list of records (object|array) | |
541 | * @return array keys are the original keys of $records; values are the $prop values | |
542 | */ | |
543 | static function collect($prop, $records) { | |
544 | $result = array(); | |
545 | foreach ($records as $key => $record) { | |
546 | if (is_object($record)) { | |
547 | $result[$key] = $record->{$prop}; | |
548 | } else { | |
549 | $result[$key] = $record[$prop]; | |
550 | } | |
551 | } | |
552 | return $result; | |
553 | } | |
554 | ||
555 | /** | |
556 | * Given a list of key-value pairs, combine thme into a single string | |
557 | * @param array $pairs e.g. array('a' => '1', 'b' => '2') | |
558 | * @param string $l1Delim e.g. ',' | |
559 | * @param string $l2Delim e.g. '=' | |
560 | * @return string e.g. 'a=1,b=2' | |
561 | */ | |
562 | static function implodeKeyValue($l1Delim, $l2Delim, $pairs) { | |
563 | $exprs = array(); | |
564 | foreach ($pairs as $key => $value) { | |
565 | $exprs[] = $key . $l2Delim . $value; | |
566 | } | |
567 | return implode($l1Delim, $exprs); | |
568 | } | |
569 | ||
570 | /** | |
571 | * Like explode() but assumes that the $value is padded with $delim on left and right | |
572 | * | |
fe18a93c | 573 | * @param mixed $values |
6a488035 TO |
574 | * @param string $delim |
575 | * @return array|NULL | |
576 | */ | |
fe18a93c CW |
577 | static function explodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) { |
578 | if ($values === NULL) { | |
6a488035 TO |
579 | return NULL; |
580 | } | |
fe18a93c CW |
581 | // If we already have an array, no need to continue |
582 | if (is_array($values)) { | |
583 | return $values; | |
584 | } | |
585 | return explode($delim, trim((string) $values, $delim)); | |
6a488035 TO |
586 | } |
587 | ||
588 | /** | |
fe18a93c | 589 | * Like implode() but creates a string that is padded with $delim on left and right |
6a488035 | 590 | * |
fe18a93c | 591 | * @param mixed $values |
6a488035 | 592 | * @param string $delim |
fe18a93c | 593 | * @return string|NULL |
6a488035 TO |
594 | */ |
595 | static function implodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) { | |
596 | if ($values === NULL) { | |
597 | return NULL; | |
598 | } | |
fe18a93c CW |
599 | // If we already have a string, strip $delim off the ends so it doesn't get added twice |
600 | if (is_string($values)) { | |
601 | $values = trim($values, $delim); | |
602 | } | |
603 | return $delim . implode($delim, (array) $values) . $delim; | |
6a488035 TO |
604 | } |
605 | ||
606 | /** | |
607 | * Function to modify the key in an array without actually changing the order | |
608 | * By default when you add an element it is added at the end | |
609 | * | |
610 | * @param array $elementArray associated array element | |
611 | * @param string $oldKey old key | |
612 | * @param string $newKey new key | |
613 | * | |
614 | * @return array | |
615 | */ | |
616 | static function crmReplaceKey(&$elementArray, $oldKey, $newKey) { | |
617 | $keys = array_keys($elementArray); | |
618 | if (FALSE === $index = array_search($oldKey, $keys)) { | |
619 | throw new Exception(sprintf('key "%s" does not exit', $oldKey)); | |
620 | } | |
621 | $keys[$index] = $newKey; | |
622 | $elementArray = array_combine($keys, array_values($elementArray)); | |
623 | return $elementArray; | |
624 | } | |
bd7b39e7 PJ |
625 | |
626 | /* | |
627 | * function to get value of first matched | |
628 | * regex key element of an array | |
629 | */ | |
630 | static function valueByRegexKey($regexKey, $list, $default = NULL) { | |
631 | if (is_array($list) && $regexKey) { | |
632 | $matches = preg_grep($regexKey, array_keys($list)); | |
633 | $key = reset($matches); | |
634 | return ($key && array_key_exists($key, $list)) ? $list[$key] : $default; | |
635 | } | |
636 | return $default; | |
637 | } | |
6a488035 TO |
638 | } |
639 |