You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

268 line
6.3 KiB

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