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