Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
ac302523 | 13 | * Provides a collection of static methods for array manipulation. |
6a488035 TO |
14 | * |
15 | * @package CRM | |
ca5cec67 | 16 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
17 | */ |
18 | class CRM_Utils_Array { | |
19 | ||
48872a57 TO |
20 | /** |
21 | * Cast a value to an array. | |
22 | * | |
23 | * This is similar to PHP's `(array)`, but it also converts iterators. | |
24 | * | |
25 | * @param mixed $value | |
26 | * @return array | |
27 | */ | |
28 | public static function cast($value) { | |
29 | if (is_array($value)) { | |
30 | return $value; | |
31 | } | |
32 | if ($value instanceof CRM_Utils_LazyArray || $value instanceof ArrayObject) { | |
33 | // iterator_to_array() would work here, but getArrayCopy() doesn't require actual iterations. | |
34 | return $value->getArrayCopy(); | |
35 | } | |
36 | if (is_iterable($value)) { | |
37 | return iterator_to_array($value); | |
38 | } | |
39 | if (is_scalar($value)) { | |
40 | return [$value]; | |
41 | } | |
42 | throw new \RuntimeException(sprintf("Cannot cast %s to array", gettype($value))); | |
43 | } | |
44 | ||
6a488035 | 45 | /** |
c9e15d2a RS |
46 | * Returns $list[$key] if such element exists, or a default value otherwise. |
47 | * | |
48 | * If $list is not actually an array at all, then the default value is | |
42d24af6 | 49 | * returned. We hope to deprecate this behaviour. |
6a488035 | 50 | * |
6a488035 | 51 | * |
c9e15d2a RS |
52 | * @param string $key |
53 | * Key value to look up in the array. | |
42d24af6 | 54 | * @param array|ArrayAccess $list |
c9e15d2a | 55 | * Array from which to look up a value. |
4d63cfde | 56 | * @param mixed $default |
c9e15d2a | 57 | * (optional) Value to return $list[$key] does not exist. |
6a488035 | 58 | * |
c9e15d2a RS |
59 | * @return mixed |
60 | * Can return any type, since $list might contain anything. | |
6a488035 | 61 | */ |
00be9182 | 62 | public static function value($key, $list, $default = NULL) { |
6a488035 TO |
63 | if (is_array($list)) { |
64 | return array_key_exists($key, $list) ? $list[$key] : $default; | |
65 | } | |
42d24af6 | 66 | if ($list instanceof ArrayAccess) { |
d3049090 RLAR |
67 | // ArrayAccess requires offsetExists is implemented for the equivalent to array_key_exists. |
68 | return $list->offsetExists($key) ? $list[$key] : $default; | |
42d24af6 | 69 | } |
70 | // @todo - eliminate these from core & uncomment this line. | |
71 | // CRM_Core_Error::deprecatedFunctionWarning('You have passed an invalid parameter for the "list"'); | |
6a488035 TO |
72 | return $default; |
73 | } | |
74 | ||
75 | /** | |
c9e15d2a | 76 | * Recursively searches an array for a key, returning the first value found. |
6a488035 | 77 | * |
c9e15d2a RS |
78 | * If $params[$key] does not exist and $params contains arrays, descend into |
79 | * each array in a depth-first manner, in array iteration order. | |
6a488035 | 80 | * |
c9e15d2a RS |
81 | * @param array $params |
82 | * The array to be searched. | |
83 | * @param string $key | |
84 | * The key to search for. | |
85 | * | |
86 | * @return mixed | |
87 | * The value of the key, or null if the key is not found. | |
6a488035 | 88 | */ |
f062e02d | 89 | public static function retrieveValueRecursive(array $params, string $key) { |
44e2789f | 90 | // Note that !empty means funky handling for 0 |
91 | // but it is 'baked in'. We should probably deprecate this | |
92 | // for a more logical approach. | |
93 | // see https://github.com/civicrm/civicrm-core/pull/19478#issuecomment-785388559 | |
94 | if (!empty($params[$key])) { | |
f062e02d | 95 | return $params[$key]; |
6a488035 | 96 | } |
f062e02d | 97 | foreach ($params as $subParam) { |
98 | if (is_array($subParam) && | |
99 | // @todo - this will mishandle values like 0 and false | |
100 | // but it's a little scary to fix. | |
101 | $value = self::retrieveValueRecursive($subParam, $key) | |
102 | ) { | |
103 | return $value; | |
6a488035 TO |
104 | } |
105 | } | |
106 | return NULL; | |
107 | } | |
108 | ||
490565d0 CW |
109 | /** |
110 | * Recursively searches through a given array for all matches | |
111 | * | |
112 | * @param $collection | |
113 | * @param $predicate | |
114 | * @return array | |
115 | */ | |
116 | public static function findAll($collection, $predicate) { | |
117 | $results = []; | |
118 | $search = function($collection) use (&$search, &$results, $predicate) { | |
119 | if (is_array($collection)) { | |
120 | if (is_callable($predicate)) { | |
121 | if ($predicate($collection)) { | |
122 | $results[] = $collection; | |
123 | } | |
124 | } | |
125 | elseif (is_array($predicate)) { | |
126 | if (count(array_intersect_assoc($collection, $predicate)) === count($predicate)) { | |
127 | $results[] = $collection; | |
128 | } | |
129 | } | |
130 | else { | |
131 | if (array_key_exists($predicate, $collection)) { | |
132 | $results[] = $collection; | |
133 | } | |
134 | } | |
135 | foreach ($collection as $item) { | |
136 | $search($item); | |
137 | } | |
138 | } | |
139 | }; | |
140 | $search($collection); | |
141 | return $results; | |
142 | } | |
143 | ||
6a488035 | 144 | /** |
c9e15d2a | 145 | * Wraps and slightly changes the behavior of PHP's array_search(). |
6a488035 | 146 | * |
c9e15d2a RS |
147 | * This function reproduces the behavior of array_search() from PHP prior to |
148 | * version 4.2.0, which was to return NULL on failure. This function also | |
149 | * checks that $list is an array before attempting to search it. | |
6a488035 | 150 | * |
6a488035 | 151 | * |
c9e15d2a RS |
152 | * @param mixed $value |
153 | * The value to search for. | |
154 | * @param array $list | |
155 | * The array to be searched. | |
156 | * | |
157 | * @return int|string|null | |
158 | * Returns the key, which could be an int or a string, or NULL on failure. | |
6a488035 | 159 | */ |
04f72de8 | 160 | public static function key($value, $list) { |
6a488035 TO |
161 | if (is_array($list)) { |
162 | $key = array_search($value, $list); | |
163 | ||
164 | // array_search returns key if found, false otherwise | |
165 | // it may return values like 0 or empty string which | |
166 | // evaluates to false | |
167 | // hence we must use identical comparison operator | |
168 | return ($key === FALSE) ? NULL : $key; | |
169 | } | |
170 | return NULL; | |
171 | } | |
172 | ||
c9e15d2a RS |
173 | /** |
174 | * Builds an XML fragment representing an array. | |
175 | * | |
176 | * Depending on the nature of the keys of the array (and its sub-arrays, | |
177 | * if any) the XML fragment may not be valid. | |
178 | * | |
179 | * @param array $list | |
180 | * The array to be serialized. | |
181 | * @param int $depth | |
182 | * (optional) Indentation depth counter. | |
f0fed404 | 183 | * @param string $separator |
c9e15d2a RS |
184 | * (optional) String to be appended after open/close tags. |
185 | * | |
c9e15d2a RS |
186 | * @return string |
187 | * XML fragment representing $list. | |
188 | */ | |
f0fed404 | 189 | public static function &xml(&$list, $depth = 1, $separator = "\n") { |
6a488035 TO |
190 | $xml = ''; |
191 | foreach ($list as $name => $value) { | |
192 | $xml .= str_repeat(' ', $depth * 4); | |
193 | if (is_array($value)) { | |
f0fed404 JF |
194 | $xml .= "<{$name}>{$separator}"; |
195 | $xml .= self::xml($value, $depth + 1, $separator); | |
6a488035 | 196 | $xml .= str_repeat(' ', $depth * 4); |
f0fed404 | 197 | $xml .= "</{$name}>{$separator}"; |
6a488035 TO |
198 | } |
199 | else { | |
200 | // make sure we escape value | |
201 | $value = self::escapeXML($value); | |
f0fed404 | 202 | $xml .= "<{$name}>$value</{$name}>{$separator}"; |
6a488035 TO |
203 | } |
204 | } | |
205 | return $xml; | |
206 | } | |
207 | ||
c9e15d2a RS |
208 | /** |
209 | * Sanitizes a string for serialization in CRM_Utils_Array::xml(). | |
210 | * | |
211 | * Replaces '&', '<', and '>' with their XML escape sequences. Replaces '^A' | |
212 | * with a comma. | |
213 | * | |
214 | * @param string $value | |
215 | * String to be sanitized. | |
216 | * | |
217 | * @return string | |
218 | * Sanitized version of $value. | |
219 | */ | |
00be9182 | 220 | public static function escapeXML($value) { |
6a488035 TO |
221 | static $src = NULL; |
222 | static $dst = NULL; | |
223 | ||
224 | if (!$src) { | |
be2fb01f CW |
225 | $src = ['&', '<', '>', '\ 1']; |
226 | $dst = ['&', '<', '>', ',']; | |
6a488035 TO |
227 | } |
228 | ||
229 | return str_replace($src, $dst, $value); | |
230 | } | |
231 | ||
232 | /** | |
c9e15d2a RS |
233 | * Converts a nested array to a flat array. |
234 | * | |
235 | * The nested structure is preserved in the string values of the keys of the | |
236 | * flat array. | |
237 | * | |
238 | * Example nested array: | |
239 | * Array | |
240 | * ( | |
241 | * [foo] => Array | |
242 | * ( | |
243 | * [0] => bar | |
244 | * [1] => baz | |
245 | * [2] => 42 | |
246 | * ) | |
f4aaa82a | 247 | * |
c9e15d2a RS |
248 | * [asdf] => Array |
249 | * ( | |
250 | * [merp] => bleep | |
251 | * [quack] => Array | |
252 | * ( | |
253 | * [0] => 1 | |
254 | * [1] => 2 | |
255 | * [2] => 3 | |
256 | * ) | |
f4aaa82a | 257 | * |
c9e15d2a | 258 | * ) |
f4aaa82a | 259 | * |
c9e15d2a RS |
260 | * [quux] => 999 |
261 | * ) | |
f4aaa82a | 262 | * |
c9e15d2a RS |
263 | * Corresponding flattened array: |
264 | * Array | |
265 | * ( | |
266 | * [foo.0] => bar | |
267 | * [foo.1] => baz | |
268 | * [foo.2] => 42 | |
269 | * [asdf.merp] => bleep | |
270 | * [asdf.quack.0] => 1 | |
271 | * [asdf.quack.1] => 2 | |
272 | * [asdf.quack.2] => 3 | |
273 | * [quux] => 999 | |
274 | * ) | |
275 | * | |
276 | * @param array $list | |
277 | * Array to be flattened. | |
278 | * @param array $flat | |
279 | * Destination array. | |
6a488035 | 280 | * @param string $prefix |
c9e15d2a | 281 | * (optional) String to prepend to keys. |
f0fed404 | 282 | * @param string $separator |
c9e15d2a | 283 | * (optional) String that separates the concatenated keys. |
6a488035 | 284 | */ |
f0fed404 | 285 | public static function flatten(&$list, &$flat, $prefix = '', $separator = ".") { |
6a488035 | 286 | foreach ($list as $name => $value) { |
f0fed404 | 287 | $newPrefix = ($prefix) ? $prefix . $separator . $name : $name; |
6a488035 | 288 | if (is_array($value)) { |
f0fed404 | 289 | self::flatten($value, $flat, $newPrefix, $separator); |
6a488035 TO |
290 | } |
291 | else { | |
941077af | 292 | $flat[$newPrefix] = $value; |
6a488035 TO |
293 | } |
294 | } | |
295 | } | |
296 | ||
297 | /** | |
c9e15d2a RS |
298 | * Converts an array with path-like keys into a tree of arrays. |
299 | * | |
300 | * This function is the inverse of CRM_Utils_Array::flatten(). | |
301 | * | |
302 | * @param string $delim | |
303 | * A path delimiter | |
304 | * @param array $arr | |
305 | * A one-dimensional array indexed by string keys | |
6a488035 | 306 | * |
c9e15d2a RS |
307 | * @return array |
308 | * Array-encoded tree | |
6a488035 | 309 | */ |
00be9182 | 310 | public function unflatten($delim, &$arr) { |
be2fb01f | 311 | $result = []; |
6a488035 TO |
312 | foreach ($arr as $key => $value) { |
313 | $path = explode($delim, $key); | |
314 | $node = &$result; | |
315 | while (count($path) > 1) { | |
316 | $key = array_shift($path); | |
317 | if (!isset($node[$key])) { | |
be2fb01f | 318 | $node[$key] = []; |
6a488035 TO |
319 | } |
320 | $node = &$node[$key]; | |
321 | } | |
322 | // last part of path | |
323 | $key = array_shift($path); | |
324 | $node[$key] = $value; | |
325 | } | |
326 | return $result; | |
327 | } | |
328 | ||
329 | /** | |
c9e15d2a RS |
330 | * Merges two arrays. |
331 | * | |
332 | * If $a1[foo] and $a2[foo] both exist and are both arrays, the merge | |
333 | * process recurses into those sub-arrays. If $a1[foo] and $a2[foo] both | |
334 | * exist but they are not both arrays, the value from $a1 overrides the | |
335 | * value from $a2 and the value from $a2 is discarded. | |
6a488035 TO |
336 | * |
337 | * @param array $a1 | |
c9e15d2a | 338 | * First array to be merged. |
6a488035 | 339 | * @param array $a2 |
c9e15d2a | 340 | * Second array to be merged. |
6a488035 | 341 | * |
c9e15d2a RS |
342 | * @return array |
343 | * The merged array. | |
6a488035 | 344 | */ |
00be9182 | 345 | public static function crmArrayMerge($a1, $a2) { |
6a488035 TO |
346 | if (empty($a1)) { |
347 | return $a2; | |
348 | } | |
349 | ||
350 | if (empty($a2)) { | |
351 | return $a1; | |
352 | } | |
353 | ||
be2fb01f | 354 | $a3 = []; |
6a488035 TO |
355 | foreach ($a1 as $key => $value) { |
356 | if (array_key_exists($key, $a2) && | |
357 | is_array($a2[$key]) && is_array($a1[$key]) | |
358 | ) { | |
359 | $a3[$key] = array_merge($a1[$key], $a2[$key]); | |
360 | } | |
361 | else { | |
362 | $a3[$key] = $a1[$key]; | |
363 | } | |
364 | } | |
365 | ||
366 | foreach ($a2 as $key => $value) { | |
367 | if (array_key_exists($key, $a1)) { | |
368 | // already handled in above loop | |
369 | continue; | |
370 | } | |
371 | $a3[$key] = $a2[$key]; | |
372 | } | |
373 | ||
374 | return $a3; | |
375 | } | |
376 | ||
c9e15d2a RS |
377 | /** |
378 | * Determines whether an array contains any sub-arrays. | |
379 | * | |
380 | * @param array $list | |
381 | * The array to inspect. | |
382 | * | |
383 | * @return bool | |
384 | * True if $list contains at least one sub-array, false otherwise. | |
c9e15d2a | 385 | */ |
00be9182 | 386 | public static function isHierarchical(&$list) { |
6a488035 TO |
387 | foreach ($list as $n => $v) { |
388 | if (is_array($v)) { | |
389 | return TRUE; | |
390 | } | |
391 | } | |
392 | return FALSE; | |
393 | } | |
394 | ||
82376c19 | 395 | /** |
54957108 | 396 | * Is array A a subset of array B. |
397 | * | |
398 | * @param array $subset | |
399 | * @param array $superset | |
400 | * | |
a6c01b45 CW |
401 | * @return bool |
402 | * TRUE if $subset is a subset of $superset | |
82376c19 | 403 | */ |
00be9182 | 404 | public static function isSubset($subset, $superset) { |
82376c19 TO |
405 | foreach ($subset as $expected) { |
406 | if (!in_array($expected, $superset)) { | |
407 | return FALSE; | |
408 | } | |
409 | } | |
410 | return TRUE; | |
411 | } | |
412 | ||
6a488035 | 413 | /** |
c9e15d2a | 414 | * Searches an array recursively in an optionally case-insensitive manner. |
6a488035 | 415 | * |
c9e15d2a RS |
416 | * @param string $value |
417 | * Value to search for. | |
418 | * @param array $params | |
419 | * Array to search within. | |
420 | * @param bool $caseInsensitive | |
421 | * (optional) Whether to search in a case-insensitive manner. | |
6a488035 | 422 | * |
c9e15d2a RS |
423 | * @return bool |
424 | * True if $value was found, false otherwise. | |
6a488035 | 425 | */ |
00be9182 | 426 | public static function crmInArray($value, $params, $caseInsensitive = TRUE) { |
6a488035 TO |
427 | foreach ($params as $item) { |
428 | if (is_array($item)) { | |
48b7f669 | 429 | $ret = self::crmInArray($value, $item, $caseInsensitive); |
6a488035 TO |
430 | } |
431 | else { | |
432 | $ret = ($caseInsensitive) ? strtolower($item) == strtolower($value) : $item == $value; | |
433 | if ($ret) { | |
434 | return $ret; | |
435 | } | |
436 | } | |
437 | } | |
438 | return FALSE; | |
439 | } | |
440 | ||
441 | /** | |
54957108 | 442 | * Convert associative array names to values and vice-versa. |
6a488035 | 443 | * |
8ff43cf2 | 444 | * This function is used by by import functions and some webforms. |
54957108 | 445 | * |
446 | * @param array $defaults | |
447 | * @param string $property | |
448 | * @param $lookup | |
449 | * @param $reverse | |
450 | * | |
451 | * @return bool | |
6a488035 | 452 | */ |
00be9182 | 453 | public static function lookupValue(&$defaults, $property, $lookup, $reverse) { |
6a488035 TO |
454 | $id = $property . '_id'; |
455 | ||
456 | $src = $reverse ? $property : $id; | |
457 | $dst = $reverse ? $id : $property; | |
458 | ||
459 | if (!array_key_exists(strtolower($src), array_change_key_case($defaults, CASE_LOWER))) { | |
460 | return FALSE; | |
461 | } | |
462 | ||
463 | $look = $reverse ? array_flip($lookup) : $lookup; | |
464 | ||
50bfb460 | 465 | // trim lookup array, ignore . ( fix for CRM-1514 ), eg for prefix/suffix make sure Dr. and Dr both are valid |
be2fb01f | 466 | $newLook = []; |
6a488035 TO |
467 | foreach ($look as $k => $v) { |
468 | $newLook[trim($k, ".")] = $v; | |
469 | } | |
470 | ||
471 | $look = $newLook; | |
472 | ||
473 | if (is_array($look)) { | |
474 | if (!array_key_exists(trim(strtolower($defaults[strtolower($src)]), '.'), array_change_key_case($look, CASE_LOWER))) { | |
475 | return FALSE; | |
476 | } | |
477 | } | |
478 | ||
479 | $tempLook = array_change_key_case($look, CASE_LOWER); | |
480 | ||
481 | $defaults[$dst] = $tempLook[trim(strtolower($defaults[strtolower($src)]), '.')]; | |
482 | return TRUE; | |
483 | } | |
484 | ||
485 | /** | |
c9e15d2a RS |
486 | * Checks whether an array is empty. |
487 | * | |
488 | * An array is empty if its values consist only of NULL and empty sub-arrays. | |
489 | * Containing a non-NULL value or non-empty array makes an array non-empty. | |
490 | * | |
491 | * If something other than an array is passed, it is considered to be empty. | |
492 | * | |
493 | * If nothing is passed at all, the default value provided is empty. | |
494 | * | |
495 | * @param array $array | |
496 | * (optional) Array to be checked for emptiness. | |
6a488035 | 497 | * |
d5cc0fc2 | 498 | * @return bool |
c9e15d2a | 499 | * True if the array is empty. |
6a488035 | 500 | */ |
be2fb01f | 501 | public static function crmIsEmptyArray($array = []) { |
6a488035 TO |
502 | if (!is_array($array)) { |
503 | return TRUE; | |
504 | } | |
505 | foreach ($array as $element) { | |
506 | if (is_array($element)) { | |
507 | if (!self::crmIsEmptyArray($element)) { | |
508 | return FALSE; | |
509 | } | |
510 | } | |
511 | elseif (isset($element)) { | |
512 | return FALSE; | |
513 | } | |
514 | } | |
515 | return TRUE; | |
516 | } | |
517 | ||
6a488035 | 518 | /** |
c9e15d2a | 519 | * Sorts an associative array of arrays by an attribute using strnatcmp(). |
6a488035 | 520 | * |
c9e15d2a RS |
521 | * @param array $array |
522 | * Array to be sorted. | |
6db70618 | 523 | * @param string|array $field |
c9e15d2a | 524 | * Name of the attribute used for sorting. |
6a488035 | 525 | * |
f4aaa82a | 526 | * @return array |
c9e15d2a | 527 | * Sorted array |
6a488035 | 528 | */ |
00be9182 | 529 | public static function crmArraySortByField($array, $field) { |
6db70618 TO |
530 | $fields = (array) $field; |
531 | uasort($array, function ($a, $b) use ($fields) { | |
532 | foreach ($fields as $f) { | |
533 | $v = strnatcmp($a[$f], $b[$f]); | |
534 | if ($v !== 0) { | |
535 | return $v; | |
536 | } | |
537 | } | |
538 | return 0; | |
539 | }); | |
6a488035 TO |
540 | return $array; |
541 | } | |
542 | ||
543 | /** | |
c9e15d2a | 544 | * Recursively removes duplicate values from a multi-dimensional array. |
6a488035 | 545 | * |
c9e15d2a RS |
546 | * @param array $array |
547 | * The input array possibly containing duplicate values. | |
6a488035 | 548 | * |
f4aaa82a | 549 | * @return array |
c9e15d2a | 550 | * The input array with duplicate values removed. |
6a488035 | 551 | */ |
00be9182 | 552 | public static function crmArrayUnique($array) { |
6a488035 TO |
553 | $result = array_map("unserialize", array_unique(array_map("serialize", $array))); |
554 | foreach ($result as $key => $value) { | |
555 | if (is_array($value)) { | |
556 | $result[$key] = self::crmArrayUnique($value); | |
557 | } | |
558 | } | |
559 | return $result; | |
560 | } | |
561 | ||
562 | /** | |
c9e15d2a RS |
563 | * Sorts an array and maintains index association (with localization). |
564 | * | |
565 | * Uses Collate from the PECL "intl" package, if available, for UTF-8 | |
566 | * sorting (e.g. list of countries). Otherwise calls PHP's asort(). | |
6a488035 | 567 | * |
c9e15d2a | 568 | * On Debian/Ubuntu: apt-get install php5-intl |
6a488035 | 569 | * |
c9e15d2a RS |
570 | * @param array $array |
571 | * (optional) Array to be sorted. | |
572 | * | |
f4aaa82a | 573 | * @return array |
c9e15d2a | 574 | * Sorted array. |
6a488035 | 575 | */ |
be2fb01f | 576 | public static function asort($array = []) { |
6a488035 TO |
577 | $lcMessages = CRM_Utils_System::getUFLocale(); |
578 | ||
579 | if ($lcMessages && $lcMessages != 'en_US' && class_exists('Collator')) { | |
580 | $collator = new Collator($lcMessages . '.utf8'); | |
581 | $collator->asort($array); | |
582 | } | |
583 | else { | |
c9e15d2a | 584 | // This calls PHP's built-in asort(). |
6a488035 TO |
585 | asort($array); |
586 | } | |
587 | ||
588 | return $array; | |
589 | } | |
590 | ||
591 | /** | |
c9e15d2a RS |
592 | * Unsets an arbitrary list of array elements from an associative array. |
593 | * | |
594 | * @param array $items | |
595 | * The array from which to remove items. | |
f4aaa82a | 596 | * |
fa0ededf CW |
597 | * Additional params: |
598 | * When passed a string, unsets $items[$key]. | |
599 | * When passed an array of strings, unsets $items[$k] for each string $k in the array. | |
6a488035 | 600 | */ |
e7292422 TO |
601 | public static function remove(&$items) { |
602 | foreach (func_get_args() as $n => $key) { | |
603 | // Skip argument 0 ($items) by testing $n for truth. | |
604 | if ($n && is_array($key)) { | |
22e263ad | 605 | foreach ($key as $k) { |
e7292422 TO |
606 | unset($items[$k]); |
607 | } | |
608 | } | |
609 | elseif ($n) { | |
610 | unset($items[$key]); | |
611 | } | |
612 | } | |
613 | } | |
6a488035 TO |
614 | |
615 | /** | |
c9e15d2a | 616 | * Builds an array-tree which indexes the records in an array. |
6a488035 | 617 | * |
c9e15d2a RS |
618 | * @param string[] $keys |
619 | * Properties by which to index. | |
620 | * @param object|array $records | |
621 | * | |
622 | * @return array | |
623 | * Multi-dimensional array, with one layer for each key. | |
6a488035 | 624 | */ |
00be9182 | 625 | public static function index($keys, $records) { |
6a488035 TO |
626 | $final_key = array_pop($keys); |
627 | ||
be2fb01f | 628 | $result = []; |
6a488035 TO |
629 | foreach ($records as $record) { |
630 | $node = &$result; | |
631 | foreach ($keys as $key) { | |
632 | if (is_array($record)) { | |
2e1f50d6 | 633 | $keyvalue = $record[$key] ?? NULL; |
0db6c3e1 TO |
634 | } |
635 | else { | |
2e1f50d6 | 636 | $keyvalue = $record->{$key} ?? NULL; |
6a488035 | 637 | } |
17d4d611 | 638 | if (isset($node[$keyvalue]) && !is_array($node[$keyvalue])) { |
be2fb01f | 639 | $node[$keyvalue] = []; |
6a488035 TO |
640 | } |
641 | $node = &$node[$keyvalue]; | |
642 | } | |
643 | if (is_array($record)) { | |
e7292422 | 644 | $node[$record[$final_key]] = $record; |
0db6c3e1 TO |
645 | } |
646 | else { | |
e7292422 | 647 | $node[$record->{$final_key}] = $record; |
6a488035 TO |
648 | } |
649 | } | |
650 | return $result; | |
651 | } | |
652 | ||
653 | /** | |
c9e15d2a | 654 | * Iterates over a list of records and returns the value of some property. |
6a488035 TO |
655 | * |
656 | * @param string $prop | |
c9e15d2a RS |
657 | * Property to retrieve. |
658 | * @param array|object $records | |
659 | * A list of records. | |
660 | * | |
661 | * @return array | |
662 | * Keys are the original keys of $records; values are the $prop values. | |
6a488035 | 663 | */ |
00be9182 | 664 | public static function collect($prop, $records) { |
be2fb01f | 665 | $result = []; |
4d34fcfa | 666 | if (is_array($records)) { |
667 | foreach ($records as $key => $record) { | |
668 | if (is_object($record)) { | |
669 | $result[$key] = $record->{$prop}; | |
0db6c3e1 TO |
670 | } |
671 | else { | |
5836c35a | 672 | $result[$key] = self::value($prop, $record); |
4d34fcfa | 673 | } |
6a488035 TO |
674 | } |
675 | } | |
676 | return $result; | |
677 | } | |
678 | ||
2c6fe88a TO |
679 | /** |
680 | * Iterates over a list of objects and executes some method on each. | |
681 | * | |
682 | * Comparison: | |
683 | * - This is like array_map(), except it executes the objects' method | |
684 | * instead of a free-form callable. | |
685 | * - This is like Array::collect(), except it uses a method | |
686 | * instead of a property. | |
687 | * | |
688 | * @param string $method | |
689 | * The method to execute. | |
690 | * @param array|Traversable $objects | |
691 | * A list of objects. | |
692 | * @param array $args | |
693 | * An optional list of arguments to pass to the method. | |
694 | * | |
695 | * @return array | |
696 | * Keys are the original keys of $objects; values are the method results. | |
697 | */ | |
be2fb01f CW |
698 | public static function collectMethod($method, $objects, $args = []) { |
699 | $result = []; | |
2c6fe88a TO |
700 | if (is_array($objects)) { |
701 | foreach ($objects as $key => $object) { | |
be2fb01f | 702 | $result[$key] = call_user_func_array([$object, $method], $args); |
2c6fe88a TO |
703 | } |
704 | } | |
705 | return $result; | |
706 | } | |
707 | ||
6a488035 | 708 | /** |
c9e15d2a | 709 | * Trims delimiters from a string and then splits it using explode(). |
6a488035 | 710 | * |
c9e15d2a RS |
711 | * This method works mostly like PHP's built-in explode(), except that |
712 | * surrounding delimiters are trimmed before explode() is called. | |
713 | * | |
714 | * Also, if an array or NULL is passed as the $values parameter, the value is | |
715 | * returned unmodified rather than being passed to explode(). | |
716 | * | |
717 | * @param array|null|string $values | |
718 | * The input string (or an array, or NULL). | |
6a488035 | 719 | * @param string $delim |
c9e15d2a RS |
720 | * (optional) The boundary string. |
721 | * | |
722 | * @return array|null | |
723 | * An array of strings produced by explode(), or the unmodified input | |
724 | * array, or NULL. | |
6a488035 | 725 | */ |
00be9182 | 726 | public static function explodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) { |
fe18a93c | 727 | if ($values === NULL) { |
6a488035 TO |
728 | return NULL; |
729 | } | |
fe18a93c CW |
730 | // If we already have an array, no need to continue |
731 | if (is_array($values)) { | |
732 | return $values; | |
733 | } | |
62bcdea6 CW |
734 | // Empty string -> empty array |
735 | if ($values === '') { | |
be2fb01f | 736 | return []; |
62bcdea6 | 737 | } |
fe18a93c | 738 | return explode($delim, trim((string) $values, $delim)); |
6a488035 TO |
739 | } |
740 | ||
741 | /** | |
c9e15d2a RS |
742 | * Joins array elements with a string, adding surrounding delimiters. |
743 | * | |
744 | * This method works mostly like PHP's built-in implode(), but the generated | |
745 | * string is surrounded by delimiter characters. Also, if NULL is passed as | |
746 | * the $values parameter, NULL is returned. | |
6a488035 | 747 | * |
fe18a93c | 748 | * @param mixed $values |
c9e15d2a RS |
749 | * Array to be imploded. If a non-array is passed, it will be cast to an |
750 | * array. | |
6a488035 | 751 | * @param string $delim |
c9e15d2a RS |
752 | * Delimiter to be used for implode() and which will surround the output |
753 | * string. | |
754 | * | |
fe18a93c | 755 | * @return string|NULL |
c9e15d2a | 756 | * The generated string, or NULL if NULL was passed as $values parameter. |
6a488035 | 757 | */ |
00be9182 | 758 | public static function implodePadded($values, $delim = CRM_Core_DAO::VALUE_SEPARATOR) { |
6a488035 TO |
759 | if ($values === NULL) { |
760 | return NULL; | |
761 | } | |
fe18a93c CW |
762 | // If we already have a string, strip $delim off the ends so it doesn't get added twice |
763 | if (is_string($values)) { | |
764 | $values = trim($values, $delim); | |
765 | } | |
766 | return $delim . implode($delim, (array) $values) . $delim; | |
6a488035 TO |
767 | } |
768 | ||
769 | /** | |
c9e15d2a RS |
770 | * Modifies a key in an array while preserving the key order. |
771 | * | |
772 | * By default when an element is added to an array, it is added to the end. | |
773 | * This method allows for changing an existing key while preserving its | |
774 | * position in the array. | |
775 | * | |
776 | * The array is both modified in-place and returned. | |
777 | * | |
778 | * @param array $elementArray | |
779 | * Array to manipulate. | |
780 | * @param string $oldKey | |
781 | * Old key to be replaced. | |
782 | * @param string $newKey | |
783 | * Replacement key string. | |
6a488035 | 784 | * |
c9e15d2a RS |
785 | * @throws Exception |
786 | * Throws a generic Exception if $oldKey is not found in $elementArray. | |
6a488035 TO |
787 | * |
788 | * @return array | |
c9e15d2a | 789 | * The manipulated array. |
6a488035 | 790 | */ |
00be9182 | 791 | public static function crmReplaceKey(&$elementArray, $oldKey, $newKey) { |
6a488035 TO |
792 | $keys = array_keys($elementArray); |
793 | if (FALSE === $index = array_search($oldKey, $keys)) { | |
794 | throw new Exception(sprintf('key "%s" does not exit', $oldKey)); | |
795 | } | |
796 | $keys[$index] = $newKey; | |
797 | $elementArray = array_combine($keys, array_values($elementArray)); | |
798 | return $elementArray; | |
799 | } | |
bd7b39e7 | 800 | |
d424ffde | 801 | /** |
c9e15d2a RS |
802 | * Searches array keys by regex, returning the value of the first match. |
803 | * | |
804 | * Given a regular expression and an array, this method searches the keys | |
805 | * of the array using the regular expression. The first match is then used | |
806 | * to index into the array, and the associated value is retrieved and | |
807 | * returned. If no matches are found, or if something other than an array | |
808 | * is passed, then a default value is returned. Unless otherwise specified, | |
809 | * the default value is NULL. | |
810 | * | |
811 | * @param string $regexKey | |
812 | * The regular expression to use when searching for matching keys. | |
813 | * @param array $list | |
814 | * The array whose keys will be searched. | |
815 | * @param mixed $default | |
816 | * (optional) The default value to return if the regex does not match an | |
817 | * array key, or if something other than an array is passed. | |
818 | * | |
819 | * @return mixed | |
820 | * The value found. | |
bd7b39e7 | 821 | */ |
00be9182 | 822 | public static function valueByRegexKey($regexKey, $list, $default = NULL) { |
bd7b39e7 PJ |
823 | if (is_array($list) && $regexKey) { |
824 | $matches = preg_grep($regexKey, array_keys($list)); | |
825 | $key = reset($matches); | |
826 | return ($key && array_key_exists($key, $list)) ? $list[$key] : $default; | |
827 | } | |
828 | return $default; | |
829 | } | |
17deafad TO |
830 | |
831 | /** | |
c9e15d2a | 832 | * Generates the Cartesian product of zero or more vectors. |
17deafad | 833 | * |
c9e15d2a RS |
834 | * @param array $dimensions |
835 | * List of dimensions to multiply. | |
836 | * Each key is a dimension name; each value is a vector. | |
837 | * @param array $template | |
838 | * (optional) A base set of values included in every output. | |
839 | * | |
840 | * @return array | |
841 | * Each item is a distinct combination of values from $dimensions. | |
17deafad | 842 | * |
d5cc0fc2 | 843 | * For example, the product of |
844 | * { | |
17deafad TO |
845 | * fg => {red, blue}, |
846 | * bg => {white, black} | |
d5cc0fc2 | 847 | * } |
848 | * would be | |
849 | * { | |
17deafad TO |
850 | * {fg => red, bg => white}, |
851 | * {fg => red, bg => black}, | |
852 | * {fg => blue, bg => white}, | |
853 | * {fg => blue, bg => black} | |
d5cc0fc2 | 854 | * } |
17deafad | 855 | */ |
be2fb01f | 856 | public static function product($dimensions, $template = []) { |
17deafad | 857 | if (empty($dimensions)) { |
be2fb01f | 858 | return [$template]; |
17deafad TO |
859 | } |
860 | ||
861 | foreach ($dimensions as $key => $value) { | |
862 | $firstKey = $key; | |
863 | $firstValues = $value; | |
864 | break; | |
865 | } | |
866 | unset($dimensions[$key]); | |
867 | ||
be2fb01f | 868 | $results = []; |
17deafad TO |
869 | foreach ($firstValues as $firstValue) { |
870 | foreach (self::product($dimensions, $template) as $result) { | |
871 | $result[$firstKey] = $firstValue; | |
872 | $results[] = $result; | |
873 | } | |
874 | } | |
875 | ||
876 | return $results; | |
877 | } | |
eb20fbf0 TO |
878 | |
879 | /** | |
fe482240 | 880 | * Get the first element of an array. |
eb20fbf0 TO |
881 | * |
882 | * @param array $array | |
883 | * @return mixed|NULL | |
884 | */ | |
00be9182 | 885 | public static function first($array) { |
eb20fbf0 TO |
886 | foreach ($array as $value) { |
887 | return $value; | |
888 | } | |
889 | return NULL; | |
890 | } | |
768c558c TO |
891 | |
892 | /** | |
893 | * Extract any $keys from $array and copy to a new array. | |
894 | * | |
895 | * Note: If a $key does not appear in $array, then it will | |
896 | * not appear in the result. | |
897 | * | |
898 | * @param array $array | |
77855840 TO |
899 | * @param array $keys |
900 | * List of keys to copy. | |
768c558c TO |
901 | * @return array |
902 | */ | |
00be9182 | 903 | public static function subset($array, $keys) { |
be2fb01f | 904 | $result = []; |
768c558c TO |
905 | foreach ($keys as $key) { |
906 | if (isset($array[$key])) { | |
907 | $result[$key] = $array[$key]; | |
908 | } | |
909 | } | |
910 | return $result; | |
911 | } | |
a335f6b2 | 912 | |
b7ceb253 CW |
913 | /** |
914 | * Transform an associative array of key=>value pairs into a non-associative array of arrays. | |
915 | * This is necessary to preserve sort order when sending an array through json_encode. | |
916 | * | |
917 | * @param array $associative | |
dde482e0 | 918 | * Ex: ['foo' => 'bar']. |
b7ceb253 | 919 | * @param string $keyName |
dde482e0 | 920 | * Ex: 'key'. |
b7ceb253 | 921 | * @param string $valueName |
dde482e0 | 922 | * Ex: 'value'. |
b7ceb253 | 923 | * @return array |
dde482e0 | 924 | * Ex: [0 => ['key' => 'foo', 'value' => 'bar']]. |
b7ceb253 | 925 | */ |
00be9182 | 926 | public static function makeNonAssociative($associative, $keyName = 'key', $valueName = 'value') { |
be2fb01f | 927 | $output = []; |
b7ceb253 | 928 | foreach ($associative as $key => $val) { |
be2fb01f | 929 | $output[] = [$keyName => $key, $valueName => $val]; |
b7ceb253 CW |
930 | } |
931 | return $output; | |
932 | } | |
96025800 | 933 | |
06d253f5 | 934 | /** |
935 | * Diff multidimensional arrays | |
50bfb460 | 936 | * (array_diff does not support multidimensional array) |
06d253f5 | 937 | * |
938 | * @param array $array1 | |
939 | * @param array $array2 | |
940 | * @return array | |
941 | */ | |
942 | public static function multiArrayDiff($array1, $array2) { | |
be2fb01f | 943 | $arrayDiff = []; |
06d253f5 | 944 | foreach ($array1 as $mKey => $mValue) { |
945 | if (array_key_exists($mKey, $array2)) { | |
946 | if (is_array($mValue)) { | |
947 | $recursiveDiff = self::multiArrayDiff($mValue, $array2[$mKey]); | |
948 | if (count($recursiveDiff)) { | |
949 | $arrayDiff[$mKey] = $recursiveDiff; | |
950 | } | |
951 | } | |
952 | else { | |
953 | if ($mValue != $array2[$mKey]) { | |
954 | $arrayDiff[$mKey] = $mValue; | |
955 | } | |
956 | } | |
957 | } | |
958 | else { | |
959 | $arrayDiff[$mKey] = $mValue; | |
960 | } | |
961 | } | |
962 | return $arrayDiff; | |
963 | } | |
5793f1d9 | 964 | |
5bc7c754 TO |
965 | /** |
966 | * Given a 2-dimensional matrix, create a new matrix with a restricted list of columns. | |
967 | * | |
968 | * @param array $matrix | |
969 | * All matrix data, as a list of rows. | |
970 | * @param array $columns | |
971 | * List of column names. | |
972 | * @return array | |
973 | */ | |
974 | public static function filterColumns($matrix, $columns) { | |
be2fb01f | 975 | $newRows = []; |
5bc7c754 | 976 | foreach ($matrix as $pos => $oldRow) { |
be2fb01f | 977 | $newRow = []; |
5bc7c754 | 978 | foreach ($columns as $column) { |
9c1bc317 | 979 | $newRow[$column] = $oldRow[$column] ?? NULL; |
5bc7c754 TO |
980 | } |
981 | $newRows[$pos] = $newRow; | |
982 | } | |
983 | return $newRows; | |
984 | } | |
985 | ||
988664a6 TO |
986 | /** |
987 | * Rotate a matrix, converting from row-oriented array to a column-oriented array. | |
988 | * | |
989 | * @param iterable $rows | |
990 | * Ex: [['a'=>10,'b'=>'11'], ['a'=>20,'b'=>21]] | |
991 | * Formula: [scalar $rowId => [scalar $colId => mixed $value]] | |
992 | * @param bool $unique | |
993 | * Only return unique values. | |
994 | * @return array | |
995 | * Ex: ['a'=>[10,20], 'b'=>[11,21]] | |
996 | * Formula: [scalar $colId => [scalar $rowId => mixed $value]] | |
997 | * Note: In unique mode, the $rowId is not meaningful. | |
998 | */ | |
999 | public static function asColumns(iterable $rows, bool $unique = FALSE) { | |
1000 | $columns = []; | |
1001 | foreach ($rows as $rowKey => $row) { | |
1002 | foreach ($row as $columnKey => $value) { | |
1003 | if (FALSE === $unique) { | |
1004 | $columns[$columnKey][$rowKey] = $value; | |
1005 | } | |
1006 | elseif (!in_array($value, $columns[$columnKey] ?? [])) { | |
1007 | $columns[$columnKey][] = $value; | |
1008 | } | |
1009 | } | |
1010 | } | |
1011 | return $columns; | |
1012 | } | |
1013 | ||
952d7eb1 | 1014 | /** |
69f9c562 | 1015 | * Rewrite the keys in an array. |
952d7eb1 TO |
1016 | * |
1017 | * @param array $array | |
69f9c562 CW |
1018 | * @param string|callable $indexBy |
1019 | * Either the value to key by, or a function($key, $value) that returns the new key. | |
952d7eb1 TO |
1020 | * @return array |
1021 | */ | |
69f9c562 | 1022 | public static function rekey($array, $indexBy) { |
be2fb01f | 1023 | $result = []; |
952d7eb1 | 1024 | foreach ($array as $key => $value) { |
69f9c562 | 1025 | $newKey = is_callable($indexBy) ? $indexBy($key, $value) : $value[$indexBy]; |
952d7eb1 TO |
1026 | $result[$newKey] = $value; |
1027 | } | |
1028 | return $result; | |
1029 | } | |
1030 | ||
4f24efcc TO |
1031 | /** |
1032 | * Copy all properties of $other into $array (recursively). | |
1033 | * | |
1034 | * @param array|ArrayAccess $array | |
1035 | * @param array $other | |
1036 | */ | |
1037 | public static function extend(&$array, $other) { | |
1038 | foreach ($other as $key => $value) { | |
1039 | if (is_array($value)) { | |
1040 | self::extend($array[$key], $value); | |
1041 | } | |
1042 | else { | |
1043 | $array[$key] = $value; | |
1044 | } | |
1045 | } | |
1046 | } | |
1047 | ||
393f41dd | 1048 | /** |
d6f1584f | 1049 | * Get a single value from an array-tree. |
393f41dd | 1050 | * |
d6f1584f CW |
1051 | * @param array $values |
1052 | * Ex: ['foo' => ['bar' => 123]]. | |
1053 | * @param array $path | |
1054 | * Ex: ['foo', 'bar']. | |
1055 | * @param mixed $default | |
1056 | * @return mixed | |
393f41dd TO |
1057 | * Ex 123. |
1058 | */ | |
d6f1584f CW |
1059 | public static function pathGet($values, $path, $default = NULL) { |
1060 | foreach ($path as $key) { | |
1061 | if (!is_array($values) || !isset($values[$key])) { | |
1062 | return $default; | |
393f41dd | 1063 | } |
d6f1584f | 1064 | $values = $values[$key]; |
393f41dd | 1065 | } |
d6f1584f CW |
1066 | return $values; |
1067 | } | |
1068 | ||
1069 | /** | |
1070 | * Check if a key isset which may be several layers deep. | |
1071 | * | |
1072 | * This is a helper for when the calling function does not know how many layers deep | |
1073 | * the path array is so cannot easily check. | |
1074 | * | |
1075 | * @param array $values | |
1076 | * @param array $path | |
1077 | * @return bool | |
1078 | */ | |
1079 | public static function pathIsset($values, $path) { | |
1080 | foreach ($path as $key) { | |
1081 | if (!is_array($values) || !isset($values[$key])) { | |
1082 | return FALSE; | |
1083 | } | |
1084 | $values = $values[$key]; | |
1085 | } | |
1086 | return TRUE; | |
393f41dd TO |
1087 | } |
1088 | ||
61bdc086 TO |
1089 | /** |
1090 | * Remove a key from an array. | |
1091 | * | |
1092 | * This is a helper for when the calling function does not know how many layers deep | |
1093 | * the path array is so cannot easily check. | |
1094 | * | |
1095 | * @param array $values | |
1096 | * @param array $path | |
1097 | * @param bool $cleanup | |
1098 | * If removed item leaves behind an empty array, should you remove the empty array? | |
1099 | * @return bool | |
1100 | * TRUE if anything has been removed. FALSE if no changes were required. | |
1101 | */ | |
1102 | public static function pathUnset(&$values, $path, $cleanup = FALSE) { | |
1103 | if (count($path) === 1) { | |
1104 | if (isset($values[$path[0]])) { | |
1105 | unset($values[$path[0]]); | |
1106 | return TRUE; | |
1107 | } | |
1108 | else { | |
1109 | return FALSE; | |
1110 | } | |
1111 | } | |
1112 | else { | |
1113 | $next = array_shift($path); | |
1114 | $r = static::pathUnset($values[$next], $path, $cleanup); | |
1115 | if ($cleanup && $values[$next] === []) { | |
1116 | $r = TRUE; | |
1117 | unset($values[$next]); | |
1118 | } | |
1119 | return $r; | |
1120 | } | |
1121 | } | |
1122 | ||
393f41dd TO |
1123 | /** |
1124 | * Set a single value in an array tree. | |
1125 | * | |
d6f1584f CW |
1126 | * @param array $values |
1127 | * Ex: ['foo' => ['bar' => 123]]. | |
393f41dd | 1128 | * @param array $pathParts |
d6f1584f | 1129 | * Ex: ['foo', 'bar']. |
393f41dd TO |
1130 | * @param $value |
1131 | * Ex: 456. | |
1132 | */ | |
d6f1584f CW |
1133 | public static function pathSet(&$values, $pathParts, $value) { |
1134 | $r = &$values; | |
393f41dd TO |
1135 | $last = array_pop($pathParts); |
1136 | foreach ($pathParts as $part) { | |
1137 | if (!isset($r[$part])) { | |
be2fb01f | 1138 | $r[$part] = []; |
393f41dd TO |
1139 | } |
1140 | $r = &$r[$part]; | |
1141 | } | |
1142 | $r[$last] = $value; | |
1143 | } | |
1144 | ||
884466e6 TO |
1145 | /** |
1146 | * Move an item in an array-tree (if it exists). | |
1147 | * | |
1148 | * @param array $values | |
1149 | * Data-tree | |
1150 | * @param string[] $src | |
1151 | * Old path for the existing item | |
1152 | * @param string[] $dest | |
1153 | * New path | |
1154 | * @param bool $cleanup | |
1155 | * @return int | |
1156 | * Number of items moved (0 or 1). | |
1157 | */ | |
1158 | public static function pathMove(&$values, $src, $dest, $cleanup = FALSE) { | |
1159 | if (!static::pathIsset($values, $src)) { | |
1160 | return 0; | |
1161 | } | |
1162 | else { | |
1163 | $value = static::pathGet($values, $src); | |
1164 | static::pathSet($values, $dest, $value); | |
1165 | static::pathUnset($values, $src, $cleanup); | |
1166 | return 1; | |
1167 | } | |
1168 | } | |
1169 | ||
2c6fe88a TO |
1170 | /** |
1171 | * Convert a simple dictionary into separate key+value records. | |
1172 | * | |
1173 | * @param array $array | |
1174 | * Ex: array('foo' => 'bar'). | |
1175 | * @param string $keyField | |
1176 | * Ex: 'key'. | |
1177 | * @param string $valueField | |
1178 | * Ex: 'value'. | |
1179 | * @return array | |
dde482e0 | 1180 | * @deprecated |
2c6fe88a TO |
1181 | */ |
1182 | public static function toKeyValueRows($array, $keyField = 'key', $valueField = 'value') { | |
dde482e0 | 1183 | return self::makeNonAssociative($array, $keyField, $valueField); |
2c6fe88a TO |
1184 | } |
1185 | ||
6c28d4be | 1186 | /** |
1187 | * Convert array where key(s) holds the actual value and value(s) as 1 into array of actual values | |
1188 | * Ex: array('foobar' => 1, 4 => 1) formatted into array('foobar', 4) | |
1189 | * | |
e5ad0335 E |
1190 | * @deprecated use convertCheckboxInputToArray instead (after testing) |
1191 | * https://github.com/civicrm/civicrm-core/pull/8169 | |
1192 | * | |
6c28d4be | 1193 | * @param array $array |
6c28d4be | 1194 | */ |
1195 | public static function formatArrayKeys(&$array) { | |
38c304a5 | 1196 | if (!is_array($array)) { |
1197 | return; | |
1198 | } | |
6c28d4be | 1199 | $keys = array_keys($array, 1); |
1200 | if (count($keys) > 1 || | |
1201 | (count($keys) == 1 && | |
f6cc7d47 | 1202 | (current($keys) > 1 || |
1203 | is_string(current($keys)) || | |
6714d8d2 SL |
1204 | // handle (0 => 4), (1 => 1) |
1205 | (current($keys) == 1 && $array[1] == 1) | |
6c28d4be | 1206 | ) |
1207 | ) | |
1208 | ) { | |
1209 | $array = $keys; | |
1210 | } | |
1211 | } | |
1212 | ||
e5ad0335 E |
1213 | /** |
1214 | * Convert the data format coming in from checkboxes to an array of values. | |
1215 | * | |
1216 | * The input format from check boxes looks like | |
1217 | * array('value1' => 1, 'value2' => 1). This function converts those values to | |
1218 | * array(''value1', 'value2). | |
1219 | * | |
1220 | * The function will only alter the array if all values are equal to 1. | |
1221 | * | |
1222 | * @param array $input | |
1223 | * | |
1224 | * @return array | |
1225 | */ | |
1226 | public static function convertCheckboxFormatToArray($input) { | |
1227 | if (isset($input[0])) { | |
1228 | return $input; | |
1229 | } | |
1230 | $keys = array_keys($input, 1); | |
1231 | if ((count($keys) == count($input))) { | |
1232 | return $keys; | |
1233 | } | |
1234 | return $input; | |
1235 | } | |
1236 | ||
589e0f03 SL |
1237 | /** |
1238 | * Ensure that array is encoded in utf8 format. | |
1239 | * | |
1240 | * @param array $array | |
1241 | * | |
1242 | * @return array $array utf8-encoded. | |
1243 | */ | |
a83e8a28 SL |
1244 | public static function encode_items($array) { |
1245 | foreach ($array as $key => $value) { | |
1246 | if (is_array($value)) { | |
1247 | $array[$key] = self::encode_items($value); | |
1248 | } | |
48b4ad1f | 1249 | elseif (is_string($value)) { |
e46fbdd3 | 1250 | $array[$key] = mb_convert_encoding($value, mb_detect_encoding($value, mb_detect_order(), TRUE), 'UTF-8'); |
a83e8a28 | 1251 | } |
48b4ad1f SL |
1252 | else { |
1253 | $array[$key] = $value; | |
1254 | } | |
a83e8a28 SL |
1255 | } |
1256 | return $array; | |
1257 | } | |
1258 | ||
bc854509 | 1259 | /** |
1260 | * Build tree of elements. | |
1261 | * | |
1262 | * @param array $elements | |
1263 | * @param int|null $parentId | |
1264 | * | |
1265 | * @return array | |
1266 | */ | |
b733747a | 1267 | public static function buildTree($elements, $parentId = NULL) { |
be2fb01f | 1268 | $branch = []; |
b733747a CW |
1269 | |
1270 | foreach ($elements as $element) { | |
1271 | if ($element['parent_id'] == $parentId) { | |
1272 | $children = self::buildTree($elements, $element['id']); | |
1273 | if ($children) { | |
1274 | $element['children'] = $children; | |
1275 | } | |
1276 | $branch[] = $element; | |
1277 | } | |
1278 | } | |
1279 | ||
1280 | return $branch; | |
1281 | } | |
1282 | ||
bc854509 | 1283 | /** |
1284 | * Find search string in tree. | |
1285 | * | |
1286 | * @param string $search | |
1287 | * @param array $tree | |
1288 | * @param string $field | |
1289 | * | |
1290 | * @return array|null | |
1291 | */ | |
b733747a CW |
1292 | public static function findInTree($search, $tree, $field = 'id') { |
1293 | foreach ($tree as $item) { | |
1294 | if ($item[$field] == $search) { | |
1295 | return $item; | |
1296 | } | |
1297 | if (!empty($item['children'])) { | |
1298 | $found = self::findInTree($search, $item['children']); | |
1299 | if ($found) { | |
1300 | return $found; | |
1301 | } | |
1302 | } | |
1303 | } | |
1304 | return NULL; | |
1305 | } | |
1306 | ||
6a488035 | 1307 | } |