~canonical-sysadmins/wordpress/4.8.1

« back to all changes in this revision

Viewing changes to wp-includes/class-wp-network-query.php

  • Committer: Barry Price
  • Date: 2016-08-17 04:50:12 UTC
  • mfrom: (1.1.18 upstream)
  • Revision ID: barry.price@canonical.com-20160817045012-qfui81zhqnqv2ba9
Merge WP4.6 from upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Network API: WP_Network_Query class
 
4
 *
 
5
 * @package WordPress
 
6
 * @subpackage Multisite
 
7
 * @since 4.6.0
 
8
 */
 
9
 
 
10
/**
 
11
 * Core class used for querying networks.
 
12
 *
 
13
 * @since 4.6.0
 
14
 *
 
15
 * @see WP_Network_Query::__construct() for accepted arguments.
 
16
 */
 
17
class WP_Network_Query {
 
18
 
 
19
        /**
 
20
         * SQL for database query.
 
21
         *
 
22
         * @since 4.6.0
 
23
         * @access public
 
24
         * @var string
 
25
         */
 
26
        public $request;
 
27
 
 
28
        /**
 
29
         * SQL query clauses.
 
30
         *
 
31
         * @since 4.6.0
 
32
         * @access protected
 
33
         * @var array
 
34
         */
 
35
        protected $sql_clauses = array(
 
36
                'select'  => '',
 
37
                'from'    => '',
 
38
                'where'   => array(),
 
39
                'groupby' => '',
 
40
                'orderby' => '',
 
41
                'limits'  => '',
 
42
        );
 
43
 
 
44
        /**
 
45
         * Query vars set by the user.
 
46
         *
 
47
         * @since 4.6.0
 
48
         * @access public
 
49
         * @var array
 
50
         */
 
51
        public $query_vars;
 
52
 
 
53
        /**
 
54
         * Default values for query vars.
 
55
         *
 
56
         * @since 4.6.0
 
57
         * @access public
 
58
         * @var array
 
59
         */
 
60
        public $query_var_defaults;
 
61
 
 
62
        /**
 
63
         * List of networks located by the query.
 
64
         *
 
65
         * @since 4.6.0
 
66
         * @access public
 
67
         * @var array
 
68
         */
 
69
        public $networks;
 
70
 
 
71
        /**
 
72
         * The amount of found networks for the current query.
 
73
         *
 
74
         * @since 4.6.0
 
75
         * @access public
 
76
         * @var int
 
77
         */
 
78
        public $found_networks = 0;
 
79
 
 
80
        /**
 
81
         * The number of pages.
 
82
         *
 
83
         * @since 4.6.0
 
84
         * @access public
 
85
         * @var int
 
86
         */
 
87
        public $max_num_pages = 0;
 
88
 
 
89
        /**
 
90
         * Constructor.
 
91
         *
 
92
         * Sets up the network query, based on the query vars passed.
 
93
         *
 
94
         * @since 4.6.0
 
95
         * @access public
 
96
         *
 
97
         * @param string|array $query {
 
98
         *     Optional. Array or query string of network query parameters. Default empty.
 
99
         *
 
100
         *     @type array        $network__in          Array of network IDs to include. Default empty.
 
101
         *     @type array        $network__not_in      Array of network IDs to exclude. Default empty.
 
102
         *     @type bool         $count                Whether to return a network count (true) or array of network objects.
 
103
         *                                              Default false.
 
104
         *     @type string       $fields               Network fields to return. Accepts 'ids' (returns an array of network IDs)
 
105
         *                                              or empty (returns an array of complete network objects). Default empty.
 
106
         *     @type int          $number               Maximum number of networks to retrieve. Default null (no limit).
 
107
         *     @type int          $offset               Number of networks to offset the query. Used to build LIMIT clause.
 
108
         *                                              Default 0.
 
109
         *     @type bool         $no_found_rows        Whether to disable the `SQL_CALC_FOUND_ROWS` query. Default true.
 
110
         *     @type string|array $orderby              Network status or array of statuses. Accepts 'id', 'domain', 'path',
 
111
         *                                              'domain_length', 'path_length' and 'network__in'. Also accepts false,
 
112
         *                                              an empty array, or 'none' to disable `ORDER BY` clause. Default 'id'.
 
113
         *     @type string       $order                How to order retrieved networks. Accepts 'ASC', 'DESC'. Default 'ASC'.
 
114
         *     @type string       $domain               Limit results to those affiliated with a given network ID.
 
115
         *                                              Default current network ID.
 
116
         *     @type array        $domain__in           Array of domains to include affiliated networks for. Default empty.
 
117
         *     @type array        $domain__not_in       Array of domains to exclude affiliated networks for. Default empty.
 
118
         *     @type string       $path                 Limit results to those affiliated with a given network ID.
 
119
         *                                              Default current network ID.
 
120
         *     @type array        $path__in             Array of paths to include affiliated networks for. Default empty.
 
121
         *     @type array        $path__not_in         Array of paths to exclude affiliated networks for. Default empty.
 
122
         *     @type string       $search               Search term(s) to retrieve matching networks for. Default empty.
 
123
         *     @type bool         $update_network_cache Whether to prime the cache for found networks. Default true.
 
124
         * }
 
125
         */
 
126
        public function __construct( $query = '' ) {
 
127
                $this->query_var_defaults = array(
 
128
                        'network__in'          => '',
 
129
                        'network__not_in'      => '',
 
130
                        'count'                => false,
 
131
                        'fields'               => '',
 
132
                        'number'               => '',
 
133
                        'offset'               => '',
 
134
                        'no_found_rows'        => true,
 
135
                        'orderby'              => 'id',
 
136
                        'order'                => 'ASC',
 
137
                        'domain'               => '',
 
138
                        'domain__in'           => '',
 
139
                        'domain__not_in'       => '',
 
140
                        'path'                 => '',
 
141
                        'path__in'             => '',
 
142
                        'path__not_in'         => '',
 
143
                        'search'               => '',
 
144
                        'update_network_cache' => true,
 
145
                );
 
146
 
 
147
                if ( ! empty( $query ) ) {
 
148
                        $this->query( $query );
 
149
                }
 
150
        }
 
151
 
 
152
        /**
 
153
         * Parses arguments passed to the network query with default query parameters.
 
154
         *
 
155
         * @since 4.6.0
 
156
         *
 
157
         * @access public
 
158
         *
 
159
         * @param string|array $query WP_Network_Query arguments. See WP_Network_Query::__construct()
 
160
         */
 
161
        public function parse_query( $query = '' ) {
 
162
                if ( empty( $query ) ) {
 
163
                        $query = $this->query_vars;
 
164
                }
 
165
 
 
166
                $this->query_vars = wp_parse_args( $query, $this->query_var_defaults );
 
167
 
 
168
                /**
 
169
                 * Fires after the network query vars have been parsed.
 
170
                 *
 
171
                 * @since 4.6.0
 
172
                 *
 
173
                 * @param WP_Network_Query &$this The WP_Network_Query instance (passed by reference).
 
174
                 */
 
175
                do_action_ref_array( 'parse_network_query', array( &$this ) );
 
176
        }
 
177
 
 
178
        /**
 
179
         * Sets up the WordPress query for retrieving networks.
 
180
         *
 
181
         * @since 4.6.0
 
182
         * @access public
 
183
         *
 
184
         * @param string|array $query Array or URL query string of parameters.
 
185
         * @return array|int List of networks, or number of networks when 'count' is passed as a query var.
 
186
         */
 
187
        public function query( $query ) {
 
188
                $this->query_vars = wp_parse_args( $query );
 
189
                return $this->get_networks();
 
190
        }
 
191
 
 
192
        /**
 
193
         * Gets a list of networks matching the query vars.
 
194
         *
 
195
         * @since 4.6.0
 
196
         * @access public
 
197
         *
 
198
         * @return int|array The list of networks.
 
199
         */
 
200
        public function get_networks() {
 
201
                $this->parse_query();
 
202
 
 
203
                /**
 
204
                 * Fires before networks are retrieved.
 
205
                 *
 
206
                 * @since 4.6.0
 
207
                 *
 
208
                 * @param WP_Network_Query &$this Current instance of WP_Network_Query, passed by reference.
 
209
                 */
 
210
                do_action_ref_array( 'pre_get_networks', array( &$this ) );
 
211
 
 
212
                // $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
 
213
                $key = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ) ) );
 
