Make WordPress Core

Ticket #59774: class-wp-list-util.php

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