PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Support / Arr.php

Arr.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.1.0, at vendor/wpfluent/framework/src/WPFluent/Support/Arr.php

1,793 lines 45.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\Framework\Support;
4
5 use Closure;
6 use ArrayAccess;
7 use InvalidArgumentException;
8 use FluentBoards\Framework\Support\Helper;
9 use FluentBoards\Framework\Support\Collection;
10 use FluentBoards\Framework\Support\MacroableTrait;
11
12 class Arr
13 {
14 use MacroableTrait;
15
16 /**
17 * Makes a collection from array
18 *
19 * @param array $array
20 * @return \FluentBoards\Framework\Support\Collection
21 */
22 public static function of(array $array)
23 {
24 return Helper::collect($array);
25 }
26
27 /**
28 * Determine whether the given value is array accessible.
29 *
30 * @param mixed $value
31 * @return bool
32 */
33 public static function accessible($value)
34 {
35 return is_array($value) || $value instanceof ArrayAccess;
36 }
37
38 /**
39 * Add an element to an array using "dot" notation if it doesn't exist.
40 *
41 * @param array $array
42 * @param string $key
43 * @param mixed $value
44 * @return array
45 */
46 public static function add($array, $key, $value)
47 {
48 if (is_null(static::get($array, $key))) {
49 static::set($array, $key, $value);
50 }
51
52 return $array;
53 }
54
55 /**
56 * Collapse an array of arrays into a single array.
57 *
58 * @param iterable $array
59 * @return array
60 */
61 public static function collapse($array)
62 {
63 $results = [];
64
65 foreach ($array as $values) {
66 if ($values instanceof Collection) {
67 $values = $values->all();
68 } elseif (! is_array($values)) {
69 continue;
70 }
71
72 $results[] = $values;
73 }
74
75 return array_merge([], ...$results);
76 }
77
78 /**
79 * Cross join the given arrays, returning all possible permutations.
80 *
81 * @param iterable ...$arrays
82 * @return array
83 */
84 public static function crossJoin(...$arrays)
85 {
86 $results = [[]];
87
88 foreach ($arrays as $index => $array) {
89 $append = [];
90
91 foreach ($results as $product) {
92 foreach ($array as $item) {
93 $product[$index] = $item;
94
95 $append[] = $product;
96 }
97 }
98
99 $results = $append;
100 }
101
102 return $results;
103 }
104
105 /**
106 * Divide an array into two arrays. One with keys and the other with values.
107 *
108 * @param array $array
109 * @return array
110 */
111 public static function divide($array)
112 {
113 return [array_keys($array), array_values($array)];
114 }
115
116 /**
117 * Flatten a multi-dimensional associative array with dots.
118 *
119 * @param iterable $array
120 * @param string $prepend
121 * @return array
122 */
123 public static function dot($array, $prepend = '')
124 {
125 $results = [];
126
127 foreach ($array as $key => $value) {
128 if (is_array($value) && ! empty($value)) {
129 $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
130 } else {
131 $results[$prepend.$key] = $value;
132 }
133 }
134
135 return $results;
136 }
137
138 /**
139 * Convert a flatten "dot" notation array into an expanded array.
140 *
141 * @param iterable $array
142 * @return array
143 */
144 public static function undot($array)
145 {
146 $results = [];
147
148 foreach ($array as $key => $value) {
149 static::set($results, $key, $value);
150 }
151
152 return $results;
153 }
154
155 /**
156 * Get all of the given array except for a specified array of keys.
157 *
158 * @param array $array
159 * @param array|string $keys
160 * @return array
161 */
162 public static function except($array, $keys)
163 {
164 static::forget($array, $keys);
165
166 return $array;
167 }
168
169 /**
170 * Determine if the given key exists in the provided array.
171 *
172 * @param \ArrayAccess|array $array
173 * @param string|int $key
174 * @return bool
175 */
176 public static function exists($array, $key)
177 {
178 if ($array instanceof Enumerable) {
179 return $array->has($key);
180 }
181
182 if ($array instanceof ArrayAccess) {
183 return $array->offsetExists($key);
184 }
185
186 return array_key_exists($key, $array);
187 }
188
189 /**
190 * Alias of exists.
191 * @param \ArrayAccess|array $array
192 * @param string|int $key
193 * @return bool
194 */
195 public static function keyExists($array, $key)
196 {
197 return static::exists($array, $key);
198 }
199
200 /**
201 * Alias of exists.
202 * @param \ArrayAccess|array $array
203 * @param string|int $key
204 * @return bool
205 */
206 public static function arrayKeyExists($array, $key)
207 {
208 return static::exists($array, $key);
209 }
210
211 /**
212 * Return the first element in an array passing a given truth test.
213 *
214 * @param iterable $array
215 * @param callable|null $callback
216 * @param mixed $default
217 * @return mixed
218 */
219 public static function first($array, ?callable $callback = null, $default = null)
220 {
221 if (is_null($callback)) {
222 if (empty($array)) {
223 return Helper::value($default);
224 }
225
226 foreach ($array as $item) {
227 return $item;
228 }
229 }
230
231 foreach ($array as $key => $value) {
232 if ($callback($value, $key)) {
233 return $value;
234 }
235 }
236
237 return Helper::value($default);
238 }
239
240 /**
241 * Returns the key of the first item (matching the specified
242 * callback if given) or null if there is no such item.
243 *
244 * @param array $array
245 * @param callable|null $callback
246 * @return mixed
247 */
248 public static function firstKey($array, ?callable $callback = null)
249 {
250 if (!$callback) {
251 return array_key_first($array);
252 }
253
254 foreach ($array as $k => $v) {
255 if ($callback($v, $k, $array)) {
256 return $k;
257 }
258 }
259
260 return null;
261 }
262
263 /**
264 * Recursively filter an array like array_filter.
265 *
266 * @param array $array
267 * @param callable|null $cb
268 * @param integer $mode (ARRAY_FILTER_USE_BOTH = 1 | ARRAY_FILTER_USE_KEY = 2)
269 * @return array
270 */
271 public static function filterRecursive($array, ?callable $cb = null, $mode = 0)
272 {
273 $result = [];
274
275 foreach ($array as $key => $value) {
276 if (is_array($value)) {
277 if (is_int($key)) {
278 $result[] = static::filterRecursive($value, $cb, $mode);
279 } else {
280 $result[$key] = static::filterRecursive($value, $cb, $mode);
281 }
282 } else {
283 if (is_null($cb)) {
284 if ($value) {
285 if (is_int($key)) {
286 $result[] = $value;
287 } else {
288 $result[$key] = $value;
289 }
290 }
291 } else {
292 if ($mode && call_user_func($cb, $value, $key)) {
293 if (is_int($key)) {
294 $result[] = $value;
295 } else {
296 $result[$key] = $value;
297 }
298 } elseif (!$mode && call_user_func($cb, $value)) {
299 if (is_int($key)) {
300 $result[] = $value;
301 } else {
302 $result[$key] = $value;
303 }
304 }
305 }
306 }
307 }
308
309 return $result;
310 }
311
312 /**
313 * Recursively search the value and return the path of first match.
314 *
315 * @param array $array
316 * @param mixed $value
317 * @param bool $ci (false for case insensitive search, true otherwise)
318 * @return string|null
319 */
320 public static function findPath($array, $value, $ci = false)
321 {
322 if (!$ci) {
323 $value = strtolower($value);
324 $array = static::map($array, 'strtolower');
325 }
326
327 foreach ($array as $key => $val) {
328 if ($val === $value) {
329 return $key;
330 } elseif (is_array($val) && $path = static::findPath($val, $value, $ci)) {
331 return $key . '.' . $path;
332 }
333 }
334 }
335
336 /**
337 * Return the last element in an array passing a given truth test.
338 *
339 * @param array $array
340 * @param callable|null $callback
341 * @param mixed $default
342 * @return mixed
343 */
344 public static function last($array, ?callable $callback = null, $default = null)
345 {
346 if (is_null($callback)) {
347 return empty($array) ? Helper::value($default) : end($array);
348 }
349
350 return static::first(array_reverse($array, true), $callback, $default);
351 }
352
353 /**
354 * Returns the key of the last item (matching the specified
355 * callback if given) or null if there is no such item.
356 *
357 * @param array $array
358 * @param callable|null $callback
359 * @return mixed
360 */
361 public static function lastKey($array, ?callable $callback = null)
362 {
363 if (!$callback) {
364 return array_key_last($array);
365 }
366
367 $lastKey = null;
368
369 foreach ($array as $k => $v) {
370 if ($callback($v, $k, $array)) {
371 $lastKey = $k;
372 }
373 }
374
375 return $lastKey;
376 }
377
378 /**
379 * Flatten a multi-dimensional array into a single level.
380 *
381 * @param iterable $array
382 * @param int|float $depth
383 * @return array
384 */
385 public static function flatten($array, $depth = INF)
386 {
387 $result = [];
388
389 foreach ($array as $item) {
390 $item = $item instanceof Collection ? $item->all() : $item;
391
392 if (! is_array($item)) {
393 $result[] = $item;
394 } else {
395 $values = $depth === 1
396 ? array_values($item)
397 : static::flatten($item, $depth - 1);
398
399 foreach ($values as $value) {
400 $result[] = $value;
401 }
402 }
403 }
404
405 return $result;
406 }
407
408 /**
409 * Remove one or many array items from a given array using "dot" notation.
410 *
411 * @param array $array
412 * @param array|string $keys
413 * @return void
414 */
415 public static function forget(&$array, $keys)
416 {
417 $original = &$array;
418
419 $keys = (array) $keys;
420
421 if (count($keys) === 0) {
422 return;
423 }
424
425 foreach ($keys as $key) {
426 // if the exact key exists in the top-level, remove it
427 if (static::exists($array, $key)) {
428 unset($array[$key]);
429
430 continue;
431 }
432
433 $parts = explode('.', $key);
434
435 // clean up before each pass
436 $array = &$original;
437
438 while (count($parts) > 1) {
439 $part = array_shift($parts);
440
441 if (isset($array[$part]) && is_array($array[$part])) {
442 $array = &$array[$part];
443 } else {
444 continue 2;
445 }
446 }
447
448 unset($array[array_shift($parts)]);
449 }
450 }
451
452 /**
453 * Get an item from an array using "dot" notation.
454 *
455 * @param \ArrayAccess|array $array
456 * @param string|int|null $key
457 * @param mixed $default
458 * @return mixed
459 */
460 public static function get($array, $key, $default = null)
461 {
462 if (! static::accessible($array)) {
463 return Helper::value($default);
464 }
465
466 if (is_null($key)) {
467 return $array;
468 }
469
470 if (static::exists($array, $key)) {
471 return $array[$key];
472 }
473
474 if (strpos($key, '.') === false) {
475 return $array[$key] ?? Helper::value($default);
476 }
477
478 foreach (explode('.', $key) as $segment) {
479 if (static::accessible($array) && static::exists($array, $segment)) {
480 $array = $array[$segment];
481 } else {
482 return Helper::value($default);
483 }
484 }
485
486 return $array;
487 }
488
489 /**
490 * Check if an item or items (using key) exist in an array using "dot" notation.
491 *
492 * @param \ArrayAccess|array $array
493 * @param string|array $keys
494 * @return bool
495 */
496 public static function has($array, $keys)
497 {
498 $keys = (array) $keys;
499
500 if (! $array || $keys === []) {
501 return false;
502 }
503
504 foreach ($keys as $key) {
505 $subKeyArray = $array;
506
507 if (static::exists($array, $key)) {
508 continue;
509 }
510
511 foreach (explode('.', $key) as $segment) {
512 if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
513 $subKeyArray = $subKeyArray[$segment];
514 } else {
515 return false;
516 }
517 }
518 }
519
520 return true;
521 }
522
523 /**
524 * Determine if any of the keys exist in an array using "dot" notation.
525 *
526 * @param \ArrayAccess|array $array
527 * @param string|array $keys
528 * @return bool
529 */
530 public static function hasAny($array, $keys)
531 {
532 if (is_null($keys)) {
533 return false;
534 }
535
536 $keys = (array) $keys;
537
538 if (! $array) {
539 return false;
540 }
541
542 if ($keys === []) {
543 return false;
544 }
545
546 foreach ($keys as $key) {
547 if (static::has($array, $key)) {
548 return true;
549 }
550 }
551
552 return false;
553 }
554
555 /**
556 * Alias of contains.
557 *
558 * @param array $array
559 * @param string|array $value
560 * @return bool
561 */
562 public static function inArray($array, $value)
563 {
564 return static::contains($array, $value);
565 }
566
567 /**
568 * Determines if an array is associative.
569 *
570 * An array is "associative" if it doesn't have
571 * sequential numerical keys beginning with zero.
572 *
573 * @param array $array
574 * @return bool
575 */
576 public static function isAssoc(array $array)
577 {
578 return !static::isList($array);
579 }
580
581 /**
582 * Determines if an array is a list.
583 *
584 * An array is a "list" if all array keys are sequential
585 * integers starting from 0 with no gaps in between.
586 *
587 * @param array $array
588 * @return bool
589 */
590 public static function isList($array)
591 {
592 $i = -1;
593 foreach ($array as $k => $v) {
594 ++$i;
595 if ($k !== $i) {
596 return false;
597 }
598 }
599 return true;
600 }
601
602 /**
603 * Determines if the given key contains a boolean value.
604 *
605 * Returns true for true, 1, "1", "true", "on" and "yes"
606 * Returns false for false, "0", "false", "off", "no", and ""
607 * Returns for all non-boolean values.
608 *
609 * @param array $array
610 * @param string $key
611 *
612 * @return bool|null
613 * @see https://www.php.net/manual/en/filter.filters.validate.php
614 */
615 public static function isTrue($array, $key)
616 {
617 return filter_var(
618 static::get($array, $key),
619 FILTER_VALIDATE_BOOLEAN,
620 FILTER_NULL_ON_FAILURE
621 );
622 }
623
624 /**
625 * Get a subset of the items from the given array.
626 *
627 * @param array $array
628 * @param array|string $keys
629 * @return array
630 */
631 public static function only($array, $keys)
632 {
633 return array_intersect_key($array, array_flip((array) $keys));
634 }
635
636 /**
637 * Select an array of values from an array.
638 *
639 * @param array $array
640 * @param array|string $keys
641 * @return array
642 */
643 public static function select($array, $keys)
644 {
645 $keys = static::wrap($keys);
646
647 return array_map(function ($item) use ($keys) {
648 $result = [];
649
650 $item = (array) $item;
651
652 foreach ($keys as $key) {
653
654 if (static::accessible($item) && static::has($item, $key)) {
655
656 [$first] = explode('.', $key);
657
658 $result[$first] = static::get($item, $first);
659 }
660 }
661
662 return $result;
663
664 }, (array) $array);
665 }
666
667 /**
668 * Pluck an array of values from an array.
669 *
670 * @param iterable $array
671 * @param string|array|int|null $value
672 * @param string|array|null $key
673 * @return array
674 */
675 public static function pluck($array, $value, $key = null)
676 {
677 $results = [];
678
679 [$value, $key] = static::explodePluckParameters($value, $key);
680
681 foreach ($array as $item) {
682 $itemValue = Helper::dataGet($item, $value);
683
684 // If the key is "null", we will just append the value to the array and keep
685 // looping. Otherwise we will key the array using the value of the key we
686 // received from the developer. Then we'll return the final array form.
687 if (is_null($key)) {
688 $results[] = $itemValue;
689 } else {
690 $itemKey = Helper::dataGet($item, $key);
691
692 if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
693 $itemKey = (string) $itemKey;
694 }
695
696 $results[$itemKey] = $itemValue;
697 }
698 }
699
700 return $results;
701 }
702
703 /**
704 * Explode the "value" and "key" arguments passed to "pluck".
705 *
706 * @param string|array $value
707 * @param string|array|null $key
708 * @return array
709 */
710 protected static function explodePluckParameters($value, $key)
711 {
712 $value = is_string($value) ? explode('.', $value) : $value;
713
714 $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
715
716 return [$value, $key];
717 }
718
719 /**
720 * Push an item onto the beginning of an array.
721 *
722 * @param array $array
723 * @param mixed $value
724 * @param mixed $key
725 * @return array
726 */
727 public static function prepend($array, $value, $key = null)
728 {
729 if (func_num_args() == 2) {
730 array_unshift($array, $value);
731 } else {
732 $array = [$key => $value] + $array;
733 }
734
735 return $array;
736 }
737
738 /**
739 * Get a value from the array, and remove it.
740 *
741 * @param array $array
742 * @param string|int $key
743 * @param mixed $default
744 * @return mixed
745 */
746 public static function pull(&$array, $key, $default = null)
747 {
748 $value = static::get($array, $key, $default);
749
750 static::forget($array, $key);
751
752 return $value;
753 }
754
755 /**
756 * Convert the array into a query string.
757 *
758 * @param array $array
759 * @return string
760 */
761 public static function query($array)
762 {
763 return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
764 }
765
766 /**
767 * Get one or a specified number of random values from an array.
768 *
769 * @param array $array
770 * @param int|null $number
771 * @param bool|false $preserveKeys
772 * @return mixed
773 *
774 * @throws \InvalidArgumentException
775 */
776 public static function random($array, $number = null, $preserveKeys = false)
777 {
778 $requested = is_null($number) ? 1 : $number;
779
780 $count = count($array);
781
782 if ($requested > $count) {
783 throw new InvalidArgumentException(
784 "You requested {$requested} items, but there are only {$count} items available."
785 );
786 }
787
788 if (is_null($number)) {
789 return $array[array_rand($array)];
790 }
791
792 if ((int) $number === 0) {
793 return [];
794 }
795
796 $keys = array_rand($array, $number);
797
798 $results = [];
799
800 if ($preserveKeys) {
801 foreach ((array) $keys as $key) {
802 $results[$key] = $array[$key];
803 }
804 } else {
805 foreach ((array) $keys as $key) {
806 $results[] = $array[$key];
807 }
808 }
809
810 return $results;
811 }
812
813 /**
814 * Set an array item to a given value using "dot" notation.
815 *
816 * If no key is given to the method, the entire array will be replaced.
817 *
818 * @param array $array
819 * @param string|null $key
820 * @param mixed $value
821 * @return array
822 */
823 public static function set(&$array, $key, $value)
824 {
825 if (is_null($key)) {
826 return $array = $value;
827 }
828
829 $keys = explode('.', $key);
830
831 foreach ($keys as $i => $key) {
832 if (count($keys) === 1) {
833 break;
834 }
835
836 unset($keys[$i]);
837
838 // If the key doesn't exist at this depth, we will just create an empty array
839 // to hold the next value, allowing us to create the arrays to hold final
840 // values at the correct depth. Then we'll keep digging into the array.
841 if (! isset($array[$key]) || ! is_array($array[$key])) {
842 $array[$key] = [];
843 }
844
845 $array = &$array[$key];
846 }
847
848 $array[array_shift($keys)] = $value;
849
850 return $array;
851 }
852
853 /**
854 * Shuffle the given array and return the result.
855 *
856 * @param array $array
857 * @param int|null $seed
858 * @return array
859 */
860 public static function shuffle($array, $seed = null)
861 {
862 if (!is_null($seed)) {
863 mt_srand($seed);
864 usort($array, function () {
865 return mt_rand(-1, 1);
866 });
867 mt_srand();
868 } else {
869 shuffle($array);
870 }
871
872 return $array;
873 }
874
875 /**
876 * Sort the array using the given callback or "dot" notation.
877 *
878 * @param array $array
879 * @param callable|array|string|null $callback
880 * @return array
881 */
882 public static function sort($array, $callback = null)
883 {
884 return Collection::make($array)->sortBy($callback)->all();
885 }
886
887 /**
888 * Sort an array in descending order.
889 *
890 * @param array $array The input array.
891 * @param int-mask-of<SORT_REGULAR|SORT_NUMERIC|SORT_STRING|SORT_LOCALE_STRING|SORT_NATURAL|SORT_FLAG_CASE> $flags
892 * @return array
893 *
894 * @see https://www.php.net/manual/en/function.rsort.php
895 */
896 public static function rsort($array, $flags = SORT_REGULAR)
897 {
898 rsort($array, $flags);
899 return $array;
900 }
901
902 /**
903 * Sort an array in ascending order and maintain index association.
904 *
905 * @param array $array
906 * @param int-mask-of<SORT_REGULAR|SORT_NUMERIC|SORT_STRING|SORT_LOCALE_STRING|SORT_NATURAL|SORT_FLAG_CASE> $flags
907 * @return array
908 * @see https://www.php.net/manual/en/function.asort.php
909 */
910 public static function asort($array, $flags = SORT_REGULAR)
911 {
912 asort($array, $flags);
913 return $array;
914 }
915
916 /**
917 * Sort an array in descending order and maintain index association.
918 *
919 * @param array $array
920 * @param int-mask-of<SORT_REGULAR|SORT_NUMERIC|SORT_STRING|SORT_LOCALE_STRING|SORT_NATURAL|SORT_FLAG_CASE> $flags
921 * @return array
922 * @see https://www.php.net/manual/en/function.arsort.php
923 */
924 public static function arsort($array, $flags = SORT_REGULAR)
925 {
926 arsort($array, $flags);
927 return $array;
928 }
929
930 /**
931 * Sort an array by key in ascending order.
932 *
933 * @param array $array
934 * @param int-mask-of<SORT_REGULAR|SORT_NUMERIC|SORT_STRING|SORT_LOCALE_STRING|SORT_NATURAL|SORT_FLAG_CASE> $flags
935 * @return array
936 * @see https://www.php.net/manual/en/function.ksort.php
937 */
938 public static function ksort($array, $flags = SORT_REGULAR)
939 {
940 ksort($array, $flags);
941 return $array;
942 }
943
944 /**
945 * Sort an array by key in descending order.
946 *
947 * @param array $array
948 * @param int-mask-of<SORT_REGULAR|SORT_NUMERIC|SORT_STRING|SORT_LOCALE_STRING|SORT_NATURAL|SORT_FLAG_CASE> $flags
949 * @return array
950 * @see https://www.php.net/manual/en/function.krsort.php
951 */
952 public static function krsort($array, $flags = SORT_REGULAR)
953 {
954 krsort($array, $flags);
955 return $array;
956 }
957
958 /**
959 * Sort an array using a "natural order" algorithm.
960 *
961 * @param array $array
962 * @return array
963 * @see https://www.php.net/manual/en/function.natsort.php
964 */
965 public static function natsort($array)
966 {
967 natsort($array);
968 return $array;
969 }
970
971 /**
972 * Sort an array using a case insensitive "natural order" algorithm.
973 *
974 * @param array $array
975 * @return array
976 * @see https://www.php.net/manual/en/function.natcasesort.php
977 */
978 public static function natcasesort($array)
979 {
980 natcasesort($array);
981 return $array;
982 }
983
984 /**
985 * Sort an array by values using a user-defined comparison function.
986 *
987 * @param array $array
988 * @return array
989 * @see https://www.php.net/manual/en/function.usort.php
990 */
991 public static function usort($array, callable $callback)
992 {
993 usort($array, $callback);
994 return $array;
995 }
996
997 /**
998 * Sort an array with a user-defined comparison
999 * function and maintain index association.
1000 *
1001 * @param array $array
1002 * @return array
1003 * @see https://www.php.net/manual/en/function.uasort.php
1004 */
1005 public static function uasort($array, callable $callback)
1006 {
1007 uasort($array, $callback);
1008 return $array;
1009 }
1010
1011 /**
1012 * Sort an array by keys using a user-defined comparison function.
1013 *
1014 * @param array $array
1015 * @return array
1016 * @see https://www.php.net/manual/en/function.uksort.php
1017 */
1018 public static function uksort($array, callable $callback)
1019 {
1020 uksort($array, $callback);
1021 return $array;
1022 }
1023
1024 /**
1025 * Recursively sort an array by keys and values.
1026 *
1027 * @param array $array
1028 * @param int $options
1029 * @param bool $desc
1030 * @return array
1031 */
1032 public static function sortRecursive(
1033 $array,
1034 $options = SORT_REGULAR,
1035 $desc = false
1036 )
1037 {
1038 foreach ($array as &$value) {
1039 if (is_array($value)) {
1040 $value = static::sortRecursive($value, $options, $desc);
1041 }
1042 }
1043
1044 if (static::isAssoc($array)) {
1045 $desc
1046 ? krsort($array, $options)
1047 : ksort($array, $options);
1048 } else {
1049 $desc
1050 ? rsort($array, $options)
1051 : sort($array, $options);
1052 }
1053
1054 return $array;
1055 }
1056
1057 /**
1058 * Recursively sort an array by keys and values in Descending order.
1059 *
1060 * @param array $array
1061 * @param int $options
1062 * @param bool $desc
1063 * @return array
1064 */
1065 public static function sortRecursiveDesc(
1066 $array,
1067 $options = SORT_REGULAR,
1068 $desc = false
1069 )
1070 {
1071 return static::sortRecursive($array, $options, true);
1072 }
1073
1074 /**
1075 * Conditionally compile classes from an array into a CSS class list.
1076 *
1077 * @param array $array
1078 * @return string
1079 */
1080 public static function toCssClasses($array)
1081 {
1082 $classList = static::wrap($array);
1083
1084 $classes = [];
1085
1086 foreach ($classList as $class => $constraint) {
1087 if (is_numeric($class)) {
1088 $classes[] = $constraint;
1089 } elseif ($constraint) {
1090 $classes[] = $class;
1091 }
1092 }
1093
1094 return implode(' ', $classes);
1095 }
1096
1097 /**
1098 * Transforms an array to \stdClass
1099 * @param array $array
1100 * @return \stdClass
1101 */
1102 public static function toObject($array)
1103 {
1104 return Helper::objectCreate($array);
1105 }
1106
1107 /**
1108 * Filter the array using the given callback.
1109 *
1110 * @param array $array
1111 * @param callable $callback
1112 * @return array
1113 */
1114 public static function where($array, callable $callback)
1115 {
1116 return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
1117 }
1118
1119 /**
1120 * Filter items where the value is not null.
1121 *
1122 * @param array $array
1123 * @return array
1124 */
1125 public static function whereNotNull($array)
1126 {
1127 return static::where($array, function ($value) {
1128 return ! is_null($value);
1129 });
1130 }
1131
1132 /**
1133 * Filter items where the value is not null.
1134 *
1135 * @param array $array
1136 * @return array
1137 */
1138 public static function whereNotTrue($array, $strict = false)
1139 {
1140 return static::where($array, function ($value) use ($strict) {
1141 return $strict ? $value === false : !$value;
1142 });
1143 }
1144
1145 /**
1146 * If the given value is not an array and not null, wrap it in one.
1147 *
1148 * @param mixed $value
1149 * @return array
1150 */
1151 public static function wrap($value)
1152 {
1153 if (is_null($value)) {
1154 return [];
1155 }
1156
1157 return is_array($value) ? $value : [$value];
1158 }
1159
1160 /**
1161 * Maps a function to all non-iterable elements of an array or an object.
1162 *
1163 * This is similar to `array_walk_recursive()` but acts upon objects too.
1164 *
1165 * @param mixed $value The array, object, or scalar.
1166 * @param callable $callback The function to map onto $value.
1167 * @see https://developer.wordpress.org/reference/functions/map_deep/
1168 *
1169 * @return mixed The value with the callback applied to all non-arrays and non-objects inside it.
1170 */
1171 public static function map($value, $callback)
1172 {
1173 return map_deep($value, $callback);
1174 }
1175
1176 /**
1177 * Check if the value(s) exist in an array using "dot" notation.
1178 *
1179 * @param array $array
1180 * @param string|array $values
1181 * @return bool
1182 */
1183 public static function contains(array $array, $values)
1184 {
1185 $result = [];
1186
1187 $values = is_array($values) ? $values : [$values];
1188
1189 foreach ($values as $value) {
1190
1191 if (in_array($value, $array)) {
1192 $result[] = $value;
1193 continue;
1194 }
1195
1196 $segments = explode('.', $value);
1197
1198 $value = array_pop($segments);
1199
1200 $nested = (array) static::get($array, implode('.', $segments));
1201
1202 if ($nested && in_array($value, $nested)) {
1203 $result[] = $value;
1204 }
1205 }
1206
1207 return count($result) === count($values);
1208 }
1209
1210 /**
1211 * Check if the any value exist in an array using "dot" notation.
1212 *
1213 * @param array $array
1214 * @param string|array $values
1215 * @return bool
1216 */
1217 public static function containsAny(array $array, $values)
1218 {
1219 $result = [];
1220
1221 $values = is_array($values) ? $values : [$values];
1222
1223 foreach ($values as $value) {
1224
1225 if (in_array($value, $array)) {
1226 return true;
1227 }
1228
1229 $segments = explode('.', $value);
1230
1231 $value = array_pop($segments);
1232
1233 $nested = (array) static::get($array, implode('.', $segments));
1234
1235 if ($nested && in_array($value, $nested)) {
1236 return true;
1237 }
1238 }
1239
1240 return false;
1241 }
1242
1243 /**
1244 * Compare two nested arrays side by side
1245 * @param array $array1
1246 * @param array $array2
1247 * @param array $path
1248 * @return array
1249 */
1250 public static function compare($array1, $array2, $path = [])
1251 {
1252 $differences = [];
1253
1254 foreach ($array1 as $key => $value1) {
1255 // Check if the key exists in the second array
1256 if (!array_key_exists($key, $array2)) {
1257 $differences[implode('.', array_merge($path, [$key]))] = [
1258 'array_1' => $value1,
1259 'array_2' => null,
1260 ];
1261 } else {
1262 // If the value is an array, recursively compare
1263 if (is_array($value1) && is_array($array2[$key])) {
1264 $differences = array_merge($differences, static::compare(
1265 $value1, $array2[$key], array_merge($path, [$key])
1266 ));
1267 } else {
1268 // Compare values
1269 if ($value1 !== $array2[$key]) {
1270 $differences[implode('.', array_merge($path, [$key]))] = [
1271 'array_1' => $value1,
1272 'array_2' => $array2[$key],
1273 ];
1274 }
1275 }
1276 }
1277 }
1278
1279 // Check for keys in the second array that are not in the first array
1280 foreach ($array2 as $key => $value2) {
1281 if (!array_key_exists($key, $array1)) {
1282 $differences[implode('.', array_merge($path, [$key]))] = [
1283 'array_1' => null,
1284 'array_2' => $value2,
1285 ];
1286 }
1287 }
1288
1289 return $differences;
1290 }
1291
1292 /**
1293 * Merge the items from the first array into the
1294 * second array if the second array is missing it.
1295 *
1296 * @param array &$array1
1297 * @param array &$array2
1298 * @return array
1299 */
1300 public static function mergeMissing(&$array1, &$array2)
1301 {
1302 foreach ($array1 as $key => $value1) {
1303 // If the key exists in the second array
1304 if (array_key_exists($key, $array2)) {
1305 // If the value is an array, recursively add missing items
1306 if (is_array($value1) && is_array($array2[$key])) {
1307 static::mergeMissing($value1, $array2[$key]);
1308 }
1309 } else {
1310 // If the key doesn't exist in the second array,
1311 // then add it with the corresponding value
1312 $array2[$key] = $value1;
1313 }
1314 }
1315
1316 return $array2;
1317 }
1318
1319 /**
1320 * Recursively merge the given array with defaults.
1321 * Overwrite $array with $defaults if only $array
1322 * contains null or empty values or doesn't exist.
1323 *
1324 * @param array $array
1325 * @param array $defaults
1326 * @return array
1327 */
1328 public static function mergeMissingValues(array $array, array $defaults)
1329 {
1330 $merged = array_merge($defaults, $array);
1331
1332 foreach ($merged as $key => $value) {
1333 if (
1334 is_array($value) &&
1335 isset($defaults[$key]) &&
1336 is_array($defaults[$key])
1337 ) {
1338 // Recursively merge arrays
1339 $merged[$key] = static::mergeMissingValues(
1340 $value, $defaults[$key]
1341 );
1342 } elseif (
1343 isset($defaults[$key]) &&
1344 (is_null($value) || $value === '')
1345 ) {
1346 // Replace null or empty values
1347 $merged[$key] = $defaults[$key];
1348 }
1349 }
1350
1351 return $merged;
1352 }
1353
1354 /**
1355 * Return matching items from array (similar to mysql's %LIKE%)
1356 *
1357 * @param string $pattern (plain string or regex)
1358 * @param array $array
1359 * @return array|false
1360 */
1361 public static function like($array, $pattern)
1362 {
1363 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1364 $pattern = '~'. preg_quote($pattern, '~') . '~i';
1365 }
1366
1367 return preg_grep($pattern, $array);
1368 }
1369
1370 /**
1371 * Return non-matching items from array (similar to mysql's NOT %LIKE%)
1372 *
1373 * @param string $pattern (plain string or regex)
1374 * @param array $array
1375 * @return array|false
1376 */
1377 public static function notLike($array, $pattern)
1378 {
1379 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1380 $pattern = '~'. preg_quote($pattern, '~') . '~i';
1381 }
1382
1383 return preg_grep($pattern, $array, PREG_GREP_INVERT);
1384 }
1385
1386 /**
1387 * Return matching starting of items from array (similar to mysql's %LIKE)
1388 *
1389 * @param string $pattern (plain string or regex)
1390 * @param array $array
1391 * @return array|false
1392 */
1393 public static function startsLike($array, $pattern)
1394 {
1395 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1396 $pattern = '~^'. preg_quote($pattern, '~') . '~i';
1397 }
1398
1399 return preg_grep($pattern, $array);
1400 }
1401
1402 /**
1403 * Return non-matching starting of items from array (similar to mysql's NOT %LIKE)
1404 *
1405 * @param string $pattern (plain string or regex)
1406 * @param array $array
1407 * @return array|false
1408 */
1409 public static function DoesNotStartLike($array, $pattern)
1410 {
1411 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1412 $pattern = '~^(?!' . preg_quote($pattern, '~') . ')~i';
1413 }
1414
1415 return preg_grep($pattern, $array);
1416 }
1417
1418 /**
1419 * Return matching ending of items from array (similar to mysql's LIKE%)
1420 *
1421 * @param string $pattern (plain string or regex)
1422 * @param array $array
1423 * @return array|false
1424 */
1425 public static function endsLike($array, $pattern)
1426 {
1427 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1428 $pattern = '~'. preg_quote($pattern, '~') . '$~i';
1429 }
1430
1431 return preg_grep($pattern, $array);
1432 }
1433
1434 /**
1435 * Return non-matching ending of items from array (similar to mysql's NOT LIKE%)
1436 *
1437 * @param string $pattern (plain string or regex)
1438 * @param array $array
1439 * @return array|false
1440 */
1441 public static function DoesNotEndLike($array, $pattern)
1442 {
1443 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1444 $pattern = '~'. preg_quote($pattern, '~') . '$~i';
1445 }
1446
1447 return preg_grep($pattern, $array, PREG_GREP_INVERT);
1448 }
1449
1450 /**
1451 * Return matching items from array by keys
1452 *
1453 * @param string $pattern (plain string or regex)
1454 * @param array $array
1455 * @return array|false
1456 */
1457 public static function keysLike($array, $pattern)
1458 {
1459 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1460 $pattern = '~'. preg_quote($pattern, '~') . '~i';
1461 }
1462
1463 $values = [];
1464
1465 $keys = preg_grep($pattern, array_keys($array));
1466
1467 foreach ($keys as $key) {
1468 $values[$key] = $array[$key];
1469 }
1470
1471 return $values;
1472 }
1473
1474 /**
1475 * Return non-matching items from array by keys
1476 *
1477 * @param string $pattern (plain string or regex)
1478 * @param array $array
1479 * @return array|false
1480 */
1481 public static function keysNotLike($array, $pattern)
1482 {
1483 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1484 $pattern = '~'. preg_quote($pattern, '~') . '~i';
1485 }
1486
1487 $values = [];
1488
1489 $keys = preg_grep($pattern, array_keys($array), 1);
1490
1491 foreach ($keys as $key) {
1492 $values[$key] = $array[$key];
1493 }
1494
1495 return $values;
1496 }
1497
1498 /**
1499 * Return matching starting of items from array by keys
1500 *
1501 * @param string $pattern (plain string or regex)
1502 * @param array $array
1503 * @return array|false
1504 */
1505 public static function keysStartLike($array, $pattern)
1506 {
1507 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1508 $pattern = '~^'. preg_quote($pattern, '~') . '~i';
1509 }
1510
1511 $values = [];
1512
1513 $keys = preg_grep($pattern, array_keys($array));
1514
1515 foreach ($keys as $key) {
1516 $values[$key] = $array[$key];
1517 }
1518
1519 return $values;
1520 }
1521
1522 /**
1523 * Return non-matching starting of items from array by keys
1524 *
1525 * @param string $pattern (plain string or regex)
1526 * @param array $array
1527 * @return array|false
1528 */
1529 public static function keysDoesNotStartLike($array, $pattern)
1530 {
1531 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1532 $pattern = '~^(?!' . preg_quote($pattern, '~') . ')~i';
1533 }
1534
1535 $values = [];
1536
1537 $keys = preg_grep($pattern, array_keys($array));
1538
1539 foreach ($keys as $key) {
1540 $values[$key] = $array[$key];
1541 }
1542
1543 return $values;
1544 }
1545
1546 /**
1547 * Return matching ending of items from array by keys
1548 *
1549 * @param string $pattern (plain string or regex)
1550 * @param array $array
1551 * @return array|false
1552 */
1553 public static function keysEndLike($array, $pattern)
1554 {
1555 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1556 $pattern = '~'. preg_quote($pattern, '~') . '$~i';
1557 }
1558
1559 $values = [];
1560
1561 $keys = preg_grep($pattern, array_keys($array));
1562
1563 foreach ($keys as $key) {
1564 $values[$key] = $array[$key];
1565 }
1566
1567 return $values;
1568 }
1569
1570 /**
1571 * Return non-matching ending of items from array by keys
1572 *
1573 * @param string $pattern (plain string or regex)
1574 * @param array $array
1575 * @return array|false
1576 */
1577 public static function keysDoesNotEndLike($array, $pattern)
1578 {
1579 if (!preg_match('/^([\/#~]).*\1$/', $pattern)) {
1580 $pattern = '~'. preg_quote($pattern, '~') . '$~i';
1581 }
1582
1583 $values = [];
1584
1585 $keys = preg_grep($pattern, array_keys($array), PREG_GREP_INVERT);
1586
1587 foreach ($keys as $key) {
1588 $values[$key] = $array[$key];
1589 }
1590
1591 return $values;
1592 }
1593
1594 /**
1595 * Insert a new item in the array at the given position.
1596 *
1597 * @param array $array
1598 * @param int $pos
1599 * @param mixed $newItem
1600 * @return array
1601 */
1602 public static function insertAt($array, $pos, $newItem)
1603 {
1604 if (!isset($array[$pos])) {
1605 $array[] = $newItem;
1606 } else {
1607 $array = array_splice($array, $pos, 0, $newItem);
1608 }
1609
1610 return $array;
1611 }
1612
1613 /**
1614 * Inserts an item before the specified key in the given array. If the
1615 * key is not found, inserts the item at the beginning of the array.
1616 *
1617 * @param array $array
1618 * @param mixed $key
1619 * @param mixed $newKey
1620 * @param mixed $newValue
1621 * @return array $newArray
1622 */
1623 public static function insertBefore($array, $key, $newKey, $newValue)
1624 {
1625 $newArray = [];
1626 $keyFound = false;
1627
1628 foreach ($array as $k => $v) {
1629 if ($k === $key) {
1630 $newArray[$newKey] = $newValue;
1631 $keyFound = true;
1632 }
1633 $newArray[$k] = $v;
1634 }
1635
1636 if (!$keyFound) {
1637 $newArray = [$newKey => $newValue] + $newArray;
1638 }
1639
1640 return $newArray;
1641 }
1642
1643 /**
1644 * Inserts an item after the specified key in the given array. If the
1645 * key is not found, inserts the item at the end of the array.
1646 *
1647 * @param array $array
1648 * @param mixed $key
1649 * @param mixed $newKey
1650 * @param mixed $newValue
1651 * @return array $newArray
1652 */
1653 public static function insertAfter($array, $key, $newKey, $newValue)
1654 {
1655 $newArray = [];
1656 $keyFound = false;
1657
1658 foreach ($array as $k => $v) {
1659 $newArray[$k] = $v;
1660 if ($k === $key) {
1661 $newArray[$newKey] = $newValue;
1662 $keyFound = true;
1663 }
1664 }
1665
1666 if (!$keyFound) {
1667 $newArray[$newKey] = $newValue;
1668 }
1669
1670 return $newArray;
1671 }
1672
1673 /**
1674 * Tests whether at least one element in the array passes
1675 * the test implemented by the provided callback.
1676 *
1677 * @param array $array
1678 * @param callable $callback
1679 * @return bool
1680 */
1681 public static function some($array, callable $callback)
1682 {
1683 foreach ($array as $k => $v) {
1684 if ($callback($v, $k, $array)) {
1685 return true;
1686 }
1687 }
1688
1689 return false;
1690 }
1691
1692 /**
1693 * Tests whether all elements in the array pass the
1694 * test implemented by the provided callback.
1695 *
1696 * @param array $array
1697 * @param callable $callback
1698 * @return bool
1699 */
1700 public static function every($array, callable $callback)
1701 {
1702 foreach ($array as $k => $v) {
1703 if (!$callback($v, $k, $array)) {
1704 return false;
1705 }
1706 }
1707
1708 return true;
1709 }
1710
1711 /**
1712 * Finds the first element in the array that satisfies the
1713 * condition implemented by the callback function.
1714 *
1715 * @param array $array
1716 * @param callable $callback
1717 * @return mixed
1718 */
1719 public static function find($array, callable $callback, $findKey = false)
1720 {
1721 foreach ($array as $k => $v) {
1722 if ($callback($v, $k, $array)) {
1723 return $findKey ? $k : $v;
1724 }
1725 }
1726
1727 return null;
1728 }
1729
1730 /**
1731 * Finds the first key in the array that satisfies the
1732 * condition implemented by the callback function.
1733 *
1734 * @param array $array
1735 * @param callable $callback
1736 * @return mixed
1737 */
1738 public static function findKey($array, callable $callback)
1739 {
1740 return static::find($array, $callback, true);
1741 }
1742
1743 /**
1744 * Find similar words in an array.
1745 *
1746 * @param string $needle
1747 * @param array $haystack
1748 * @param integer $accuracy
1749 * @return string|null
1750 */
1751 public static function findSimilar($needle, array $haystack, $accuracy = 60)
1752 {
1753 $matches = [];
1754
1755 foreach ($haystack as $item) {
1756 if (Str::isSimilar($needle, $item, $accuracy)) {
1757 $matches[] = $item;
1758 }
1759 }
1760
1761 return $matches ? $matches[0] : null;
1762 }
1763
1764 /**
1765 * Pass the items through a series of callbacks.
1766 *
1767 * @param array $items
1768 * @param array $callbacks
1769 * @param integer $mode
1770 * @return array
1771 */
1772 public static function passThrough(array $items, array $callbacks, $mode = 0)
1773 {
1774 foreach ($items as $key => &$item) {
1775 reset($callbacks);
1776 foreach ($callbacks as $callback) {
1777 switch ($mode) {
1778 case 1:
1779 $items[$key] = $callback($key);
1780 break;
1781 case 2:
1782 $items[$key] = $callback($key, $item ?? '');
1783 break;
1784 default:
1785 $items[$key] = $callback($item ?? '');
1786 }
1787 }
1788 }
1789
1790 return $items;
1791 }
1792 }
1793