214
                $last_changed = wp_cache_get( 'last_changed', 'networks' );
 
215
                if ( ! $last_changed ) {
 
216
                        $last_changed = microtime();
 
217
                        wp_cache_set( 'last_changed', $last_changed, 'networks' );
 
218
                }
 
219
 
 
220
                $cache_key = "get_network_ids:$key:$last_changed";
 
221
                $cache_value = wp_cache_get( $cache_key, 'networks' );
 
222
 
 
223
                if ( false === $cache_value ) {
 
224
                        $network_ids = $this->get_network_ids();
 
225
                        if ( $network_ids ) {
 
226
                                $this->set_found_networks();
 
227
                        }
 
228
 
 
229
                        $cache_value = array(
 
230
                                'network_ids' => $network_ids,
 
231
                                'found_networks' => $this->found_networks,
 
232
                        );
 
233
                        wp_cache_add( $cache_key, $cache_value, 'networks' );
 
234
                } else {
 
235
                        $network_ids = $cache_value['network_ids'];
 
236
                        $this->found_networks = $cache_value['found_networks'];
 
237
                }
 
238
 
 
239
                if ( $this->found_networks && $this->query_vars['number'] ) {
 
240
                        $this->max_num_pages = ceil( $this->found_networks / $this->query_vars['number'] );
 
241
                }
 
