|
1 <?php |
|
2 /** |
|
3 * WordPress List utility class |
|
4 * |
|
5 * @package WordPress |
|
6 * @since 4.7.0 |
|
7 */ |
|
8 |
|
9 /** |
|
10 * List utility. |
|
11 * |
|
12 * Utility class to handle operations on an array of objects. |
|
13 * |
|
14 * @since 4.7.0 |
|
15 */ |
|
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 /** |
|
26 * The output array. |
|
27 * |
|
28 * @since 4.7.0 |
|
29 * @var array |
|
30 */ |
|
31 private $output = array(); |
|
32 |
|
33 /** |
|
34 * Temporary arguments for sorting. |
|
35 * |
|
36 * @since 4.7.0 |
|
37 * @var array |
|
38 */ |
|
39 private $orderby = array(); |
|
40 |
|
41 /** |
|
42 * Constructor. |
|
43 * |
|
44 * Sets the input array. |
|
45 * |
|
46 * @since 4.7.0 |
|
47 * |
|
48 * @param array $input Array to perform operations on. |
|
49 */ |
|
50 public function __construct( $input ) { |
|
51 $this->output = $this->input = $input; |
|
52 } |
|
53 |
|
54 /** |
|
55 * Returns the original input array. |
|
56 * |
|
57 * @since 4.7.0 |
|
58 * |
|
59 * @return array The input array. |
|
60 */ |
|
61 public function get_input() { |
|
62 return $this->input; |
|
63 } |
|
64 |
|
65 /** |
|
66 * Returns the output array. |
|
67 * |
|
68 * @since 4.7.0 |
|
69 * |
|
70 * @return array The output array. |
|
71 */ |
|
72 public function get_output() { |
|
73 return $this->output; |
|
74 } |
|
75 |
|
76 /** |
|
77 * Filters the list, based on a set of key => value arguments. |
|
78 * |
|
79 * @since 4.7.0 |
|
80 * |
|
81 * @param array $args Optional. An array of key => value arguments to match |
|
82 * against each object. Default empty array. |
|
83 * @param string $operator Optional. The logical operation to perform. 'AND' means |
|
84 * all elements from the array must match. 'OR' means only |
|
85 * one element needs to match. 'NOT' means no elements may |
|
86 * match. Default 'AND'. |
|
87 * @return array Array of found values. |
|
88 */ |
|
89 public function filter( $args = array(), $operator = 'AND' ) { |
|
90 if ( empty( $args ) ) { |
|
91 return $this->output; |
|
92 } |
|
93 |
|
94 $operator = strtoupper( $operator ); |
|
95 |
|
96 if ( ! in_array( $operator, array( 'AND', 'OR', 'NOT' ), true ) ) { |
|
97 return array(); |
|
98 } |
|
99 |
|
100 $count = count( $args ); |
|
101 $filtered = array(); |
|
102 |
|
103 foreach ( $this->output as $key => $obj ) { |
|
104 $to_match = (array) $obj; |
|
105 |
|
106 $matched = 0; |
|
107 foreach ( $args as $m_key => $m_value ) { |
|
108 if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] ) { |
|
109 $matched++; |
|
110 } |
|
111 } |
|
112 |
|
113 if ( |
|
114 ( 'AND' == $operator && $matched == $count ) || |
|
115 ( 'OR' == $operator && $matched > 0 ) || |
|
116 ( 'NOT' == $operator && 0 == $matched ) |
|
117 ) { |
|
118 $filtered[$key] = $obj; |
|
119 } |
|
120 } |
|
121 |
|
122 $this->output = $filtered; |
|
123 |
|
124 return $this->output; |
|
125 } |
|
126 |
|
127 /** |
|
128 * Plucks a certain field out of each object in the list. |
|
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 from the object to place instead of the entire object |
|
136 * @param int|string $index_key Optional. Field from the object 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 if ( ! $index_key ) { |
|
144 /* |
|
145 * This is simple. Could at some point wrap array_column() |
|
146 * if we knew we had an array of arrays. |
|
147 */ |
|
148 foreach ( $this->output as $key => $value ) { |
|
149 if ( is_object( $value ) ) { |
|
150 $this->output[ $key ] = $value->$field; |
|
151 } else { |
|
152 $this->output[ $key ] = $value[ $field ]; |
|
153 } |
|
154 } |
|
155 return $this->output; |
|
156 } |
|
157 |
|
158 /* |
|
159 * When index_key is not set for a particular item, push the value |
|
160 * to the end of the stack. This is how array_column() behaves. |
|
161 */ |
|
162 $newlist = array(); |
|
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 |
|
179 $this->output = $newlist; |
|
180 |
|
181 return $this->output; |
|
182 } |
|
183 |
|
184 /** |
|
185 * Sorts the list, based on one or more orderby arguments. |
|
186 * |
|
187 * @since 4.7.0 |
|
188 * |
|
189 * @param string|array $orderby Optional. Either the field name to order by or an array |
|
190 * of multiple orderby fields as $orderby => $order. |
|
191 * @param string $order Optional. Either 'ASC' or 'DESC'. Only used if $orderby |
|
192 * is a string. |
|
193 * @param bool $preserve_keys Optional. Whether to preserve keys. Default false. |
|
194 * @return array The sorted array. |
|
195 */ |
|
196 public function sort( $orderby = array(), $order = 'ASC', $preserve_keys = false ) { |
|
197 if ( empty( $orderby ) ) { |
|
198 return $this->output; |
|
199 } |
|
200 |
|
201 if ( is_string( $orderby ) ) { |
|
202 $orderby = array( $orderby => $order ); |
|
203 } |
|
204 |
|
205 foreach ( $orderby as $field => $direction ) { |
|
206 $orderby[ $field ] = 'DESC' === strtoupper( $direction ) ? 'DESC' : 'ASC'; |
|
207 } |
|
208 |
|
209 $this->orderby = $orderby; |
|
210 |
|
211 if ( $preserve_keys ) { |
|
212 uasort( $this->output, array( $this, 'sort_callback' ) ); |
|
213 } else { |
|
214 usort( $this->output, array( $this, 'sort_callback' ) ); |
|
215 } |
|
216 |
|
217 $this->orderby = array(); |
|
218 |
|
219 return $this->output; |
|
220 } |
|
221 |
|
222 /** |
|
223 * Callback to sort the list by specific fields. |
|
224 * |
|
225 * @since 4.7.0 |
|
226 * |
|
227 * @see WP_List_Util::sort() |
|
228 * |
|
229 * @param object|array $a One object to compare. |
|
230 * @param object|array $b The other object to compare. |
|
231 * @return int 0 if both objects equal. -1 if second object should come first, 1 otherwise. |
|
232 */ |
|
233 private function sort_callback( $a, $b ) { |
|
234 if ( empty( $this->orderby ) ) { |
|
235 return 0; |
|
236 } |
|
237 |
|
238 $a = (array) $a; |
|
239 $b = (array) $b; |
|
240 |
|
241 foreach ( $this->orderby as $field => $direction ) { |
|
242 if ( ! isset( $a[ $field ] ) || ! isset( $b[ $field ] ) ) { |
|
243 continue; |
|
244 } |
|
245 |
|
246 if ( $a[ $field ] == $b[ $field ] ) { |
|
247 continue; |
|
248 } |
|
249 |
|
250 $results = 'DESC' === $direction ? array( 1, -1 ) : array( -1, 1 ); |
|
251 |
|
252 if ( is_numeric( $a[ $field ] ) && is_numeric( $b[ $field ] ) ) { |
|
253 return ( $a[ $field ] < $b[ $field ] ) ? $results[0] : $results[1]; |
|
254 } |
|
255 |
|
256 return 0 > strcmp( $a[ $field ], $b[ $field ] ) ? $results[0] : $results[1]; |
|
257 } |
|
258 |
|
259 return 0; |
|
260 } |
|
261 } |