class-wp-list-util.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <?php
  2. /**
  3. * WordPress List utility class
  4. *
  5. * @package WordPress
  6. * @since 4.7.0
  7. */
  8. /**
  9. * List utility.
  10. *
  11. * Utility class to handle operations on an array of objects or arrays.
  12. *
  13. * @since 4.7.0
  14. */
  15. #[AllowDynamicProperties]
  16. class WP_List_Util {
  17. /**
  18. * The input array.
  19. *
  20. * @since 4.7.0
  21. * @var array
  22. */
  23. private $input = array();
  24. /**
  25. * The output array.
  26. *
  27. * @since 4.7.0
  28. * @var array
  29. */
  30. private $output = array();
  31. /**
  32. * Temporary arguments for sorting.
  33. *
  34. * @since 4.7.0
  35. * @var string[]
  36. */
  37. private $orderby = array();
  38. /**
  39. * Constructor.
  40. *
  41. * Sets the input array.
  42. *
  43. * @since 4.7.0
  44. *
  45. * @param array $input Array to perform operations on.
  46. */
  47. public function __construct( $input ) {
  48. $this->output = $input;
  49. $this->input = $input;
  50. }
  51. /**
  52. * Returns the original input array.
  53. *
  54. * @since 4.7.0
  55. *
  56. * @return array The input array.
  57. */
  58. public function get_input() {
  59. return $this->input;
  60. }
  61. /**
  62. * Returns the output array.
  63. *
  64. * @since 4.7.0
  65. *
  66. * @return array The output array.
  67. */
  68. public function get_output() {
  69. return $this->output;
  70. }
  71. /**
  72. * Filters the list, based on a set of key => value arguments.
  73. *
  74. * Retrieves the objects from the list that match the given arguments.
  75. * Key represents property name, and value represents property value.
  76. *
  77. * If an object has more properties than those specified in arguments,
  78. * that will not disqualify it. When using the 'AND' operator,
  79. * any missing properties will disqualify it.
  80. *
  81. * @since 4.7.0
  82. *
  83. * @param array $args Optional. An array of key => value arguments to match
  84. * against each object. Default empty array.
  85. * @param string $operator Optional. The logical operation to perform. 'AND' means
  86. * all elements from the array must match. 'OR' means only
  87. * one element needs to match. 'NOT' means no elements may
  88. * match. Default 'AND'.
  89. * @return array Array of found values.
  90. */
  91. public function filter( $args = array(), $operator = 'AND' ) {
  92. if ( empty( $args ) ) {
  93. return $this->output;
  94. }
  95. $operator = strtoupper( $operator );
  96. if ( ! in_array( $operator, array( 'AND', 'OR', 'NOT' ), true ) ) {
  97. $this->output = array();
  98. return $this->output;
  99. }
  100. $count = count( $args );
  101. $filtered = array();
  102. foreach ( $this->output as $key => $obj ) {
  103. $matched = 0;
  104. foreach ( $args as $m_key => $m_value ) {
  105. if ( is_array( $obj ) ) {
  106. // Treat object as an array.
  107. if ( array_key_exists( $m_key, $obj ) && ( $m_value == $obj[ $m_key ] ) ) {
  108. $matched++;
  109. }
  110. } elseif ( is_object( $obj ) ) {
  111. // Treat object as an object.
  112. if ( isset( $obj->{$m_key} ) && ( $m_value == $obj->{$m_key} ) ) {
  113. $matched++;
  114. }
  115. }
  116. }
  117. if ( ( 'AND' === $operator && $matched === $count )
  118. || ( 'OR' === $operator && $matched > 0 )
  119. || ( 'NOT' === $operator && 0 === $matched )
  120. ) {
  121. $filtered[ $key ] = $obj;
  122. }
  123. }
  124. $this->output = $filtered;
  125. return $this->output;
  126. }
  127. /**
  128. * Plucks a certain field out of each element in the input array.
  129. *
  130. * This has the same functionality and prototype of
  131. * array_column() (PHP 5.5) but also supports objects.
  132. *
  133. * @since 4.7.0
  134. *
  135. * @param int|string $field Field to fetch from the object or array.
  136. * @param int|string $index_key Optional. Field from the element to use as keys for the new array.
  137. * Default null.
  138. * @return array Array of found values. If `$index_key` is set, an array of found values with keys
  139. * corresponding to `$index_key`. If `$index_key` is null, array keys from the original
  140. * `$list` will be preserved in the results.
  141. */
  142. public function pluck( $field, $index_key = null ) {
  143. $newlist = array();
  144. if ( ! $index_key ) {
  145. /*
  146. * This is simple. Could at some point wrap array_column()
  147. * if we knew we had an array of arrays.
  148. */
  149. foreach ( $this->output as $key => $value ) {
  150. if ( is_object( $value ) ) {
  151. $newlist[ $key ] = $value->$field;
  152. } else {
  153. $newlist[ $key ] = $value[ $field ];
  154. }
  155. }
  156. $this->output = $newlist;
  157. return $this->output;
  158. }
  159. /*
  160. * When index_key is not set for a particular item, push the value
  161. * to the end of the stack. This is how array_column() behaves.
  162. */
  163. foreach ( $this->output as $value ) {
  164. if ( is_object( $value ) ) {
  165. if ( isset( $value->$index_key ) ) {
  166. $newlist[ $value->$index_key ] = $value->$field;
  167. } else {
  168. $newlist[] = $value->$field;
  169. }
  170. } else {
  171. if ( isset( $value[ $index_key ] ) ) {
  172. $newlist[ $value[ $index_key ] ] = $value[ $field ];
  173. } else {
  174. $newlist[] = $value[ $field ];
  175. }
  176. }
  177. }
  178. $this->output = $newlist;
  179. return $this->output;
  180. }
  181. /**
  182. * Sorts the input array based on one or more orderby arguments.
  183. *
  184. * @since 4.7.0
  185. *
  186. * @param string|array $orderby Optional. Either the field name to order by or an array
  187. * of multiple orderby fields as $orderby => $order.
  188. * @param string $order Optional. Either 'ASC' or 'DESC'. Only used if $orderby
  189. * is a string.
  190. * @param bool $preserve_keys Optional. Whether to preserve keys. Default false.
  191. * @return array The sorted array.
  192. */
  193. public function sort( $orderby = array(), $order = 'ASC', $preserve_keys = false ) {
  194. if ( empty( $orderby ) ) {
  195. return $this->output;
  196. }
  197. if ( is_string( $orderby ) ) {
  198. $orderby = array( $orderby => $order );
  199. }
  200. foreach ( $orderby as $field => $direction ) {
  201. $orderby[ $field ] = 'DESC' === strtoupper( $direction ) ? 'DESC' : 'ASC';
  202. }
  203. $this->orderby = $orderby;
  204. if ( $preserve_keys ) {
  205. uasort( $this->output, array( $this, 'sort_callback' ) );
  206. } else {
  207. usort( $this->output, array( $this, 'sort_callback' ) );
  208. }
  209. $this->orderby = array();
  210. return $this->output;
  211. }
  212. /**
  213. * Callback to sort an array by specific fields.
  214. *
  215. * @since 4.7.0
  216. *
  217. * @see WP_List_Util::sort()
  218. *
  219. * @param object|array $a One object to compare.
  220. * @param object|array $b The other object to compare.
  221. * @return int 0 if both objects equal. -1 if second object should come first, 1 otherwise.
  222. */
  223. private function sort_callback( $a, $b ) {
  224. if ( empty( $this->orderby ) ) {
  225. return 0;
  226. }
  227. $a = (array) $a;
  228. $b = (array) $b;
  229. foreach ( $this->orderby as $field => $direction ) {
  230. if ( ! isset( $a[ $field ] ) || ! isset( $b[ $field ] ) ) {
  231. continue;
  232. }
  233. if ( $a[ $field ] == $b[ $field ] ) {
  234. continue;
  235. }
  236. $results = 'DESC' === $direction ? array( 1, -1 ) : array( -1, 1 );
  237. if ( is_numeric( $a[ $field ] ) && is_numeric( $b[ $field ] ) ) {
  238. return ( $a[ $field ] < $b[ $field ] ) ? $results[0] : $results[1];
  239. }
  240. return 0 > strcmp( $a[ $field ], $b[ $field ] ) ? $results[0] : $results[1];
  241. }
  242. return 0;
  243. }
  244. }