242
 
 
243
                // If querying for a count only, there's nothing more to do.
 
244
                if ( $this->query_vars['count'] ) {
 
245
                        // $network_ids is actually a count in this case.
 
246
                        return intval( $network_ids );
 
247
                }
 
248
 
 
249
                $network_ids = array_map( 'intval', $network_ids );
 
250
 
 
251
                if ( 'ids' == $this->query_vars['fields'] ) {
 
252
                        $this->networks = $network_ids;
 
253
                        return $this->networks;
 
254
                }
 
255
 
 
256
                if ( $this->query_vars['update_network_cache'] ) {
 
257
                        _prime_network_caches( $network_ids );
 
258
                }
 
259
 
 
260
                // Fetch full network objects from the primed cache.
 
261
                $_networks = array();
 
262
                foreach ( $network_ids as $network_id ) {
 
263
                        if ( $_network = get_network( $network_id ) ) {
 
264
                                $_networks[] = $_network;
 
265
                        }
 
266
                }
 
267
 
 
268
                /**
 
269
                 * Filters the network query results.
 
270
                 *
 
271
                 * @since 4.6.0
 
272
                 *
 
273
                 * @param array            $results  An array of networks.
 
274
                 * @param WP_Network_Query &$this    Current instance of WP_Network_Query, passed by reference.
 
275
                 */
 
276
                $_networks = apply_filters_ref_array( 'the_networks', array( $_networks, &$this ) );
 
277
 
 
278
                // Convert to WP_Network instances
 
279
                $this->networks = array_map( 'get_network', $_networks );
 
280
 
 
281
                return $this->networks;
 
282
        }
 
283
 
 
284
        /**
 
285
         * Used internally to get a list of network IDs matching the query vars.
 
286
         *
 
287
         * @since 4.6.0
 
288
         * @access protected
 
289
         *
 
290
         * @return int|array A single count of network IDs if a count query. An array of network IDs if a full query.
 
291
         */
 
