| 1 |
<?php |
| 2 |
|
| 3 |
namespace Give\Vendors\StellarWP\Arrays; |
| 4 |
|
| 5 |
use ArrayAccess; |
| 6 |
use Closure; |
| 7 |
use InvalidArgumentException; |
| 8 |
|
| 9 |
/** |
| 10 |
* Array utilities |
| 11 |
*/ |
| 12 |
class Arr { |
| 13 |
/** |
| 14 |
* Determines if the given value is array accessible. |
| 15 |
* |
| 16 |
* @param mixed $value |
| 17 |
* |
| 18 |
* @return bool |
| 19 |
*/ |
| 20 |
public static function accessible( $value ): bool { |
| 21 |
return is_array( $value ) || $value instanceof ArrayAccess; |
| 22 |
} |
| 23 |
|
| 24 |
/** |
| 25 |
* Add an element to an array using "dot" notation if it doesn't exist. |
| 26 |
* |
| 27 |
* @param array $array |
| 28 |
* @param string|int|float $key |
| 29 |
* @param mixed $value |
| 30 |
* |
| 31 |
* @return array |
| 32 |
*/ |
| 33 |
public static function add( $array, $key, $value ) { |
| 34 |
$key = explode( '.', $key ); |
| 35 |
$key = static::wrap( $key ); |
| 36 |
if ( is_null( static::get( $array, $key ) ) ) { |
| 37 |
$array = static::set( $array, $key, $value ); |
| 38 |
} |
| 39 |
|
| 40 |
return $array; |
| 41 |
} |
| 42 |
|
| 43 |
/** |
| 44 |
* Duplicates any key not prefixed with '_' creating a prefixed duplicate one. |
| 45 |
* |
| 46 |
* The prefixing and duplication is recursive. |
| 47 |
* |
| 48 |
* @since 1.0.0 |
| 49 |
* |
| 50 |
* @param mixed $array The array whose keys should be duplicated. |
| 51 |
* @param bool $recursive Whether the prefixing and duplication should be |
| 52 |
* recursive or shallow. |
| 53 |
* |
| 54 |
* @return array|mixed The array with the duplicate, prefixed, keys or the |
| 55 |
* original input if not an array. |
| 56 |
*/ |
| 57 |
public static function add_prefixed_keys_to( $array, bool $recursive = false ) { |
| 58 |
if ( ! is_array( $array ) ) { |
| 59 |
return $array; |
| 60 |
} |
| 61 |
|
| 62 |
$prefixed = []; |
| 63 |
foreach ( $array as $key => $value ) { |
| 64 |
if ( $recursive && is_array( $value ) ) { |
| 65 |
$value = self::add_prefixed_keys_to( $value, true ); |
| 66 |
// And also add it to the original array. |
| 67 |
$array[ $key ] = array_merge( $array[ $key ], $value ); |
| 68 |
} |
| 69 |
|
| 70 |
if ( 0 === strpos( $key, '_' ) ) { |
| 71 |
continue; |
| 72 |
} |
| 73 |
|
| 74 |
$prefixed[ '_' . $key ] = $value; |
| 75 |
} |
| 76 |
|
| 77 |
return array_merge( $array, $prefixed ); |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Duplicates any key prefixed with '_' creating an un-prefixed duplicate one. |
| 82 |
* |
| 83 |
* The un-prefixing and duplication is recursive. |
| 84 |
* |
| 85 |
* @since 1.0.0 |
| 86 |
* |
| 87 |
* @param mixed $array The array whose keys should be duplicated. |
| 88 |
* @param bool $recursive Whether the un-prefixing and duplication should be |
| 89 |
* recursive or shallow. |
| 90 |
* |
| 91 |
* @return mixed|array The array with the duplicate, unprefixed, keys or the |
| 92 |
* original input if not an array. |
| 93 |
*/ |
| 94 |
public static function add_unprefixed_keys_to( $array, bool $recursive = false ) { |
| 95 |
if ( ! is_array( $array ) ) { |
| 96 |
return $array; |
| 97 |
} |
| 98 |
|
| 99 |
$unprefixed = []; |
| 100 |
foreach ( $array as $key => $value ) { |
| 101 |
if ( $recursive && is_array( $value ) ) { |
| 102 |
$value = self::add_unprefixed_keys_to( $value, true ); |
| 103 |
// And also add it to the original array. |
| 104 |
$array[ $key ] = array_merge( $array[ $key ], $value ); |
| 105 |
} |
| 106 |
|
| 107 |
if ( 0 !== strpos( $key, '_' ) ) { |
| 108 |
continue; |
| 109 |
} |
| 110 |
$unprefixed[ substr( $key, 1 ) ] = $value; |
| 111 |
} |
| 112 |
|
| 113 |
return array_merge( $array, $unprefixed ); |
| 114 |
} |
| 115 |
|
| 116 |
/** |
| 117 |
* Recursively visits all elements of an array applying the specified callback to each element |
| 118 |
* key and value. |
| 119 |
* |
| 120 |
* @since 1.0.0 |
| 121 |
* |
| 122 |
* @param array|mixed $input The input array whose nodes should be visited. |
| 123 |
* @param callable $visitor A callback function that will be called on each array item; the callback will |
| 124 |
* receive the item key and value as input and should return an array that contains |
| 125 |
* the update key and value in the shape `[ <key>, <value> ]`. Returning a `null` |
| 126 |
* key will cause the element to be removed from the array. |
| 127 |
*/ |
| 128 |
public static function array_visit_recursive( $input, callable $visitor ) { |
| 129 |
if ( ! is_array( $input ) ) { |
| 130 |
return $input; |
| 131 |
} |
| 132 |
|
| 133 |
$return = []; |
| 134 |
|
| 135 |
foreach ( $input as $key => &$value ) { |
| 136 |
if ( is_array( $value ) ) { |
| 137 |
$value = static::array_visit_recursive( $value, $visitor ); |
| 138 |
} |
| 139 |
// Ensure visitors can quickly return `null` to remove an element. |
| 140 |
[ $updated_key, $update_value ] = array_replace( [ $key, $value ], static::wrap( $visitor( $key, $value ) ) ); |
| 141 |
if ( false === $updated_key ) { |
| 142 |
// Visitor will be able to remove an element by returning a `false` key for it. |
| 143 |
continue; |
| 144 |
} |
| 145 |
if ( null === $updated_key ) { |
| 146 |
// Automatically assign the first available numeric index to the element. |
| 147 |
$return[] = $update_value; |
| 148 |
} else { |
| 149 |
$return[ $updated_key ] = $update_value; |
| 150 |
} |
| 151 |
} |
| 152 |
|
| 153 |
return $return; |
| 154 |
} |
| 155 |
|
| 156 |
|
| 157 |
/** |
| 158 |
* Collapse an array of arrays into a single array. |
| 159 |
* |
| 160 |
* @param iterable $array |
| 161 |
* |
| 162 |
* @return array |
| 163 |
*/ |
| 164 |
public static function collapse( $array ) { |
| 165 |
$results = []; |
| 166 |
|
| 167 |
foreach ( $array as $values ) { |
| 168 |
if ( ! is_array( $values ) ) { |
| 169 |
continue; |
| 170 |
} |
| 171 |
|
| 172 |
$results[] = $values; |
| 173 |
} |
| 174 |
|
| 175 |
return array_merge( [], ...$results ); |
| 176 |
} |
| 177 |
|
| 178 |
/** |
| 179 |
* The inverse of the `stringify_keys` method, it will restore numeric keys for previously |
| 180 |
* stringified keys. |
| 181 |
* |
| 182 |
* @since 1.0.0 |
| 183 |
* |
| 184 |
* @param array<int|string,mixed> $input The input array whose stringified keys should be |
| 185 |
* destringified. |
| 186 |
* @param string $prefix The prefix that should be used to target only specific string keys. |
| 187 |
* |
| 188 |
* @return array<int|string,mixed> The input array, its stringified keys destringified. |
| 189 |
*/ |
| 190 |
public static function destringify_keys( array $input, string $prefix = 'sk_' ): array { |
| 191 |
$visitor = static function( $key, $value ) use ( $prefix ) { |
| 192 |
$destringified_key = 0 === self::strpos( $key, $prefix ) ? null : $key; |
| 193 |
|
| 194 |
return [ $destringified_key, $value ]; |
| 195 |
}; |
| 196 |
|
| 197 |
return static::array_visit_recursive( $input, $visitor ); |
| 198 |
} |
| 199 |
|
| 200 |
|
| 201 |
/** |
| 202 |
* Flatten a multi-dimensional associative array with dots. |
| 203 |
* |
| 204 |
* @param iterable $array |
| 205 |
* @param string $prepend |
| 206 |
* |
| 207 |
* @return array |
| 208 |
*/ |
| 209 |
public static function dot( $array, $prepend = '' ) { |
| 210 |
$results = []; |
| 211 |
|
| 212 |
foreach ( $array as $key => $value ) { |
| 213 |
if ( is_array( $value ) ) { |
| 214 |
$results = array_merge( $results, static::dot( $value, $prepend . $key . '.' ) ); |
| 215 |
} else { |
| 216 |
$results[ $prepend . $key ] = $value; |
| 217 |
} |
| 218 |
} |
| 219 |
|
| 220 |
return $results; |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Sanitize a multidimensional array. |
| 225 |
* |
| 226 |
* @link https://gist.github.com/esthezia/5804445 |
| 227 |
* @since 1.0.0 |
| 228 |
* |
| 229 |
* @param array|mixed $data The array to sanitize. |
| 230 |
* |
| 231 |
* @return array The sanitized array |
| 232 |
* |
| 233 |
*/ |
| 234 |
public static function escape_multidimensional_array( $data = [] ): array { |
| 235 |
|
| 236 |
if ( ! is_array( $data ) || ! count( $data ) ) { |
| 237 |
return []; |
| 238 |
} |
| 239 |
|
| 240 |
foreach ( $data as $key => $value ) { |
| 241 |
if ( ! is_array( $value ) && ! is_object( $value ) ) { |
| 242 |
$data[ $key ] = esc_attr( trim( $value ) ); |
| 243 |
} |
| 244 |
if ( is_array( $value ) ) { |
| 245 |
$data[ $key ] = self::escape_multidimensional_array( $value ); |
| 246 |
} |
| 247 |
} |
| 248 |
|
| 249 |
return $data; |
| 250 |
} |
| 251 |
|
| 252 |
/** |
| 253 |
* Discards everything other than array values having string keys and scalar values, ensuring a |
| 254 |
* one-dimensional, associative array result. |
| 255 |
* |
| 256 |
* @link https://www.php.net/manual/language.types.array.php Keys cast to non-strings will be discarded. |
| 257 |
* |
| 258 |
* @since 1.0.0 |
| 259 |
* |
| 260 |
* @param array|mixed $array |
| 261 |
* |
| 262 |
* @return array|mixed Associative or empty array. |
| 263 |
*/ |
| 264 |
public static function filter_to_flat_scalar_associative_array( $array ) { |
| 265 |
$result = []; |
| 266 |
|
| 267 |
if ( ! is_array( $array ) ) { |
| 268 |
return $result; |
| 269 |
} |
| 270 |
|
| 271 |
foreach ( $array as $k => $v ) { |
| 272 |
if ( ! is_string( $k ) ) { |
| 273 |
continue; |
| 274 |
} |
| 275 |
|
| 276 |
if ( ! is_scalar( $v ) ) { |
| 277 |
continue; |
| 278 |
} |
| 279 |
|
| 280 |
$result[ $k ] = $v; |
| 281 |
} |
| 282 |
|
| 283 |
return $result; |
| 284 |
} |
| 285 |
|
| 286 |
/** |
| 287 |
* Get all of the given array except for a specified array of keys. |
| 288 |
* |
| 289 |
* @param array $array |
| 290 |
* @param array|string|int|float $keys |
| 291 |
* |
| 292 |
* @return array |
| 293 |
*/ |
| 294 |
public static function except( $array, $keys ) { |
| 295 |
$new_array = $array; |
| 296 |
static::forget( $new_array, $keys ); |
| 297 |
|
| 298 |
return $new_array; |
| 299 |
} |
| 300 |
|
| 301 |
|
| 302 |
/** |
| 303 |
* Determine if the given key exists in the provided array. |
| 304 |
* |
| 305 |
* @param \ArrayAccess|array $array |
| 306 |
* @param string|int|float $key |
| 307 |
* |
| 308 |
* @return bool |
| 309 |
*/ |
| 310 |
public static function exists( $array, $key ) { |
| 311 |
return static::has( $array, $key ); |
| 312 |
} |
| 313 |
|
| 314 |
/** |
| 315 |
* Filters an associative array non-recursively, keeping only the values attached |
| 316 |
* to keys starting with the specified prefix. |
| 317 |
* |
| 318 |
* @since 1.0.0 |
| 319 |
* |
| 320 |
* @param array $array The array to filter. |
| 321 |
* @param string $prefix The prefix, or prefixes, of the keys to keep. |
| 322 |
* |
| 323 |
* @return array The filtered array. |
| 324 |
*/ |
| 325 |
public static function filter_prefixed( array $array, string $prefix ): array { |
| 326 |
$prefixes = implode( '|', array_map( 'preg_quote', static::wrap( $prefix ) ) ); |
| 327 |
$pattern = '/^(' . $prefixes . ')/'; |
| 328 |
$filtered = []; |
| 329 |
foreach ( $array as $key => $value ) { |
| 330 |
if ( ! preg_match( $pattern, $key ) ) { |
| 331 |
continue; |
| 332 |
} |
| 333 |
$filtered[ $key ] = $value; |
| 334 |
} |
| 335 |
|
| 336 |
return $filtered; |
| 337 |
} |
| 338 |
|
| 339 |
/** |
| 340 |
* Return the first element in an array passing a given truth test. |
| 341 |
* |
| 342 |
* @param iterable $array |
| 343 |
* @param callable|null $callback |
| 344 |
* @param mixed $default |
| 345 |
* |
| 346 |
* @return mixed |
| 347 |
*/ |
| 348 |
public static function first( $array, ?callable $callback = null, $default = null ) { |
| 349 |
if ( is_null( $callback ) ) { |
| 350 |
if ( empty( $array ) ) { |
| 351 |
return self::value( $default ); |
| 352 |
} |
| 353 |
|
| 354 |
foreach ( $array as $item ) { |
| 355 |
return $item; |
| 356 |
} |
| 357 |
} |
| 358 |
|
| 359 |
foreach ( $array as $key => $value ) { |
| 360 |
if ( $callback( $value, $key ) ) { |
| 361 |
return $value; |
| 362 |
} |
| 363 |
} |
| 364 |
|
| 365 |
return self::value( $default ); |
| 366 |
} |
| 367 |
|
| 368 |
/** |
| 369 |
* Flatten a multi-dimensional array into a single level. |
| 370 |
* |
| 371 |
* Typical use case is to flatten arrays like those returned by `get_post_meta( $id )`. |
| 372 |
* Empty arrays are replaced with an empty string. |
| 373 |
* |
| 374 |
* @since 1.0.0 |
| 375 |
* |
| 376 |
* @param iterable $array |
| 377 |
* @param int $depth |
| 378 |
* |
| 379 |
* @return array The flattened array. |
| 380 |
*/ |
| 381 |
public static function flatten( $array, int $depth = PHP_INT_MAX ): array { |
| 382 |
$result = []; |
| 383 |
|
| 384 |
if ( $depth < 1 ) { |
| 385 |
return $array; |
| 386 |
} |
| 387 |
|
| 388 |
foreach ( $array as $key => $item ) { |
| 389 |
if ( ! is_array( $item ) ) { |
| 390 |
// Preserve string keys, use numeric keys for numeric |
| 391 |
if ( is_string( $key ) ) { |
| 392 |
$result[ $key ] = $item; |
| 393 |
} else { |
| 394 |
$result[] = $item; |
| 395 |
} |
| 396 |
} else { |
| 397 |
$values = $depth === 1 |
| 398 |
? array_values( $item ) |
| 399 |
: static::flatten( $item, $depth - 1 ); |
| 400 |
|
| 401 |
foreach ( $values as $value_key => $value ) { |
| 402 |
// Preserve string keys from nested arrays |
| 403 |
if ( is_string( $value_key ) ) { |
| 404 |
$result[ $value_key ] = $value; |
| 405 |
} else { |
| 406 |
$result[] = $value; |
| 407 |
} |
| 408 |
} |
| 409 |
} |
| 410 |
} |
| 411 |
|
| 412 |
return $result; |
| 413 |
} |
| 414 |
|
| 415 |
/** |
| 416 |
* Remove one or many array items from a given array using "dot" notation. |
| 417 |
* |
| 418 |
* @param array $array |
| 419 |
* @param array|string|int|float $keys |
| 420 |
* |
| 421 |
* @return void |
| 422 |
*/ |
| 423 |
public static function forget( &$array, $keys ) { |
| 424 |
$keys = (array) $keys; |
| 425 |
|
| 426 |
foreach ( $keys as $key ) { |
| 427 |
// Convert dot notation to array segments |
| 428 |
$parts = explode( '.', $key ); |
| 429 |
|
| 430 |
if ( count( $parts ) === 1 ) { |
| 431 |
unset( $array[ $key ] ); |
| 432 |
continue; |
| 433 |
} |
| 434 |
|
| 435 |
// For nested keys, traverse the array |
| 436 |
$current = &$array; |
| 437 |
$lastKey = array_pop( $parts ); |
| 438 |
|
| 439 |
foreach ( $parts as $part ) { |
| 440 |
if ( ! isset( $current[ $part ] ) || ! is_array( $current[ $part ] ) ) { |
| 441 |
continue 2; |
| 442 |
} |
| 443 |
$current = &$current[ $part ]; |
| 444 |
} |
| 445 |
|
| 446 |
unset( $current[ $lastKey ] ); |
| 447 |
} |
| 448 |
} |
| 449 |
|
| 450 |
/** |
| 451 |
* Find a value inside of an array or object, including one nested a few levels deep. |
| 452 |
* |
| 453 |
* Example: get( $a, [ 0, 1, 2 ] ) returns the value of $a[0][1][2] or the default. |
| 454 |
* |
| 455 |
* @param array|object|mixed $variable Array or object to search within. |
| 456 |
* @param array|string|int|null $indexes Specify each nested index in order. Can also be in dot notation. |
| 457 |
* Example: array( 'lvl1', 'lvl2' ) or 'lvl1.lvl2'. |
| 458 |
* @param mixed $default Default value if the search finds nothing. |
| 459 |
* |
| 460 |
* @return mixed The value of the specified index or the default if not found. |
| 461 |
* @throws \InvalidArgumentException If the provided variable is not an array and does not implement ArrayAccess. |
| 462 |
*/ |
| 463 |
public static function get( $variable, $indexes, $default = null ) { |
| 464 |
if ( ! static::accessible( $variable ) ) { |
| 465 |
throw new \InvalidArgumentException( 'The provided variable is not an array and does not implement ArrayAccess.' ); |
| 466 |
} |
| 467 |
|
| 468 |
if ( is_null( $indexes ) ) { |
| 469 |
return $variable; |
| 470 |
} |
| 471 |
|
| 472 |
if ( is_string( $indexes ) && isset( $variable[ $indexes ] ) ) { |
| 473 |
return $variable[ $indexes ]; |
| 474 |
} elseif ( is_string( $indexes ) ) { |
| 475 |
$indexes = explode( '.', $indexes ); |
| 476 |
} |
| 477 |
|
| 478 |
$indexes = static::wrap( $indexes ); |
| 479 |
|
| 480 |
foreach ( $indexes as $index ) { |
| 481 |
if ( ! static::exists( $variable, $index ) ) { |
| 482 |
$variable = $default; |
| 483 |
break; |
| 484 |
} |
| 485 |
|
| 486 |
$variable = $variable[ $index ]; |
| 487 |
} |
| 488 |
|
| 489 |
return $variable; |
| 490 |
} |
| 491 |
|
| 492 |
/** |
| 493 |
* Returns the value associated with the first index, among the indexes, that is set in the array.. |
| 494 |
* |
| 495 |
* @since 1.0.0 |
| 496 |
* |
| 497 |
* @param array $array The array to search. |
| 498 |
* @param array $indexes The indexes to search; in order the function will look from the first to the last. |
| 499 |
* @param mixed $default The value that will be returned if the array does not have any of the indexes set. |
| 500 |
* |
| 501 |
* @return mixed|null The set value or the default value. |
| 502 |
*/ |
| 503 |
public static function get_first_set( array $array, array $indexes, $default = null ) { |
| 504 |
foreach ( $indexes as $index ) { |
| 505 |
if ( ! isset( $array[ $index ] ) ) { |
| 506 |
continue; |
| 507 |
} |
| 508 |
|
| 509 |
return $array[ $index ]; |
| 510 |
} |
| 511 |
|
| 512 |
return $default; |
| 513 |
} |
| 514 |
|
| 515 |
/** |
| 516 |
* Find a value inside a list of array or objects, including one nested a few levels deep. |
| 517 |
* |
| 518 |
* @since 1.0.0 |
| 519 |
* |
| 520 |
* Example: get( [$a, $b, $c], [ 0, 1, 2 ] ) returns the value of $a[0][1][2] found in $a, $b or $c |
| 521 |
* or the default. |
| 522 |
* |
| 523 |
* @param array $variables Array of arrays or objects to search within. |
| 524 |
* @param array|string $indexes Specify each nested index in order. |
| 525 |
* Example: array( 'lvl1', 'lvl2' ); |
| 526 |
* @param mixed $default Default value if the search finds nothing. |
| 527 |
* |
| 528 |
* @return mixed The value of the specified index or the default if not found. |
| 529 |
*/ |
| 530 |
public static function get_in_any( array $variables, $indexes, $default = null ) { |
| 531 |
foreach ( $variables as $variable ) { |
| 532 |
$found = self::get( $variable, $indexes, '__not_found__' ); |
| 533 |
if ( '__not_found__' !== $found ) { |
| 534 |
return $found; |
| 535 |
} |
| 536 |
} |
| 537 |
|
| 538 |
return $default; |
| 539 |
} |
| 540 |
|
| 541 |
|
| 542 |
/** |
| 543 |
* Check if an item or items exist in an array using "dot" notation. |
| 544 |
* |
| 545 |
* @param \ArrayAccess|array $array |
| 546 |
* @param array|string|int|null $indexes The indexes to search; in order the function will look from the first to the last. |
| 547 |
* |
| 548 |
* @return bool |
| 549 |
*/ |
| 550 |
public static function has( $array, $indexes ) { |
| 551 |
if ( ! is_array( $array ) ) { |
| 552 |
return false; |
| 553 |
} |
| 554 |
|
| 555 |
if ( isset( $array[ $indexes ] ) ) { |
| 556 |
return true; |
| 557 |
} |
| 558 |
|
| 559 |
if ( is_string( $indexes ) && isset( $array[ $indexes ] ) ) { |
| 560 |
return true; |
| 561 |
} |
| 562 |
|
| 563 |
if ( is_string( $indexes ) ) { |
| 564 |
$indexes = explode( '.', $indexes ); |
| 565 |
} |
| 566 |
|
| 567 |
// Start with the root array |
| 568 |
$current = &$array; |
| 569 |
|
| 570 |
// Get all segments except the last one |
| 571 |
$segments = array_slice( $indexes, 0, -1 ); |
| 572 |
$final_key = end( $indexes ); |
| 573 |
|
| 574 |
// Iterate through every key, setting the pointer one level deeper each time. |
| 575 |
foreach ( $segments as $segment ) { |
| 576 |
if ( ! isset( $current[ $segment ] ) || ! is_array( $current[ $segment ] ) ) { |
| 577 |
return false; |
| 578 |
} |
| 579 |
$current = &$current[ $segment ]; |
| 580 |
} |
| 581 |
|
| 582 |
return isset( $current[ $final_key ] ); |
| 583 |
} |
| 584 |
|
| 585 |
/** |
| 586 |
* Insert an array after a specified key within another array. |
| 587 |
* |
| 588 |
* @param string|int $key The key of the array to insert after. |
| 589 |
* @param array $source_array The array to insert into. |
| 590 |
* @param mixed $insert Value or array to insert. |
| 591 |
* |
| 592 |
* @return array |
| 593 |
*/ |
| 594 |
public static function insert_after_key( $key, array $source_array, $insert ): array { |
| 595 |
if ( ! is_array( $insert ) ) { |
| 596 |
$insert = [ $insert ]; |
| 597 |
} |
| 598 |
|
| 599 |
if ( array_key_exists( $key, $source_array ) ) { |
| 600 |
$position = array_search( $key, array_keys( $source_array ) ) + 1; |
| 601 |
$source_array = array_slice( $source_array, 0, $position, true ) + $insert + array_slice( $source_array, $position, null, true ); |
| 602 |
} else { |
| 603 |
// If no key is found, then add it to the end of the array. |
| 604 |
$source_array += $insert; |
| 605 |
} |
| 606 |
|
| 607 |
return $source_array; |
| 608 |
} |
| 609 |
|
| 610 |
/** |
| 611 |
* Insert an array immediately before a specified key within another array. |
| 612 |
* |
| 613 |
* @param string|int $key The key of the array to insert before. |
| 614 |
* @param array $source_array The array to insert into. |
| 615 |
* @param mixed $insert Value or array to insert. |
| 616 |
* |
| 617 |
* @return array |
| 618 |
*/ |
| 619 |
public static function insert_before_key( $key, array $source_array, $insert ): array { |
| 620 |
if ( ! is_array( $insert ) ) { |
| 621 |
$insert = [ $insert ]; |
| 622 |
} |
| 623 |
|
| 624 |
if ( array_key_exists( $key, $source_array ) ) { |
| 625 |
$position = array_search( $key, array_keys( $source_array ) ); |
| 626 |
$source_array = array_slice( $source_array, 0, $position, true ) + $insert + array_slice( $source_array, $position, null, true ); |
| 627 |
} else { |
| 628 |
// If no key is found, then add it to the end of the array. |
| 629 |
$source_array += $insert; |
| 630 |
} |
| 631 |
|
| 632 |
return $source_array; |
| 633 |
} |
| 634 |
|
| 635 |
|
| 636 |
/** |
| 637 |
* Determines if an array is associative. |
| 638 |
* |
| 639 |
* An array is "associative" if it doesn't have sequential numerical keys beginning with zero. |
| 640 |
* |
| 641 |
* @param array $array |
| 642 |
* |
| 643 |
* @return bool |
| 644 |
*/ |
| 645 |
public static function is_assoc( array $array ) { |
| 646 |
return ! static::is_list( $array ); |
| 647 |
} |
| 648 |
|
| 649 |
/** |
| 650 |
* Determines if an array is a list. |
| 651 |
* |
| 652 |
* An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between. |
| 653 |
* |
| 654 |
* @param array $array |
| 655 |
* |
| 656 |
* @return bool |
| 657 |
*/ |
| 658 |
public static function is_list( $array ) { |
| 659 |
if ( function_exists( 'array_is_list' ) ) { |
| 660 |
return array_is_list( $array ); |
| 661 |
} |
| 662 |
|
| 663 |
$i = 0; |
| 664 |
foreach ( $array as $k => $v ) { |
| 665 |
if ( $k !== $i++ ) { |
| 666 |
return false; |
| 667 |
} |
| 668 |
} |
| 669 |
|
| 670 |
return true; |
| 671 |
} |
| 672 |
|
| 673 |
|
| 674 |
/** |
| 675 |
* Join all items using a string. The final items can use a separate glue string. |
| 676 |
* |
| 677 |
* @param array $array |
| 678 |
* @param string $glue |
| 679 |
* @param string $finalGlue |
| 680 |
* |
| 681 |
* @return string |
| 682 |
*/ |
| 683 |
public static function join( $array, $glue, $finalGlue = '' ) { |
| 684 |
if ( $finalGlue === '' ) { |
| 685 |
return implode( $glue, $array ); |
| 686 |
} |
| 687 |
|
| 688 |
if ( count( $array ) === 0 ) { |
| 689 |
return ''; |
| 690 |
} |
| 691 |
|
| 692 |
if ( count( $array ) === 1 ) { |
| 693 |
return end( $array ); |
| 694 |
} |
| 695 |
|
| 696 |
$finalItem = array_pop( $array ); |
| 697 |
|
| 698 |
return implode( $glue, $array ) . $finalGlue . $finalItem; |
| 699 |
} |
| 700 |
|
| 701 |
/** |
| 702 |
* Return the last element in an array passing a given truth test. |
| 703 |
* |
| 704 |
* @param array $array |
| 705 |
* @param callable|null $callback |
| 706 |
* @param mixed $default |
| 707 |
* |
| 708 |
* @return mixed |
| 709 |
*/ |
| 710 |
public static function last( $array, ?callable $callback = null, $default = null ) { |
| 711 |
if ( is_null( $callback ) ) { |
| 712 |
return empty( $array ) ? self::value( $default ) : end( $array ); |
| 713 |
} |
| 714 |
|
| 715 |
return static::first( array_reverse( $array, true ), $callback, $default ); |
| 716 |
} |
| 717 |
|
| 718 |
/** |
| 719 |
* Converts a list to an array filtering out empty string elements. |
| 720 |
* |
| 721 |
* @param string|mixed|null $value A string representing a list of values separated by the specified separator |
| 722 |
* or an array. If the list is a string (e.g. a CSV list) then it will urldecoded |
| 723 |
* before processing. |
| 724 |
* @param string|mixed $sep The char(s) separating the list elements; will be ignored if the list is an array. |
| 725 |
* |
| 726 |
* @return array An array of list elements. |
| 727 |
*/ |
| 728 |
public static function list_to_array( $value, $sep = ',' ): array { |
| 729 |
// Let's not jump through all the hoops if the value is empty. |
| 730 |
if ( empty( $value ) ) { |
| 731 |
return []; |
| 732 |
} |
| 733 |
// since we might receive URL encoded strings for CSV lists let's URL decode them first |
| 734 |
$value = is_array( $value ) ? $value : urldecode( $value ); |
| 735 |
|
| 736 |
$sep = ! is_string( $sep ) ? ',' : $sep; |
| 737 |
|
| 738 |
if ( $value === '' ) { |
| 739 |
return []; |
| 740 |
} |
| 741 |
|
| 742 |
if ( ! is_array( $value ) ) { |
| 743 |
$value = preg_split( '/\\s*' . preg_quote( $sep, '/' ) . '\\s*/', $value ); |
| 744 |
} |
| 745 |
|
| 746 |
$filtered = []; |
| 747 |
foreach ( $value as $v ) { |
| 748 |
if ( '' === $v ) { |
| 749 |
continue; |
| 750 |
} |
| 751 |
$filtered[] = is_numeric( $v ) ? $v + 0 : $v; |
| 752 |
} |
| 753 |
|
| 754 |
return $filtered; |
| 755 |
} |
| 756 |
|
| 757 |
/** |
| 758 |
* Returns an array of values obtained by using the keys on the map; keys |
| 759 |
* that do not have a match in map are discarded. |
| 760 |
* |
| 761 |
* To discriminate from not found results and legitimately `false` |
| 762 |
* values from the map the `$found` parameter will be set by reference. |
| 763 |
* |
| 764 |
* @since 1.0.0 |
| 765 |
* |
| 766 |
* @param string|array $keys One or more keys that should be used to get |
| 767 |
* the new values |
| 768 |
* @param array $map An associative array relating the keys to the new |
| 769 |
* values. |
| 770 |
* @param bool $found When using a single key this argument will be |
| 771 |
* set to indicate whether the mapping was successful |
| 772 |
* or not. |
| 773 |
* |
| 774 |
* @return array|mixed|false An array of mapped values, a single mapped value when passing |
| 775 |
* one key only or `false` if one key was passed but the key could |
| 776 |
* not be mapped. |
| 777 |
*/ |
| 778 |
public static function map_or_discard( $keys, array $map, bool &$found = true ) { |
| 779 |
$hash = md5( (string) time() ); |
| 780 |
$mapped = []; |
| 781 |
|
| 782 |
foreach ( (array) $keys as $key ) { |
| 783 |
$meta_key = self::get( $map, $key, $hash ); |
| 784 |
if ( $hash === $meta_key ) { |
| 785 |
continue; |
| 786 |
} |
| 787 |
$mapped[] = $meta_key; |
| 788 |
} |
| 789 |
|
| 790 |
$found = (bool) count( $mapped ); |
| 791 |
|
| 792 |
if ( is_array( $keys ) ) { |
| 793 |
return $mapped; |
| 794 |
} |
| 795 |
|
| 796 |
return $found ? $mapped[0] : false; |
| 797 |
} |
| 798 |
|
| 799 |
/** |
| 800 |
* Recursively merge two arrays preserving keys. |
| 801 |
* |
| 802 |
* @link http://php.net/manual/en/function.array-merge-recursive.php#92195 |
| 803 |
* |
| 804 |
* @since 1.0.0 |
| 805 |
* |
| 806 |
* @param array $array1 |
| 807 |
* @param array $array2 |
| 808 |
* |
| 809 |
* @return array |
| 810 |
*/ |
| 811 |
public static function merge_recursive( array &$array1, array &$array2 ): array { |
| 812 |
$merged = $array1; |
| 813 |
|
| 814 |
foreach ( $array2 as $key => &$value ) { |
| 815 |
if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) { |
| 816 |
$merged[ $key ] = static::merge_recursive( $merged[ $key ], $value ); |
| 817 |
} else if ( is_int( $key ) ) { |
| 818 |
$merged[] = $value; |
| 819 |
} else { |
| 820 |
$merged[ $key ] = $value; |
| 821 |
} |
| 822 |
} |
| 823 |
|
| 824 |
return $merged; |
| 825 |
} |
| 826 |
|
| 827 |
/** |
| 828 |
* Merges two or more arrays in the nested format used by WP_Query arguments preserving and merging them correctly. |
| 829 |
* |
| 830 |
* The method will recursively replace named keys and merge numeric keys. The method takes its name from its intended |
| 831 |
* primary use, but it's not limited to query arguments only. |
| 832 |
* |
| 833 |
* @since 1.0.0 |
| 834 |
* |
| 835 |
* @param array<string|int,mixed> ...$arrays A set of arrays to merge. |
| 836 |
* |
| 837 |
* @return array<string|int,mixed> The recursively merged array. |
| 838 |
*/ |
| 839 |
public static function merge_recursive_query_vars( array ...$arrays ): array { |
| 840 |
if ( ! count( $arrays ) ) { |
| 841 |
return []; |
| 842 |
} |
| 843 |
|
| 844 |
// Temporarily transform numeric keys to string keys generated with time-related randomness. |
| 845 |
$stringified = array_map( [ static::class, 'stringify_keys' ], $arrays ); |
| 846 |
// Replace recursive will recursively replace any entry that has the same string key, stringified keys will never match due to randomness. |
| 847 |
$merged = array_replace_recursive( ...$stringified ); |
| 848 |
|
| 849 |
// Finally destringify the keys to return something that will resemble, in shape, the original arrays. |
| 850 |
return static::destringify_keys( $merged ); |
| 851 |
} |
| 852 |
|
| 853 |
/** |
| 854 |
* Get a subset of the items from the given array. |
| 855 |
* |
| 856 |
* @param array $array |
| 857 |
* @param array|string $keys |
| 858 |
* |
| 859 |
* @return array |
| 860 |
*/ |
| 861 |
public static function only( $array, $keys ) { |
| 862 |
return array_intersect_key( $array, array_flip( static::wrap( $keys ) ) ); |
| 863 |
} |
| 864 |
|
| 865 |
/** |
| 866 |
* Build an array from migrating aliased key values to their canonical key values, removing all alias keys. |
| 867 |
* |
| 868 |
* If the original array has values for both the alias and its canonical, keep the canonical's value and |
| 869 |
* discard the alias' value. |
| 870 |
* |
| 871 |
* @since 1.0.0 |
| 872 |
* |
| 873 |
* @param array $original An associative array of values, such as passed shortcode arguments. |
| 874 |
* @param array $alias_map An associative array of aliases: key as alias, value as mapped canonical. |
| 875 |
* Example: [ 'alias' => 'canonical', 'from' => 'to', 'that' => 'becomes_this' ] |
| 876 |
* |
| 877 |
* @return array |
| 878 |
*/ |
| 879 |
public static function parse_associative_array_alias( array $original, array $alias_map ): array { |
| 880 |
// Ensure array values. |
| 881 |
$alias_map = static::filter_to_flat_scalar_associative_array( $alias_map ); |
| 882 |
|
| 883 |
// Fail gracefully if alias array wasn't setup as [ 'from' => 'to' ]. |
| 884 |
if ( empty( $alias_map ) ) { |
| 885 |
return $original; |
| 886 |
} |
| 887 |
|
| 888 |
$result = $original; |
| 889 |
|
| 890 |
// Parse aliases. |
| 891 |
foreach ( $alias_map as $from => $to ) { |
| 892 |
// If this alias isn't in use, go onto the next. |
| 893 |
if ( ! isset( $result[ $from ] ) ) { |
| 894 |
continue; |
| 895 |
} |
| 896 |
|
| 897 |
// Only allow setting alias value if canonical value is not already present. |
| 898 |
if ( ! isset( $result[ $to ] ) ) { |
| 899 |
$result[ $to ] = $result[ $from ]; |
| 900 |
} |
| 901 |
|
| 902 |
// Always remove the alias key. |
| 903 |
unset( $result[ $from ] ); |
| 904 |
} |
| 905 |
|
| 906 |
return $result; |
| 907 |
} |
| 908 |
|
| 909 |
/** |
| 910 |
* Push an item onto the beginning of an array. |
| 911 |
* |
| 912 |
* @param array $array |
| 913 |
* @param mixed $value |
| 914 |
* @param mixed $key |
| 915 |
* |
| 916 |
* @return array |
| 917 |
*/ |
| 918 |
public static function prepend( $array, $value, $key = null ) { |
| 919 |
if ( func_num_args() == 2 ) { |
| 920 |
array_unshift( $array, $value ); |
| 921 |
} else { |
| 922 |
$array = [ $key => $value ] + $array; |
| 923 |
} |
| 924 |
|
| 925 |
return $array; |
| 926 |
} |
| 927 |
|
| 928 |
/** |
| 929 |
* Get a value from the array, and remove it. |
| 930 |
* |
| 931 |
* @param array $array |
| 932 |
* @param string|int $key |
| 933 |
* @param mixed $default |
| 934 |
* |
| 935 |
* @return mixed |
| 936 |
*/ |
| 937 |
public static function pull( &$array, $key, $default = null ) { |
| 938 |
$value = static::get( $array, $key, $default ); |
| 939 |
|
| 940 |
static::forget( $array, $key ); |
| 941 |
|
| 942 |
return $value; |
| 943 |
} |
| 944 |
|
| 945 |
/** |
| 946 |
* Convert the array into a query string. |
| 947 |
* |
| 948 |
* @param array $array |
| 949 |
* |
| 950 |
* @return string |
| 951 |
*/ |
| 952 |
public static function query( $array ) { |
| 953 |
return http_build_query( $array, '', '&', PHP_QUERY_RFC3986 ); |
| 954 |
} |
| 955 |
|
| 956 |
/** |
| 957 |
* Get one or a specified number of random values from an array. |
| 958 |
* |
| 959 |
* @param array $array |
| 960 |
* @param int|null $number |
| 961 |
* @param bool $preserveKeys |
| 962 |
* |
| 963 |
* @return mixed |
| 964 |
* |
| 965 |
* @throws \InvalidArgumentException |
| 966 |
*/ |
| 967 |
public static function random( $array, $number = null, $preserveKeys = false ) { |
| 968 |
$requested = is_null( $number ) ? 1 : $number; |
| 969 |
|
| 970 |
$count = count( $array ); |
| 971 |
|
| 972 |
if ( $requested > $count ) { |
| 973 |
throw new InvalidArgumentException( |
| 974 |
"You requested {$requested} items, but there are only {$count} items available." |
| 975 |
); |
| 976 |
} |
| 977 |
|
| 978 |
if ( is_null( $number ) ) { |
| 979 |
return $array[ array_rand( $array ) ]; |
| 980 |
} |
| 981 |
|
| 982 |
if ( (int) $number === 0 ) { |
| 983 |
return []; |
| 984 |
} |
| 985 |
|
| 986 |
$keys = array_rand( $array, $number ); |
| 987 |
$keys = static::wrap( $keys ); |
| 988 |
|
| 989 |
$results = []; |
| 990 |
|
| 991 |
if ( $preserveKeys ) { |
| 992 |
foreach ( $keys as $key ) { |
| 993 |
$results[ $key ] = $array[ $key ]; |
| 994 |
} |
| 995 |
} else { |
| 996 |
foreach ( $keys as $key ) { |
| 997 |
$results[] = $array[ $key ]; |
| 998 |
} |
| 999 |
} |
| 1000 |
|
| 1001 |
return $results; |
| 1002 |
} |
| 1003 |
|
| 1004 |
/** |
| 1005 |
* Recursively key-sort an array. |
| 1006 |
* |
| 1007 |
* @since 1.0.0 |
| 1008 |
* |
| 1009 |
* @param array $array The array to sort, modified by reference. |
| 1010 |
* |
| 1011 |
* @return bool The sorting result. |
| 1012 |
*/ |
| 1013 |
public static function recursive_ksort( array &$array ): bool { |
| 1014 |
// First recursively sort all sub-arrays |
| 1015 |
foreach ( $array as &$value ) { |
| 1016 |
if ( is_array( $value ) ) { |
| 1017 |
static::recursive_ksort( $value ); |
| 1018 |
} |
| 1019 |
} |
| 1020 |
|
| 1021 |
// Then sort the current array, ensuring numeric keys are sorted numerically |
| 1022 |
return ksort( $array, SORT_NATURAL ); |
| 1023 |
} |
| 1024 |
|
| 1025 |
/** |
| 1026 |
* Recursively remove numeric keys from an array. |
| 1027 |
* |
| 1028 |
* @since 1.0.0 |
| 1029 |
* |
| 1030 |
* @param array<string|int,mixed> $input The input array. |
| 1031 |
* |
| 1032 |
* @return array<int|mixed> An array that only contains integer keys at any of its levels. |
| 1033 |
*/ |
| 1034 |
public static function remove_numeric_keys_recursive( array $input ): array { |
| 1035 |
return self::array_visit_recursive( |
| 1036 |
$input, |
| 1037 |
static function( $key ) { |
| 1038 |
return is_numeric( $key ) ? false : $key; |
| 1039 |
} |
| 1040 |
); |
| 1041 |
} |
| 1042 |
|
| 1043 |
/** |
| 1044 |
* Recursively remove numeric keys from an array. |
| 1045 |
* |
| 1046 |
* @since 1.0.0 |
| 1047 |
* |
| 1048 |
* @param array<string|int,mixed> $input The input array. |
| 1049 |
* |
| 1050 |
* @return array<string,mixed> An array that only contains non numeric keys at any of its levels. |
| 1051 |
*/ |
| 1052 |
public static function remove_string_keys_recursive( array $input ): array { |
| 1053 |
return self::array_visit_recursive( |
| 1054 |
$input, |
| 1055 |
static function( $key ) { |
| 1056 |
return ! is_numeric( $key ) ? false : $key; |
| 1057 |
} |
| 1058 |
); |
| 1059 |
} |
| 1060 |
|
| 1061 |
/** |
| 1062 |
* Set key/value within an array, can set a key nested inside of a multidimensional array. |
| 1063 |
* |
| 1064 |
* Example: set( $a, [ 0, 1, 2 ], 'hi' ) sets $a[0][1][2] = 'hi' and returns $a. |
| 1065 |
* |
| 1066 |
* @param mixed $array The array containing the key this sets. |
| 1067 |
* @param string|array $key To set a key nested multiple levels deep pass an array |
| 1068 |
* specifying each key in order as a value. |
| 1069 |
* Example: array( 'lvl1', 'lvl2', 'lvl3' ); |
| 1070 |
* @param mixed $value The value. |
| 1071 |
* |
| 1072 |
* @return array Full array with the key set to the specified value. |
| 1073 |
*/ |
| 1074 |
public static function set( $array, $key, $value ): array { |
| 1075 |
// Convert input to array if not already |
| 1076 |
if ( ! is_array( $array ) ) { |
| 1077 |
$array = []; |
| 1078 |
} |
| 1079 |
|
| 1080 |
// If key is a string with dots, convert to array |
| 1081 |
if ( is_string( $key ) ) { |
| 1082 |
$key = explode( '.', $key ); |
| 1083 |
} |
| 1084 |
|
| 1085 |
// Convert strings and such to array. |
| 1086 |
$key = static::wrap( $key ); |
| 1087 |
|
| 1088 |
// Start with the root array |
| 1089 |
$current = &$array; |
| 1090 |
|
| 1091 |
// Get all segments except the last one |
| 1092 |
$segments = array_slice( $key, 0, -1 ); |
| 1093 |
$final_key = end( $key ); |
| 1094 |
|
| 1095 |
// Traverse through each key segment |
| 1096 |
foreach ( $segments as $segment ) { |
| 1097 |
// If the current segment is a Closure or not an array, convert it to an array |
| 1098 |
if ( ! isset( $current[ $segment ] ) || ! is_array( $current[ $segment ] ) ) { |
| 1099 |
$current[ $segment ] = []; |
| 1100 |
} |
| 1101 |
|
| 1102 |
// Move pointer to next level |
| 1103 |
$current = &$current[ $segment ]; |
| 1104 |
} |
| 1105 |
|
| 1106 |
// Set the final value |
| 1107 |
$current[ $final_key ] = $value; |
| 1108 |
|
| 1109 |
return $array; |
| 1110 |
} |
| 1111 |
|
| 1112 |
/** |
| 1113 |
* Shapes, filtering it, an array to the specified expected set of required keys. |
| 1114 |
* |
| 1115 |
* @since 1.0.0 |
| 1116 |
* |
| 1117 |
* @param array $array The input array to shape. |
| 1118 |
* @param array $shape The shape to update the array with. It should only define keys |
| 1119 |
* or arrays of keys. Keys that have no values will be set to `null`. |
| 1120 |
* To add the key only if set, prefix the key with `?`, e.g. `?foo`. |
| 1121 |
* |
| 1122 |
* @return array The input array shaped and ordered per the shape. |
| 1123 |
*/ |
| 1124 |
public static function shape_filter( array $array, array $shape ): array { |
| 1125 |
$shaped = []; |
| 1126 |
foreach ( $shape as $shape_index => $shape_key ) { |
| 1127 |
$optional = is_array( $shape_key ) ? |
| 1128 |
strpos( $shape_index, '?' ) === 0 |
| 1129 |
: strpos( $shape_key, '?' ) === 0; |
| 1130 |
|
| 1131 |
if ( is_array( $shape_key ) ) { |
| 1132 |
$shape_index = $optional ? substr( $shape_index, 1 ) : $shape_index; |
| 1133 |
if ( $optional && ! isset( $array[ $shape_index ] ) ) { |
| 1134 |
continue; |
| 1135 |
} |
| 1136 |
$shaped[ $shape_index ] = self::shape_filter( $array[ $shape_index ] ?? [], $shape_key ); |
| 1137 |
} else { |
| 1138 |
$shape_key = $optional ? substr( $shape_key, 1 ) : $shape_key; |
| 1139 |
if ( ! isset( $array[ $shape_key ] ) && $optional ) { |
| 1140 |
continue; |
| 1141 |
} |
| 1142 |
$shaped[ $shape_key ] = $array[ $shape_key ] ?? null; |
| 1143 |
} |
| 1144 |
} |
| 1145 |
|
| 1146 |
return $shaped; |
| 1147 |
} |
| 1148 |
|
| 1149 |
/** |
| 1150 |
* Shuffle the given array and return the result. |
| 1151 |
* |
| 1152 |
* @param array $array |
| 1153 |
* @param int|null $seed |
| 1154 |
* |
| 1155 |
* @return array |
| 1156 |
*/ |
| 1157 |
public static function shuffle( $array, $seed = null ) { |
| 1158 |
if ( is_null( $seed ) ) { |
| 1159 |
shuffle( $array ); |
| 1160 |
} else { |
| 1161 |
mt_srand( $seed ); |
| 1162 |
shuffle( $array ); |
| 1163 |
mt_srand(); |
| 1164 |
} |
| 1165 |
|
| 1166 |
return $array; |
| 1167 |
} |
| 1168 |
|
| 1169 |
/** |
| 1170 |
* Sort based on Priority |
| 1171 |
* |
| 1172 |
* @since 1.0.0 |
| 1173 |
* |
| 1174 |
* @param array $array Array to sort. |
| 1175 |
* |
| 1176 |
* @return array |
| 1177 |
*/ |
| 1178 |
public static function sort_by_priority( $array ): array { |
| 1179 |
if ( ! is_array( $array ) ) { |
| 1180 |
throw new \InvalidArgumentException( 'Input must be an array' ); |
| 1181 |
} |
| 1182 |
|
| 1183 |
if ( static::is_assoc( $array ) ) { |
| 1184 |
uasort( $array, [ static::class, 'sort_by_priority_comparison' ] ); |
| 1185 |
} else { |
| 1186 |
usort( $array, [ static::class, 'sort_by_priority_comparison' ] ); |
| 1187 |
} |
| 1188 |
|
| 1189 |
return $array; |
| 1190 |
} |
| 1191 |
|
| 1192 |
/** |
| 1193 |
* Sort based on Priority |
| 1194 |
* |
| 1195 |
* @since 1.0.0 |
| 1196 |
* |
| 1197 |
* @param object|array $b Second subject to compare |
| 1198 |
* |
| 1199 |
* @param object|array $a First Subject to compare |
| 1200 |
* |
| 1201 |
* @return int |
| 1202 |
*/ |
| 1203 |
public static function sort_by_priority_comparison( $a, $b ): int { |
| 1204 |
if ( is_array( $a ) ) { |
| 1205 |
$a_priority = $a['priority']; |
| 1206 |
} else { |
| 1207 |
$a_priority = $a->priority; |
| 1208 |
} |
| 1209 |
|
| 1210 |
if ( is_array( $b ) ) { |
| 1211 |
$b_priority = $b['priority']; |
| 1212 |
} else { |
| 1213 |
$b_priority = $b->priority; |
| 1214 |
} |
| 1215 |
|
| 1216 |
if ( (int) $a_priority === (int) $b_priority ) { |
| 1217 |
return 0; |
| 1218 |
} |
| 1219 |
|
| 1220 |
return (int) $a_priority > (int) $b_priority ? 1 : -1; |
| 1221 |
} |
| 1222 |
|
| 1223 |
/** |
| 1224 |
* Recursively sort an array by keys and values. |
| 1225 |
* |
| 1226 |
* @param array $array |
| 1227 |
* @param int $options |
| 1228 |
* @param bool $descending |
| 1229 |
* |
| 1230 |
* @return array |
| 1231 |
*/ |
| 1232 |
public static function sort_recursive( $array, $options = SORT_REGULAR, $descending = false ) { |
| 1233 |
foreach ( $array as &$value ) { |
| 1234 |
if ( is_array( $value ) ) { |
| 1235 |
$value = static::sort_recursive( $value, $options, $descending ); |
| 1236 |
} |
| 1237 |
} |
| 1238 |
|
| 1239 |
if ( ! static::is_list( $array ) ) { |
| 1240 |
$descending |
| 1241 |
? krsort( $array, $options ) |
| 1242 |
: ksort( $array, $options ); |
| 1243 |
} else { |
| 1244 |
$descending |
| 1245 |
? rsort( $array, $options ) |
| 1246 |
: sort( $array, $options ); |
| 1247 |
} |
| 1248 |
|
| 1249 |
return $array; |
| 1250 |
} |
| 1251 |
|
| 1252 |
/** |
| 1253 |
* Recursively sort an array by keys and values in descending order. |
| 1254 |
* |
| 1255 |
* @param array $array |
| 1256 |
* @param int $options |
| 1257 |
* |
| 1258 |
* @return array |
| 1259 |
*/ |
| 1260 |
public static function sort_recursive_desc( $array, $options = SORT_REGULAR ) { |
| 1261 |
return static::sort_recursive( $array, $options, true ); |
| 1262 |
} |
| 1263 |
|
| 1264 |
/** |
| 1265 |
* Stringifies the numeric keys of an array. |
| 1266 |
* |
| 1267 |
* @since 1.0.0 |
| 1268 |
* |
| 1269 |
* @param array<int|string,mixed> $input The input array whose keys should be stringified. |
| 1270 |
* @param string|null $prefix The prefix that should be use to stringify the keys, if not provided |
| 1271 |
* then it will be generated. |
| 1272 |
* |
| 1273 |
* @return array<string,mixed> The input array with each numeric key stringified. |
| 1274 |
*/ |
| 1275 |
public static function stringify_keys( array $input, $prefix = null ): array { |
| 1276 |
$prefix = null === $prefix ? uniqid( 'sk_' ) : $prefix; |
| 1277 |
$visitor = static function( $key, $value ) use ( $prefix ) { |
| 1278 |
$string_key = is_numeric( $key ) ? $prefix . $key : $key; |
| 1279 |
|
| 1280 |
return [ $string_key, $value ]; |
| 1281 |
}; |
| 1282 |
|
| 1283 |
return static::array_visit_recursive( $input, $visitor ); |
| 1284 |
} |
| 1285 |
|
| 1286 |
/** |
| 1287 |
* Behaves exactly like the native strpos(), but accepts an array of needles. |
| 1288 |
* |
| 1289 |
* @see strpos() |
| 1290 |
* |
| 1291 |
* @param string $haystack String to search in. |
| 1292 |
* @param array|string $needles Strings to search for. |
| 1293 |
* @param int $offset Starting position of search. |
| 1294 |
* |
| 1295 |
* @return false|int Integer position of first needle occurrence. |
| 1296 |
*/ |
| 1297 |
public static function strpos( string $haystack, $needles, int $offset = 0 ) { |
| 1298 |
$needles = static::wrap( $needles ); |
| 1299 |
|
| 1300 |
$needles = array_filter( $needles, static function( $needle ) { |
| 1301 |
return is_string( $needle ) && $needle !== ''; |
| 1302 |
} ); |
| 1303 |
|
| 1304 |
foreach ( $needles as $i ) { |
| 1305 |
$search = strpos( $haystack, $i, $offset ); |
| 1306 |
|
| 1307 |
if ( false !== $search ) { |
| 1308 |
return $search; |
| 1309 |
} |
| 1310 |
} |
| 1311 |
|
| 1312 |
return false; |
| 1313 |
} |
| 1314 |
|
| 1315 |
/** |
| 1316 |
* Returns a list separated by the specified separator. |
| 1317 |
* |
| 1318 |
* @since 1.0.0 |
| 1319 |
* |
| 1320 |
* @param mixed $list |
| 1321 |
* @param string $sep |
| 1322 |
* |
| 1323 |
* @return string The list separated by the specified separator or the original list if the list is empty. |
| 1324 |
*/ |
| 1325 |
public static function to_list( $list, string $sep = ',' ): string { |
| 1326 |
if ( $list === null ) { |
| 1327 |
return ''; |
| 1328 |
} |
| 1329 |
|
| 1330 |
if ( empty( $list ) ) { |
| 1331 |
return is_array( $list ) ? '' : $list; |
| 1332 |
} |
| 1333 |
|
| 1334 |
if ( is_array( $list ) ) { |
| 1335 |
return implode( $sep, $list ); |
| 1336 |
} |
| 1337 |
|
| 1338 |
return $list; |
| 1339 |
} |
| 1340 |
|
| 1341 |
/** |
| 1342 |
* Convert a flatten "dot" notation array into an expanded array. |
| 1343 |
* |
| 1344 |
* @param iterable $array |
| 1345 |
* |
| 1346 |
* @return array |
| 1347 |
*/ |
| 1348 |
public static function undot( $array ) { |
| 1349 |
$results = []; |
| 1350 |
|
| 1351 |
foreach ( $array as $key => $value ) { |
| 1352 |
$results = static::set( $results, explode( '.', $key ), $value ); |
| 1353 |
} |
| 1354 |
|
| 1355 |
return $results; |
| 1356 |
} |
| 1357 |
|
| 1358 |
/** |
| 1359 |
* Searches an array using a callback and returns the index of the first match. |
| 1360 |
* |
| 1361 |
* This method fills the gap left by the non-existence of an `array_usearch` function. |
| 1362 |
* |
| 1363 |
* @since 1.0.0 |
| 1364 |
* |
| 1365 |
* @param mixed $needle The element to search in the array. |
| 1366 |
* @param array $haystack The array to search. |
| 1367 |
* @param callable $callback A callback function with signature `fn($needle, $value, $key) :bool` |
| 1368 |
* that will be used to find the first match of needle in haystack. |
| 1369 |
* |
| 1370 |
* @return string|int|false Either the index of the first match or `false` if no match was found. |
| 1371 |
*/ |
| 1372 |
public static function usearch( $needle, array $haystack, callable $callback ) { |
| 1373 |
foreach ( $haystack as $key => $value ) { |
| 1374 |
if ( $callback( $needle, $value, $key ) ) { |
| 1375 |
return $key; |
| 1376 |
} |
| 1377 |
} |
| 1378 |
|
| 1379 |
return false; |
| 1380 |
} |
| 1381 |
|
| 1382 |
/** |
| 1383 |
* Filter the array using the given callback. |
| 1384 |
* |
| 1385 |
* @param array $array |
| 1386 |
* @param callable $callback |
| 1387 |
* |
| 1388 |
* @return array |
| 1389 |
*/ |
| 1390 |
public static function where( $array, callable $callback ) { |
| 1391 |
return array_filter( $array, $callback, ARRAY_FILTER_USE_BOTH ); |
| 1392 |
} |
| 1393 |
|
| 1394 |
/** |
| 1395 |
* Filter items where the value is not null. |
| 1396 |
* |
| 1397 |
* @param array $array |
| 1398 |
* |
| 1399 |
* @return array |
| 1400 |
*/ |
| 1401 |
public static function where_not_null( $array ) { |
| 1402 |
return static::where( $array, static function( $value ) { |
| 1403 |
return ! is_null( $value ); |
| 1404 |
} ); |
| 1405 |
} |
| 1406 |
|
| 1407 |
/** |
| 1408 |
* If the given value is not an array and not null, wrap it in one. |
| 1409 |
* |
| 1410 |
* @param mixed $value |
| 1411 |
* |
| 1412 |
* @return array |
| 1413 |
*/ |
| 1414 |
public static function wrap( $value ) { |
| 1415 |
if ( is_null( $value ) ) { |
| 1416 |
return []; |
| 1417 |
} |
| 1418 |
|
| 1419 |
return is_array( $value ) ? $value : [ $value ]; |
| 1420 |
} |
| 1421 |
|
| 1422 |
/** |
| 1423 |
* Recursively computes the intersection of arrays using keys for comparison. |
| 1424 |
* |
| 1425 |
* @param mixed[] $array The array with master keys to check. |
| 1426 |
* @param mixed[] ...$arrays Additional arrays to compare keys against. |
| 1427 |
* |
| 1428 |
* @return mixed[] An associative array containing all the entries of $array |
| 1429 |
* whose keys exist in every provided array, recursively. |
| 1430 |
*/ |
| 1431 |
public static function intersect_key_recursive( array $array, array ...$arrays ): array { |
| 1432 |
$array = array_intersect_key( $array, ...$arrays ); |
| 1433 |
|
| 1434 |
foreach ( $array as $key => $value ) { |
| 1435 |
if ( ! is_array( $value ) ) { |
| 1436 |
continue; |
| 1437 |
} |
| 1438 |
|
| 1439 |
$arrays_to_intersect = []; |
| 1440 |
foreach ( $arrays as $intersect_array ) { |
| 1441 |
if ( ! isset( $intersect_array[ $key ] ) ) { |
| 1442 |
unset( $array[ $key ] ); |
| 1443 |
continue 2; |
| 1444 |
} |
| 1445 |
|
| 1446 |
if ( ! is_array( $intersect_array[ $key ] ) ) { |
| 1447 |
continue; |
| 1448 |
} |
| 1449 |
|
| 1450 |
$arrays_to_intersect[] = $intersect_array[ $key ]; |
| 1451 |
} |
| 1452 |
|
| 1453 |
if ( empty( $arrays_to_intersect ) ) { |
| 1454 |
continue; |
| 1455 |
} |
| 1456 |
|
| 1457 |
$array[ $key ] = static::intersect_key_recursive( $value, ...$arrays_to_intersect ); |
| 1458 |
} |
| 1459 |
|
| 1460 |
return $array; |
| 1461 |
} |
| 1462 |
|
| 1463 |
/** |
| 1464 |
* Return the default value of the given value. |
| 1465 |
* |
| 1466 |
* @template TValue |
| 1467 |
* @template TArgs |
| 1468 |
* |
| 1469 |
* @param TValue|\Closure(TArgs): TValue $value |
| 1470 |
* @param TArgs ...$args |
| 1471 |
* @return TValue |
| 1472 |
*/ |
| 1473 |
private static function value( $value, ...$args ) { |
| 1474 |
return $value instanceof Closure ? $value( ...$args ) : $value; |
| 1475 |
} |
| 1476 |
} |
| 1477 |
|