Нема описа

Array.php 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /*******************************************************************************
  3. * Copyright (c) 2019, Code Atlantic LLC
  4. ******************************************************************************/
  5. if ( ! defined( 'ABSPATH' ) ) {
  6. exit;
  7. }
  8. /**
  9. * Class PUM_Utils_Array
  10. *
  11. * Various functions to help manipulating arrays.
  12. */
  13. class PUM_Utils_Array {
  14. /**
  15. * Filters out null values.
  16. *
  17. * @param array $array
  18. *
  19. * @return array
  20. */
  21. public static function filter_null( $array = [] ) {
  22. return array_filter( $array, [ __CLASS__, '_filter_null' ] );
  23. }
  24. /**
  25. * @param null $val
  26. *
  27. * @return bool
  28. */
  29. public static function _filter_null( $val = null ) {
  30. return isset( $val );
  31. }
  32. /**
  33. * Clean variables using sanitize_text_field.
  34. *
  35. * @param $var
  36. *
  37. * @return array|string
  38. */
  39. public static function sanitize( $var ) {
  40. if ( is_string( $var ) ) {
  41. return sanitize_text_field( $var );
  42. }
  43. return array_map( [ __CLASS__, 'sanitize' ], (array) $var );
  44. }
  45. /**
  46. * Helper function to move or swap array keys in various ways.
  47. *
  48. * PUM_Utils_Array::move_item($arr, 'move me', 'up'); //move it one up
  49. * PUM_Utils_Array::move_item($arr, 'move me', 'down'); //move it one down
  50. * PUM_Utils_Array::move_item($arr, 'move me', 'top'); //move it to top
  51. * PUM_Utils_Array::move_item($arr, 'move me', 'bottom'); //move it to bottom
  52. *
  53. * PUM_Utils_Array::move_item($arr, 'move me', -1); //move it one up
  54. * PUM_Utils_Array::move_item($arr, 'move me', 1); //move it one down
  55. * PUM_Utils_Array::move_item($arr, 'move me', 2); //move it two down
  56. *
  57. * PUM_Utils_Array::move_item($arr, 'move me', 'before', 'b'); //move it before ['b']
  58. * PUM_Utils_Array::move_item($arr, 'move me', 'up', 'b'); //move it before ['b']
  59. * PUM_Utils_Array::move_item($arr, 'move me', -1, 'b'); //move it before ['b']
  60. * PUM_Utils_Array::move_item($arr, 'move me', 'after', 'b'); //move it after ['b']
  61. * PUM_Utils_Array::move_item($arr, 'move me', 'down', 'b'); //move it after ['b']
  62. * PUM_Utils_Array::move_item($arr, 'move me', 1, 'b'); //move it after ['b']
  63. * PUM_Utils_Array::move_item($arr, 'move me', 2, 'b'); //move it two positions after ['b']
  64. *
  65. * Special syntax, to swap two elements:
  66. * PUM_Utils_Array::move_item($arr, 'a', 0, 'd'); //Swap ['a'] with ['d']
  67. * PUM_Utils_Array::move_item($arr, 'a', 'swap', 'd'); //Swap ['a'] with ['d']
  68. *
  69. * @param array $ref_arr
  70. * @param string $key1
  71. * @param int|string $move
  72. * @param string|null $key2
  73. *
  74. * @return bool
  75. */
  76. public static function move_item( &$ref_arr, $key1, $move, $key2 = null ) {
  77. $arr = $ref_arr;
  78. if ( $key2 == null ) {
  79. $key2 = $key1;
  80. }
  81. if ( ! isset( $arr[ $key1 ] ) || ! isset( $arr[ $key2 ] ) ) {
  82. return false;
  83. }
  84. $i = 0;
  85. foreach ( $arr as &$val ) {
  86. $val = [
  87. 'sort' => ( ++ $i * 10 ),
  88. 'val' => $val,
  89. ];
  90. }
  91. // Add a quick keyword `swap` to make syntax simpler to remember.
  92. if ( 'swap' === $move ) {
  93. $move = 0;
  94. }
  95. if ( is_numeric( $move ) ) {
  96. if ( $move == 0 && $key1 == $key2 ) {
  97. return true;
  98. } elseif ( $move == 0 ) {
  99. $tmp = $arr[ $key1 ]['sort'];
  100. $arr[ $key1 ]['sort'] = $arr[ $key2 ]['sort'];
  101. $arr[ $key2 ]['sort'] = $tmp;
  102. } else {
  103. $arr[ $key1 ]['sort'] = $arr[ $key2 ]['sort'] + ( $move * 10 + ( $key1 == $key2 ? ( $move < 0 ? - 5 : 5 ) : 0 ) );
  104. }
  105. } else {
  106. switch ( $move ) {
  107. case 'up':
  108. case 'before':
  109. $arr[ $key1 ]['sort'] = $arr[ $key2 ]['sort'] - ( $key1 == $key2 ? 15 : 5 );
  110. break;
  111. case 'down':
  112. case 'after':
  113. $arr[ $key1 ]['sort'] = $arr[ $key2 ]['sort'] + ( $key1 == $key2 ? 15 : 5 );
  114. break;
  115. case 'top':
  116. $arr[ $key1 ]['sort'] = 5;
  117. break;
  118. case 'bottom':
  119. $arr[ $key1 ]['sort'] = $i * 10 + 5;
  120. break;
  121. default:
  122. return false;
  123. }
  124. }
  125. uasort( $arr, [ __CLASS__, 'sort_by_sort' ] );
  126. foreach ( $arr as &$val ) {
  127. $val = $val['val'];
  128. }
  129. $ref_arr = $arr;
  130. return true;
  131. }
  132. /**
  133. * Pluck all array keys beginning with string.
  134. *
  135. * @param array $array
  136. * @param bool|string|array $strings
  137. *
  138. * @return array
  139. */
  140. public static function pluck_keys_starting_with( $array, $strings = [] ) {
  141. $to_be_removed = self::remove_keys_starting_with( $array, $strings );
  142. return array_diff_key( $array, $to_be_removed );
  143. }
  144. /**
  145. * Pluck all array keys ending with string.
  146. *
  147. * @param array $array
  148. * @param bool|string|array $strings
  149. *
  150. * @return array
  151. */
  152. public static function pluck_keys_ending_with( $array, $strings = [] ) {
  153. $to_be_removed = self::remove_keys_ending_with( $array, $strings );
  154. return array_diff_key( $array, $to_be_removed );
  155. }
  156. /**
  157. * Extract only allowed keys from an array.
  158. *
  159. * @param array $array Array to be extracted from.
  160. * @param string[] $allowed_keys List of keys.
  161. *
  162. * @return array
  163. */
  164. public static function allowed_keys( $array, $allowed_keys = [] ) {
  165. return array_intersect_key( $array, array_flip( $allowed_keys ) );
  166. }
  167. /**
  168. * This works exactly the same as wp_parse_args, except we remove unused keys for sanitization.
  169. *
  170. * @param array $array Array to be parsed.
  171. * @param array $allowed_args Array of key=>defaultValue pairs for each allowed argument.
  172. *
  173. * @return array
  174. */
  175. public static function parse_allowed_args( $array, $allowed_args = [] ) {
  176. $array = wp_parse_args( $array, $allowed_args );
  177. return self::allowed_keys( $array, array_keys( $allowed_args ) );
  178. }
  179. /**
  180. * Pluck specified array keys.
  181. *
  182. * @param array $array
  183. * @param string[] $keys
  184. *
  185. * @return array
  186. */
  187. public static function pluck( $array, $keys = [] ) {
  188. return self::pluck_keys_containing( $array, $keys );
  189. }
  190. /**
  191. * Pluck all array keys containing a string or strings.
  192. *
  193. * @param array $array
  194. * @param string[] $strings
  195. *
  196. * @return array
  197. */
  198. public static function pluck_keys_containing( $array, $strings = [] ) {
  199. $to_be_removed = self::remove_keys_containing( $array, $strings );
  200. return array_diff_key( $array, $to_be_removed );
  201. }
  202. /**
  203. * Remove all array keys beginning with string.
  204. *
  205. * @param array $array
  206. * @param string[] $strings
  207. *
  208. * @return array
  209. */
  210. public static function remove_keys_starting_with( $array, $strings = [] ) {
  211. if ( ! $strings ) {
  212. return $array;
  213. }
  214. if ( ! is_array( $strings ) ) {
  215. $strings = [ $strings ];
  216. }
  217. foreach ( $array as $key => $value ) {
  218. foreach ( $strings as $string ) {
  219. if ( strpos( $key, $string ) === 0 ) {
  220. unset( $array[ $key ] );
  221. }
  222. }
  223. }
  224. return $array;
  225. }
  226. /**
  227. * Remove all array keys ending with string.
  228. *
  229. * @param array $array
  230. * @param bool|string|array $strings
  231. *
  232. * @return array
  233. */
  234. public static function remove_keys_ending_with( $array, $strings = [] ) {
  235. if ( ! $strings ) {
  236. return $array;
  237. }
  238. if ( ! is_array( $strings ) ) {
  239. $strings = [ $strings ];
  240. }
  241. foreach ( $array as $key => $value ) {
  242. foreach ( $strings as $string ) {
  243. $length = strlen( $string );
  244. if ( substr( $key, - $length ) === $string ) {
  245. unset( $array[ $key ] );
  246. }
  247. }
  248. }
  249. return $array;
  250. }
  251. /**
  252. * Remove all array keys containing string.
  253. *
  254. * @param array $array
  255. * @param bool|string|array $strings
  256. *
  257. * @return array
  258. */
  259. public static function remove_keys_containing( $array, $strings = [] ) {
  260. if ( ! $strings ) {
  261. return $array;
  262. }
  263. if ( ! is_array( $strings ) ) {
  264. $strings = [ $strings ];
  265. }
  266. foreach ( $array as $key => $value ) {
  267. foreach ( $strings as $string ) {
  268. if ( strpos( $key, $string ) !== false ) {
  269. unset( $array[ $key ] );
  270. }
  271. }
  272. }
  273. return $array;
  274. }
  275. /**
  276. * Remove all array keys containing string.
  277. *
  278. * @param array $array
  279. * @param string|array $keys
  280. *
  281. * @return array
  282. */
  283. public static function remove_keys( $array, $keys = [] ) {
  284. if ( empty( $keys ) ) {
  285. return $array;
  286. }
  287. if ( is_string( $keys ) ) {
  288. $keys = [ $keys ];
  289. }
  290. foreach ( (array) $keys as $key ) {
  291. if ( is_string( $key ) && array_key_exists( $key, $array ) ) {
  292. unset( $array[ $key ] );
  293. }
  294. }
  295. return $array;
  296. }
  297. /**
  298. * Sort nested arrays with various options.
  299. *
  300. * @param array $array
  301. * @param string $type
  302. * @param bool $reverse
  303. *
  304. * @return array
  305. */
  306. public static function sort( $array = [], $type = 'key', $reverse = false ) {
  307. if ( ! is_array( $array ) ) {
  308. return $array;
  309. }
  310. switch ( $type ) {
  311. case 'key':
  312. if ( ! $reverse ) {
  313. ksort( $array );
  314. } else {
  315. krsort( $array );
  316. }
  317. break;
  318. case 'natural':
  319. natsort( $array );
  320. break;
  321. case 'priority':
  322. if ( ! $reverse ) {
  323. uasort( $array, [ __CLASS__, 'sort_by_priority' ] );
  324. } else {
  325. uasort( $array, [ __CLASS__, 'rsort_by_priority' ] );
  326. }
  327. break;
  328. }
  329. return $array;
  330. }
  331. /**
  332. * @param $a
  333. * @param $b
  334. *
  335. * @return bool
  336. */
  337. public static function sort_by_sort( $a, $b ) {
  338. return $a['sort'] > $b['sort'];
  339. }
  340. /**
  341. * Sort array by priority value
  342. *
  343. * @param $a
  344. * @param $b
  345. *
  346. * @return int
  347. */
  348. public static function sort_by_priority( $a, $b ) {
  349. $pri_a = isset( $a['pri'] ) ? $a['pri'] : ( isset( $a['priority'] ) ? $a['priority'] : false );
  350. $pri_b = isset( $b['pri'] ) ? $b['pri'] : ( isset( $b['priority'] ) ? $b['priority'] : false );
  351. if ( ! is_numeric( $pri_a ) || ! is_numeric( $pri_b ) || $pri_a === $pri_b ) {
  352. return 0;
  353. }
  354. return ( $pri_a < $pri_b ) ? - 1 : 1;
  355. }
  356. /**
  357. * Sort array in reverse by priority value
  358. *
  359. * @param $a
  360. * @param $b
  361. *
  362. * @return int
  363. */
  364. public static function rsort_by_priority( $a, $b ) {
  365. $pri_a = isset( $a['pri'] ) ? $a['pri'] : ( isset( $a['priority'] ) ? $a['priority'] : false );
  366. $pri_b = isset( $b['pri'] ) ? $b['pri'] : ( isset( $b['priority'] ) ? $b['priority'] : false );
  367. if ( ! is_numeric( $pri_a ) || ! is_numeric( $pri_b ) || $pri_a === $pri_b ) {
  368. return 0;
  369. }
  370. return ( $pri_a < $pri_b ) ? 1 : - 1;
  371. }
  372. /**
  373. * Replace array key with new key name in same order
  374. *
  375. * @param $array
  376. * @param $old_key
  377. * @param $new_key
  378. *
  379. * @return array
  380. */
  381. public static function replace_key( $array, $old_key, $new_key ) {
  382. $keys = array_keys( $array );
  383. if ( false === $index = array_search( $old_key, $keys, true ) ) {
  384. // throw new \Exception( sprintf( 'Key "%s" does not exit', $old_key ) );
  385. }
  386. $keys[ $index ] = $new_key;
  387. return array_combine( $keys, array_values( $array ) );
  388. }
  389. /**
  390. * Converts 'false' & 'true' string values in any array to proper boolean values.
  391. *
  392. * @param array|mixed $data
  393. *
  394. * @return array|mixed
  395. */
  396. public static function fix_json_boolean_values( $data ) {
  397. if ( is_array( $data ) ) {
  398. foreach ( (array) $data as $key => $value ) {
  399. if ( is_string( $value ) && in_array( $value, [ 'true', 'false' ] ) ) {
  400. $data[ $key ] = json_decode( $value );
  401. } elseif ( is_array( $value ) ) {
  402. $data[ $key ] = self::fix_json_boolean_values( $value );
  403. }
  404. }
  405. }
  406. return $data;
  407. }
  408. /**
  409. * @param $obj
  410. *
  411. * @return array
  412. */
  413. public static function from_object( $obj ) {
  414. if ( is_object( $obj ) ) {
  415. $obj = (array) $obj;
  416. }
  417. if ( is_array( $obj ) ) {
  418. $new = [];
  419. foreach ( $obj as $key => $val ) {
  420. $new[ $key ] = self::from_object( $val );
  421. }
  422. } else {
  423. $new = $obj;
  424. }
  425. return $new;
  426. }
  427. /**
  428. * @param $array
  429. *
  430. * @return array
  431. */
  432. public static function safe_json_decode( $array ) {
  433. if ( ! empty( $array ) && is_string( $array ) ) {
  434. if ( strpos( $array, '\"' ) >= 0 ) {
  435. $array = stripslashes( $array );
  436. }
  437. $array = json_decode( $array );
  438. $array = self::from_object( $array );
  439. $array = self::fix_json_boolean_values( $array );
  440. }
  441. return (array) $array;
  442. }
  443. /**
  444. * Ensures proper encoding for strings before json_encode is used.
  445. *
  446. * @param array|string $data
  447. *
  448. * @return mixed|string
  449. */
  450. public static function safe_json_encode( $data = [] ) {
  451. return wp_json_encode( self::make_safe_for_json_encode( $data ) );
  452. }
  453. /**
  454. * json_encode only accepts valid UTF8 characters, thus we need to properly convert translations and other data to proper utf.
  455. *
  456. * This function does that recursively.
  457. *
  458. * @param array|string $data
  459. *
  460. * @return array|string
  461. */
  462. public static function make_safe_for_json_encode( $data = [] ) {
  463. if ( is_scalar( $data ) ) {
  464. return html_entity_decode( (string) $data, ENT_QUOTES, 'UTF-8' );
  465. }
  466. if ( is_array( $data ) ) {
  467. foreach ( (array) $data as $key => $value ) {
  468. if ( is_scalar( $value ) && ! is_bool( $value ) ) {
  469. $data[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
  470. } elseif ( is_array( $value ) ) {
  471. $data[ $key ] = self::make_safe_for_json_encode( $value );
  472. }
  473. }
  474. }
  475. return $data;
  476. }
  477. /**
  478. * @param $d
  479. *
  480. * @return array|string
  481. */
  482. public static function utf8_encode_recursive( $d ) {
  483. if ( is_array( $d ) ) {
  484. foreach ( $d as $k => $v ) {
  485. $d[ $k ] = self::utf8_encode_recursive( $v );
  486. }
  487. } elseif ( is_string( $d ) ) {
  488. return utf8_encode( $d );
  489. }
  490. return $d;
  491. }
  492. /**
  493. * @param $value
  494. * @param bool $encode
  495. *
  496. * @return string
  497. */
  498. public static function maybe_json_attr( $value, $encode = false ) {
  499. if ( is_object( $value ) || is_array( $value ) ) {
  500. return $encode ? htmlspecialchars( json_encode( $value ) ) : json_encode( $value );
  501. }
  502. return $value;
  503. }
  504. /**
  505. * Remaps array keys.
  506. *
  507. * @param array $array an array values.
  508. * @param array $remap_array an array of $old_key => $new_key values.
  509. *
  510. * @return array
  511. */
  512. public static function remap_keys( $array, $remap_array = [] ) {
  513. foreach ( $remap_array as $old_key => $new_key ) {
  514. $value = isset( $array[ $old_key ] ) ? $array[ $old_key ] : false;
  515. if ( ! empty( $value ) ) {
  516. $array[ $new_key ] = $value;
  517. }
  518. unset( $array[ $old_key ] );
  519. }
  520. return $array;
  521. }
  522. }