292
        protected function get_network_ids() {
 
293
                global $wpdb;
 
294
 
 
295
                $order = $this->parse_order( $this->query_vars['order'] );
 
296
 
 
297
                // Disable ORDER BY with 'none', an empty array, or boolean false.
 
298
                if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
 
299
                        $orderby = '';
 
300
                } elseif ( ! empty( $this->query_vars['orderby'] ) ) {
 
301
                        $ordersby = is_array( $this->query_vars['orderby'] ) ?
 
302
                                $this->query_vars['orderby'] :
 
303
                                preg_split( '/[,\s]/', $this->query_vars['orderby'] );
 
304
 
 
305
                        $orderby_array = array();
 
306
                        foreach ( $ordersby as $_key => $_value ) {
 
307
                                if ( ! $_value ) {
 
308
                                        continue;
 
309
                                }
 
310
 
 
311
                                if ( is_int( $_key ) ) {
 
312
                                        $_orderby = $_value;
 
313
                                        $_order = $order;
 
314
                                } else {
 
315
                                        $_orderby = $_key;
 
316
                                        $_order = $_value;
 
317
                                }
 
318
 
 
319
                                $parsed = $this->parse_orderby( $_orderby );
 
320
 
 
321
                                if ( ! $parsed ) {
 
322
                                        continue;
 
323
                                }
 
324
 
 
325
                                if ( 'network__in' === $_orderby ) {
 
326
                                        $orderby_array[] = $parsed;
 
327
                                        continue;
 
328
                                }
 
329
 
 
330
                                $orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
 
331
                        }
 
332
 
 
333
                        $orderby = implode( ', ', $orderby_array );
 
334
                } else {
 
335
                        $orderby = "$wpdb->site.id $order";
 
336
                }
 
337
 
 
338
                $number = absint( $this->query_vars['number'] );
 
339
                $offset = absint( $this->query_vars['offset'] );
 
340
 
 
341
                if ( ! empty( $number ) ) {
 
342
                        if ( $offset ) {
 
343
                                $limits = 'LIMIT ' . $offset . ',' . $number;
 
344
                        } else {
 
345
                                $limits = 'LIMIT ' . $number;
 
346
                        }
 
347
                }
 
348
 
 
349
                if ( $this->query_vars['count'] ) {
 
350
                        $fields = 'COUNT(*)';
 
351
                } else {
 
352
                        $fields = "$wpdb->site.id";
 
353
                }
 
354
 
 
355
                // Parse network IDs for an IN clause.
 
356
                if ( ! empty( $this->query_vars['network__in'] ) ) {
 
357
                        $this->sql_clauses['where']['network__in'] = "$wpdb->site.id IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['network__in'] ) ) . ' )';
 
358
                }
 
359
 
 
360
                // Parse network IDs for a NOT IN clause.
 
361
                if ( ! empty( $this->query_vars['network__not_in'] ) ) {
 
362
                        $this->sql_clauses['where']['network__not_in'] = "$wpdb->site.id NOT IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['network__not_in'] ) ) . ' )';
 
363
                }
 
364
 
 
365
                if ( ! empty( $this->query_vars['domain'] ) ) {
 
366
                        $this->sql_clauses['where']['domain'] = $wpdb->prepare( "$wpdb->site.domain = %s", $this->query_vars['domain'] );
 
367
                }
 
368
 
 
369
                // Parse network domain for an IN clause.
 
370
                if ( is_array( $this->query_vars['domain__in'] ) ) {
 
371
                        $this->sql_clauses['where']['domain__in'] = "$wpdb->site.domain IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['domain__in'] ) ) . "' )";
 
372
                }
 
373
 
 
374
                // Parse network domain for a NOT IN clause.
 
375
                if ( is_array( $this->query_vars['domain__not_in'] ) ) {
 
376
                        $this->sql_clauses['where']['domain__not_in'] = "$wpdb->site.domain NOT IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['domain__not_in'] ) ) . "' )";
 
377
                }
 
378
 
 
379
                if ( ! empty( $this->query_vars['path'] ) ) {
 
380
                        $this->sql_clauses['where']['path'] = $wpdb->prepare( "$wpdb->site.path = %s", $this->query_vars['path'] );
 
381
                }
 
382
 
 
383
                // Parse network path for an IN clause.
 
384
                if ( is_array( $this->query_vars['path__in'] ) ) {
 
385
                        $this->sql_clauses['where']['path__in'] = "$wpdb->site.path IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['path__in'] ) ) . "' )";
 
386
                }
 
387
 
 
388
                // Parse network path for a NOT IN clause.
 
389
                if ( is_array( $this->query_vars['path__not_in'] ) ) {
 
390
                        $this->sql_clauses['where']['path__not_in'] = "$wpdb->site.path NOT IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['path__not_in'] ) ) . "' )";
 
391
                }
 
392
 
 
393
                // Falsey search strings are ignored.
 
394
                if ( strlen( $this->query_vars['search'] ) ) {
 
395
                        $this->sql_clauses['where']['search'] = $this->get_search_sql(
 
396
                                $this->query_vars['search'],
 
397
                                array( "$wpdb->site.domain", "$wpdb->site.path" )
 
398
                        );
 
399
                }
 
400
 
 
401
                $join = '';
 
402
 
 
403
                $where = implode( ' AND ', $this->sql_clauses['where'] );
 
404
 
 
405
                $pieces = array( 'fields', 'join', 'where', 'orderby', 'limits', 'groupby' );
 
406
 
 
407
                /**
 
408
                 * Filters the network query clauses.
 
409
                 *
 
410
                 * @since 4.6.0
 
411
                 *
 
412
                 * @param array            $pieces A compacted array of network query clauses.
 
413
                 * @param WP_Network_Query &$this  Current instance of WP_Network_Query, passed by reference.
 
414
                 */
 
415
                $clauses = apply_filters_ref_array( 'networks_clauses', array( compact( $pieces ), &$this ) );
 
416
 
 
417
                $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
 
418
                $join = isset( $clauses['join'] ) ? $clauses['join'] : '';
 
419
                $where = isset( $clauses['where'] ) ? $clauses['where'] : '';
 
420
                $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
 
421
                $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
 
422
                $groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
 
423
 
 
424
                if ( $where ) {
 
425
                        $where = 'WHERE ' . $where;
 
426
                }
 
427
 
 
428
                if ( $groupby ) {
 
429
                        $groupby = 'GROUP BY ' . $groupby;
 
430
                }
 
431
 
 
432
                if ( $orderby ) {
 
433
                        $orderby = "ORDER BY $orderby";
 
434
                }
 
435
 
 
436
                $found_rows = '';
 
437
                if ( ! $this->query_vars['no_found_rows'] ) {
 
438
                        $found_rows = 'SQL_CALC_FOUND_ROWS';
 
439
                }
 
440
 
 
441
                $this->sql_clauses['select']  = "SELECT $found_rows $fields";
 
442
                $this->sql_clauses['from']    = "FROM $wpdb->site $join";
 
443
                $this->sql_clauses['groupby'] = $groupby;
 
444
                $this->sql_clauses['orderby'] = $orderby;
 
445
                $this->sql_clauses['limits']  = $limits;
 
446
 
 
447
                $this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
 
448
 
 
449
                if ( $this->query_vars['count'] ) {
 
450
                        return intval( $wpdb->get_var( $this->request ) );
 
451
                }
 
452
 
 
453
                $network_ids = $wpdb->get_col( $this->request );
 
454
 
 
455
                return array_map( 'intval', $network_ids );
 
456
        }
 
457
 
 
458
        /**
 
459
         * Populates found_networks and max_num_pages properties for the current query
 
460
         * if the limit clause was used.
 
461
         *
 
462
         * @since 4.6.0
 
463
         * @access private
 
464
         *
 
465
         * @global wpdb $wpdb WordPress database abstraction object.
 
466
         */
 
467
        private function set_found_networks() {
 
468
                global $wpdb;
 
469
 
 
470
                if ( $this->query_vars['number'] && ! $this->query_vars['no_found_rows'] ) {
 
471
                        /**
 
472
                         * Filters the query used to retrieve found network count.
 
473
                         *
 
474
                         * @since 4.6.0
 
475
                         *
 
476
                         * @param string           $found_networks_query SQL query. Default 'SELECT FOUND_ROWS()'.
 
477
                         * @param WP_Network_Query $network_query        The `WP_Network_Query` instance.
 
478
                         */
 
479
                        $found_networks_query = apply_filters( 'found_networks_query', 'SELECT FOUND_ROWS()', $this );
 
480
 
 
481
                        $this->found_networks = (int) $wpdb->get_var( $found_networks_query );
 
482
                }
 
483
        }
 
484
 
 
485
        /**
 
486
         * Used internally to generate an SQL string for searching across multiple columns.
 
487
         *
 
488
         * @since 4.6.0
 
489
         * @access protected
 
490
         *
 
491
         * @global wpdb  $wpdb WordPress database abstraction object.
 
492
         *
 
493
         * @param string $string  Search string.
 
494
         * @param array  $columns Columns to search.
 
495
         *
 
496
         * @return string Search SQL.
 
497
         */
 
498
        protected function get_search_sql( $string, $columns ) {
 
499
                global $wpdb;
 
500
 
 
501
                $like = '%' . $wpdb->esc_like( $string ) . '%';
 
502
 
 
503
                $searches = array();
 
504
                foreach ( $columns as $column ) {
 
505
                        $searches[] = $wpdb->prepare( "$column LIKE %s", $like );
 
506
                }
 
507
 
 
508
                return '(' . implode( ' OR ', $searches ) . ')';
 
509
        }
 
510
 
 
511
        /**
 
512
         * Parses and sanitizes 'orderby' keys passed to the network query.
 
513
         *
 
514
         * @since 4.6.0
 
515
         * @access protected
 
516
         *
 
517
         * @global wpdb $wpdb WordPress database abstraction object.
 
518
         *
 
519
         * @param string $orderby Alias for the field to order by.
 
520
         * @return string|false Value to used in the ORDER clause. False otherwise.
 
521
         */
 
522
        protected function parse_orderby( $orderby ) {
 
523
                global $wpdb;
 
524
 
 
525
                $allowed_keys = array(
 
526
                        'id',
 
527
                        'domain',
 
528
                        'path',
 
529
                );
 
530
 
 
531
                $parsed = false;
 
532
                if ( $orderby == 'network__in' ) {
 
533
                        $network__in = implode( ',', array_map( 'absint', $this->query_vars['network__in'] ) );
 
534
                        $parsed = "FIELD( {$wpdb->site}.id, $network__in )";
 
535
                } elseif ( $orderby == 'domain_length' || $orderby == 'path_length' ) {
 
536
                        $field = substr( $orderby, 0, -7 );
 
537
                        $parsed = "CHAR_LENGTH($wpdb->site.$field)";
 
538
                } elseif ( in_array( $orderby, $allowed_keys ) ) {
 
539
                        $parsed = "$wpdb->site.$orderby";
 
540
                }
 
541
 
 
542
                return $parsed;
 
543
        }
 
544
 
 
545
        /**
 
546
         * Parses an 'order' query variable and cast it to 'ASC' or 'DESC' as necessary.
 
547
         *
 
548
         * @since 4.6.0
 
549
         * @access protected
 
550
         *
 
551
         * @param string $order The 'order' query variable.
 
552
         * @return string The sanitized 'order' query variable.
 
553
         */
 
554
        protected function parse_order( $order ) {
 
555
                if ( ! is_string( $order ) || empty( $order ) ) {
 
556
                        return 'ASC';
 
557
                }
 
558
 
 
559
                if ( 'ASC' === strtoupper( $order ) ) {
 
560
                        return 'ASC';
 
561
                } else {
 
562
                        return 'DESC';
 
563
                }
 
564
        }
 
565
}