Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-wp-site-query.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Site API: WP_Site_Query class
4
 *
5
 * @package WordPress
6
 * @subpackage Sites
7
 * @since 4.6.0
8
 */
9
10
/**
11
 * Core class used for querying sites.
12
 *
13
 * @since 4.6.0
14
 *
15
 * @see WP_Site_Query::__construct() for accepted arguments.
16
 */
17
class WP_Site_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
	 * Date query container.
46
	 *
47
	 * @since 4.6.0
48
	 * @access public
49
	 * @var object WP_Date_Query
50
	 */
51
	public $date_query = false;
52
53
	/**
54
	 * Query vars set by the user.
55
	 *
56
	 * @since 4.6.0
57
	 * @access public
58
	 * @var array
59
	 */
60
	public $query_vars;
61
62
	/**
63
	 * Default values for query vars.
64
	 *
65
	 * @since 4.6.0
66
	 * @access public
67
	 * @var array
68
	 */
69
	public $query_var_defaults;
70
71
	/**
72
	 * List of sites located by the query.
73
	 *
74
	 * @since 4.6.0
75
	 * @access public
76
	 * @var array
77
	 */
78
	public $sites;
79
80
	/**
81
	 * The amount of found sites for the current query.
82
	 *
83
	 * @since 4.6.0
84
	 * @access public
85
	 * @var int
86
	 */
87
	public $found_sites = 0;
88
89
	/**
90
	 * The number of pages.
91
	 *
92
	 * @since 4.6.0
93
	 * @access public
94
	 * @var int
95
	 */
96
	public $max_num_pages = 0;
97
98
	/**
99
	 * Sets up the site query, based on the query vars passed.
100
	 *
101
	 * @since 4.6.0
102
	 * @since 4.8.0 Introduced the 'lang_id', 'lang__in', and 'lang__not_in' parameters.
103
	 * @access public
104
	 *
105
	 * @param string|array $query {
106
	 *     Optional. Array or query string of site query parameters. Default empty.
107
	 *
108
	 *     @type array        $site__in          Array of site IDs to include. Default empty.
109
	 *     @type array        $site__not_in      Array of site IDs to exclude. Default empty.
110
	 *     @type bool         $count             Whether to return a site count (true) or array of site objects.
111
	 *                                           Default false.
112
	 *     @type array        $date_query        Date query clauses to limit sites by. See WP_Date_Query.
113
	 *                                           Default null.
114
	 *     @type string       $fields            Site fields to return. Accepts 'ids' (returns an array of site IDs)
115
	 *                                           or empty (returns an array of complete site objects). Default empty.
116
	 *     @type int          $ID                A site ID to only return that site. Default empty.
117
	 *     @type int          $number            Maximum number of sites to retrieve. Default 100.
118
	 *     @type int          $offset            Number of sites to offset the query. Used to build LIMIT clause.
119
	 *                                           Default 0.
120
	 *     @type bool         $no_found_rows     Whether to disable the `SQL_CALC_FOUND_ROWS` query. Default true.
121
	 *     @type string|array $orderby           Site status or array of statuses. Accepts 'id', 'domain', 'path',
122
	 *                                           'network_id', 'last_updated', 'registered', 'domain_length',
123
	 *                                           'path_length', 'site__in' and 'network__in'. Also accepts false,
124
	 *                                           an empty array, or 'none' to disable `ORDER BY` clause.
125
	 *                                           Default 'id'.
126
	 *     @type string       $order             How to order retrieved sites. Accepts 'ASC', 'DESC'. Default 'ASC'.
127
	 *     @type int          $network_id        Limit results to those affiliated with a given network ID. If 0,
128
	 *                                           include all networks. Default 0.
129
	 *     @type array        $network__in       Array of network IDs to include affiliated sites for. Default empty.
130
	 *     @type array        $network__not_in   Array of network IDs to exclude affiliated sites for. Default empty.
131
	 *     @type string       $domain            Limit results to those affiliated with a given domain. Default empty.
132
	 *     @type array        $domain__in        Array of domains to include affiliated sites for. Default empty.
133
	 *     @type array        $domain__not_in    Array of domains to exclude affiliated sites for. Default empty.
134
	 *     @type string       $path              Limit results to those affiliated with a given path. Default empty.
135
	 *     @type array        $path__in          Array of paths to include affiliated sites for. Default empty.
136
	 *     @type array        $path__not_in      Array of paths to exclude affiliated sites for. Default empty.
137
	 *     @type int          $public            Limit results to public sites. Accepts '1' or '0'. Default empty.
138
	 *     @type int          $archived          Limit results to archived sites. Accepts '1' or '0'. Default empty.
139
	 *     @type int          $mature            Limit results to mature sites. Accepts '1' or '0'. Default empty.
140
	 *     @type int          $spam              Limit results to spam sites. Accepts '1' or '0'. Default empty.
141
	 *     @type int          $deleted           Limit results to deleted sites. Accepts '1' or '0'. Default empty.
142
	 *     @type int          $lang_id           Limit results to a language ID. Default empty.
143
	 *     @type array        $lang__in          Array of language IDs to include affiliated sites for. Default empty.
144
	 *     @type array        $lang__not_in      Array of language IDs to exclude affiliated sites for. Default empty.
145
	 *     @type string       $search            Search term(s) to retrieve matching sites for. Default empty.
146
	 *     @type array        $search_columns    Array of column names to be searched. Accepts 'domain' and 'path'.
147
	 *                                           Default empty array.
148
	 *     @type bool         $update_site_cache Whether to prime the cache for found sites. Default false.
149
	 * }
150
	 */
151
	public function __construct( $query = '' ) {
152
		$this->query_var_defaults = array(
153
			'fields'            => '',
154
			'ID'                => '',
155
			'site__in'          => '',
156
			'site__not_in'      => '',
157
			'number'            => 100,
158
			'offset'            => '',
159
			'no_found_rows'     => true,
160
			'orderby'           => 'id',
161
			'order'             => 'ASC',
162
			'network_id'        => 0,
163
			'network__in'       => '',
164
			'network__not_in'   => '',
165
			'domain'            => '',
166
			'domain__in'        => '',
167
			'domain__not_in'    => '',
168
			'path'              => '',
169
			'path__in'          => '',
170
			'path__not_in'      => '',
171
			'public'            => null,
172
			'archived'          => null,
173
			'mature'            => null,
174
			'spam'              => null,
175
			'deleted'           => null,
176
			'lang_id'           => null,
177
			'lang__in'          => '',
178
			'lang__not_in'      => '',
179
			'search'            => '',
180
			'search_columns'    => array(),
181
			'count'             => false,
182
			'date_query'        => null, // See WP_Date_Query
183
			'update_site_cache' => true,
184
		);
185
186
		if ( ! empty( $query ) ) {
187
			$this->query( $query );
188
		}
189
	}
190
191
	/**
192
	 * Parses arguments passed to the site query with default query parameters.
193
	 *
194
	 * @since 4.6.0
195
	 * @access public
196
	 *
197
	 * @see WP_Site_Query::__construct()
198
	 *
199
	 * @param string|array $query Array or string of WP_Site_Query arguments. See WP_Site_Query::__construct().
200
	 */
201 View Code Duplication
	public function parse_query( $query = '' ) {
202
		if ( empty( $query ) ) {
203
			$query = $this->query_vars;
204
		}
205
206
		$this->query_vars = wp_parse_args( $query, $this->query_var_defaults );
207
208
		/**
209
		 * Fires after the site query vars have been parsed.
210
		 *
211
		 * @since 4.6.0
212
		 *
213
		 * @param WP_Site_Query &$this The WP_Site_Query instance (passed by reference).
214
		 */
215
		do_action_ref_array( 'parse_site_query', array( &$this ) );
216
	}
217
218
	/**
219
	 * Sets up the WordPress query for retrieving sites.
220
	 *
221
	 * @since 4.6.0
222
	 * @access public
223
	 *
224
	 * @param string|array $query Array or URL query string of parameters.
225
	 * @return array|int List of sites, or number of sites when 'count' is passed as a query var.
226
	 */
227
	public function query( $query ) {
228
		$this->query_vars = wp_parse_args( $query );
229
230
		return $this->get_sites();
231
	}
232
233
	/**
234
	 * Retrieves a list of sites matching the query vars.
235
	 *
236
	 * @since 4.6.0
237
	 * @access public
238
	 *
239
	 * @return array|int List of sites, or number of sites when 'count' is passed as a query var.
240
	 */
241 View Code Duplication
	public function get_sites() {
242
		$this->parse_query();
243
244
		/**
245
		 * Fires before sites are retrieved.
246
		 *
247
		 * @since 4.6.0
248
		 *
249
		 * @param WP_Site_Query &$this Current instance of WP_Site_Query, passed by reference.
250
		 */
251
		do_action_ref_array( 'pre_get_sites', array( &$this ) );
252
253
		// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
254
		$key = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ) ) );
255
		$last_changed = wp_cache_get_last_changed( 'sites' );
256
257
		$cache_key = "get_sites:$key:$last_changed";
258
		$cache_value = wp_cache_get( $cache_key, 'sites' );
259
260
		if ( false === $cache_value ) {
261
			$site_ids = $this->get_site_ids();
262
			if ( $site_ids ) {
263
				$this->set_found_sites();
264
			}
265
266
			$cache_value = array(
267
				'site_ids' => $site_ids,
268
				'found_sites' => $this->found_sites,
269
			);
270
			wp_cache_add( $cache_key, $cache_value, 'sites' );
271
		} else {
272
			$site_ids = $cache_value['site_ids'];
273
			$this->found_sites = $cache_value['found_sites'];
274
		}
275
276
		if ( $this->found_sites && $this->query_vars['number'] ) {
277
			$this->max_num_pages = ceil( $this->found_sites / $this->query_vars['number'] );
0 ignored issues
show
Documentation Bug introduced by
The property $max_num_pages was declared of type integer, but ceil($this->found_sites ...->query_vars['number']) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
278
		}
279
280
		// If querying for a count only, there's nothing more to do.
281
		if ( $this->query_vars['count'] ) {
282
			// $site_ids is actually a count in this case.
283
			return intval( $site_ids );
284
		}
285
286
		$site_ids = array_map( 'intval', $site_ids );
287
288
		if ( 'ids' == $this->query_vars['fields'] ) {
289
			$this->sites = $site_ids;
290
291
			return $this->sites;
292
		}
293
294
		// Prime site network caches.
295
		if ( $this->query_vars['update_site_cache'] ) {
296
			_prime_site_caches( $site_ids );
297
		}
298
299
		// Fetch full site objects from the primed cache.
300
		$_sites = array();
301
		foreach ( $site_ids as $site_id ) {
302
			if ( $_site = get_site( $site_id ) ) {
303
				$_sites[] = $_site;
304
			}
305
		}
306
307
		/**
308
		 * Filters the site query results.
309
		 *
310
		 * @since 4.6.0
311
		 *
312
		 * @param array         $results An array of sites.
313
		 * @param WP_Site_Query &$this   Current instance of WP_Site_Query, passed by reference.
314
		 */
315
		$_sites = apply_filters_ref_array( 'the_sites', array( $_sites, &$this ) );
316
317
		// Convert to WP_Site instances.
318
		$this->sites = array_map( 'get_site', $_sites );
319
320
		return $this->sites;
321
	}
322
323
	/**
324
	 * Used internally to get a list of site IDs matching the query vars.
325
	 *
326
	 * @since 4.6.0
327
	 * @access protected
328
	 *
329
	 * @global wpdb $wpdb WordPress database abstraction object.
330
	 *
331
	 * @return int|array A single count of site IDs if a count query. An array of site IDs if a full query.
332
	 */
333
	protected function get_site_ids() {
334
		global $wpdb;
335
336
		$order = $this->parse_order( $this->query_vars['order'] );
337
338
		// Disable ORDER BY with 'none', an empty array, or boolean false.
339 View Code Duplication
		if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
340
			$orderby = '';
341
		} elseif ( ! empty( $this->query_vars['orderby'] ) ) {
342
			$ordersby = is_array( $this->query_vars['orderby'] ) ?
343
				$this->query_vars['orderby'] :
344
				preg_split( '/[,\s]/', $this->query_vars['orderby'] );
345
346
			$orderby_array = array();
347
			foreach ( $ordersby as $_key => $_value ) {
348
				if ( ! $_value ) {
349
					continue;
350
				}
351
352
				if ( is_int( $_key ) ) {
353
					$_orderby = $_value;
354
					$_order = $order;
355
				} else {
356
					$_orderby = $_key;
357
					$_order = $_value;
358
				}
359
360
				$parsed = $this->parse_orderby( $_orderby );
361
362
				if ( ! $parsed ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parsed of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
363
					continue;
364
				}
365
366
				if ( 'site__in' === $_orderby || 'network__in' === $_orderby ) {
367
					$orderby_array[] = $parsed;
368
					continue;
369
				}
370
371
				$orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
372
			}
373
374
			$orderby = implode( ', ', $orderby_array );
375
		} else {
376
			$orderby = "blog_id $order";
377
		}
378
379
		$number = absint( $this->query_vars['number'] );
380
		$offset = absint( $this->query_vars['offset'] );
381
382 View Code Duplication
		if ( ! empty( $number ) ) {
383
			if ( $offset ) {
384
				$limits = 'LIMIT ' . $offset . ',' . $number;
385
			} else {
386
				$limits = 'LIMIT ' . $number;
387
			}
388
		}
389
390
		if ( $this->query_vars['count'] ) {
391
			$fields = 'COUNT(*)';
392
		} else {
393
			$fields = 'blog_id';
394
		}
395
396
		// Parse site IDs for an IN clause.
397
		$site_id = absint( $this->query_vars['ID'] );
398
		if ( ! empty( $site_id ) ) {
399
			$this->sql_clauses['where']['ID'] = $wpdb->prepare( 'blog_id = %d', $site_id );
400
		}
401
402
		// Parse site IDs for an IN clause.
403
		if ( ! empty( $this->query_vars['site__in'] ) ) {
404
			$this->sql_clauses['where']['site__in'] = "blog_id IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['site__in'] ) ) . ' )';
405
		}
406
407
		// Parse site IDs for a NOT IN clause.
408
		if ( ! empty( $this->query_vars['site__not_in'] ) ) {
409
			$this->sql_clauses['where']['site__not_in'] = "blog_id NOT IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['site__not_in'] ) ) . ' )';
410
		}
411
412
		$network_id = absint( $this->query_vars['network_id'] );
413
414
		if ( ! empty( $network_id ) ) {
415
			$this->sql_clauses['where']['network_id'] = $wpdb->prepare( 'site_id = %d', $network_id );
416
		}
417
418
		// Parse site network IDs for an IN clause.
419
		if ( ! empty( $this->query_vars['network__in'] ) ) {
420
			$this->sql_clauses['where']['network__in'] = 'site_id IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['network__in'] ) ) . ' )';
421
		}
422
423
		// Parse site network IDs for a NOT IN clause.
424
		if ( ! empty( $this->query_vars['network__not_in'] ) ) {
425
			$this->sql_clauses['where']['network__not_in'] = 'site_id NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['network__not_in'] ) ) . ' )';
426
		}
427
428 View Code Duplication
		if ( ! empty( $this->query_vars['domain'] ) ) {
429
			$this->sql_clauses['where']['domain'] = $wpdb->prepare( 'domain = %s', $this->query_vars['domain'] );
430
		}
431
432
		// Parse site domain for an IN clause.
433
		if ( is_array( $this->query_vars['domain__in'] ) ) {
434
			$this->sql_clauses['where']['domain__in'] = "domain IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['domain__in'] ) ) . "' )";
435
		}
436
437
		// Parse site domain for a NOT IN clause.
438
		if ( is_array( $this->query_vars['domain__not_in'] ) ) {
439
			$this->sql_clauses['where']['domain__not_in'] = "domain NOT IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['domain__not_in'] ) ) . "' )";
440
		}
441
442 View Code Duplication
		if ( ! empty( $this->query_vars['path'] ) ) {
443
			$this->sql_clauses['where']['path'] = $wpdb->prepare( 'path = %s', $this->query_vars['path'] );
444
		}
445
446
		// Parse site path for an IN clause.
447
		if ( is_array( $this->query_vars['path__in'] ) ) {
448
			$this->sql_clauses['where']['path__in'] = "path IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['path__in'] ) ) . "' )";
449
		}
450
451
		// Parse site path for a NOT IN clause.
452
		if ( is_array( $this->query_vars['path__not_in'] ) ) {
453
			$this->sql_clauses['where']['path__not_in'] = "path NOT IN ( '" . implode( "', '", $wpdb->_escape( $this->query_vars['path__not_in'] ) ) . "' )";
454
		}
455
456 View Code Duplication
		if ( is_numeric( $this->query_vars['archived'] ) ) {
457
			$archived = absint( $this->query_vars['archived'] );
458
			$this->sql_clauses['where']['archived'] = $wpdb->prepare( "archived = %d ", $archived );
459
		}
460
461 View Code Duplication
		if ( is_numeric( $this->query_vars['mature'] ) ) {
462
			$mature = absint( $this->query_vars['mature'] );
463
			$this->sql_clauses['where']['mature'] = $wpdb->prepare( "mature = %d ", $mature );
464
		}
465
466 View Code Duplication
		if ( is_numeric( $this->query_vars['spam'] ) ) {
467
			$spam = absint( $this->query_vars['spam'] );
468
			$this->sql_clauses['where']['spam'] = $wpdb->prepare( "spam = %d ", $spam );
469
		}
470
471 View Code Duplication
		if ( is_numeric( $this->query_vars['deleted'] ) ) {
472
			$deleted = absint( $this->query_vars['deleted'] );
473
			$this->sql_clauses['where']['deleted'] = $wpdb->prepare( "deleted = %d ", $deleted );
474
		}
475
476 View Code Duplication
		if ( is_numeric( $this->query_vars['public'] ) ) {
477
			$public = absint( $this->query_vars['public'] );
478
			$this->sql_clauses['where']['public'] = $wpdb->prepare( "public = %d ", $public );
479
		}
480
481 View Code Duplication
		if ( is_numeric( $this->query_vars['lang_id'] ) ) {
482
			$lang_id = absint( $this->query_vars['lang_id'] );
483
			$this->sql_clauses['where']['lang_id'] = $wpdb->prepare( "lang_id = %d ", $lang_id );
484
		}
485
486
		// Parse site language IDs for an IN clause.
487
		if ( ! empty( $this->query_vars['lang__in'] ) ) {
488
			$this->sql_clauses['where']['lang__in'] = 'lang_id IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['lang__in'] ) ) . ' )';
489
		}
490
491
		// Parse site language IDs for a NOT IN clause.
492
		if ( ! empty( $this->query_vars['lang__not_in'] ) ) {
493
			$this->sql_clauses['where']['lang__not_in'] = 'lang_id NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['lang__not_in'] ) ) . ' )';
494
		}
495
496
		// Falsey search strings are ignored.
497
		if ( strlen( $this->query_vars['search'] ) ) {
498
			$search_columns = array();
499
500
			if ( $this->query_vars['search_columns'] ) {
501
				$search_columns = array_intersect( $this->query_vars['search_columns'], array( 'domain', 'path' ) );
502
			}
503
504
			if ( ! $search_columns ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $search_columns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
505
				$search_columns = array( 'domain', 'path' );
506
			}
507
508
			/**
509
			 * Filters the columns to search in a WP_Site_Query search.
510
			 *
511
			 * The default columns include 'domain' and 'path.
512
			 *
513
			 * @since 4.6.0
514
			 *
515
			 * @param array         $search_columns Array of column names to be searched.
516
			 * @param string        $search         Text being searched.
517
			 * @param WP_Site_Query $this           The current WP_Site_Query instance.
518
			 */
519
			$search_columns = apply_filters( 'site_search_columns', $search_columns, $this->query_vars['search'], $this );
520
521
			$this->sql_clauses['where']['search'] = $this->get_search_sql( $this->query_vars['search'], $search_columns );
522
		}
523
524
		$date_query = $this->query_vars['date_query'];
525
		if ( ! empty( $date_query ) && is_array( $date_query ) ) {
526
			$this->date_query = new WP_Date_Query( $date_query, 'registered' );
527
			$this->sql_clauses['where']['date_query'] = preg_replace( '/^\s*AND\s*/', '', $this->date_query->get_sql() );
528
		}
529
530
		$join = '';
531
532
		$where = implode( ' AND ', $this->sql_clauses['where'] );
533
534
		$pieces = array( 'fields', 'join', 'where', 'orderby', 'limits', 'groupby' );
535
536
		/**
537
		 * Filters the site query clauses.
538
		 *
539
		 * @since 4.6.0
540
		 *
541
		 * @param array $pieces A compacted array of site query clauses.
542
		 * @param WP_Site_Query &$this Current instance of WP_Site_Query, passed by reference.
543
		 */
544
		$clauses = apply_filters_ref_array( 'sites_clauses', array( compact( $pieces ), &$this ) );
545
546
		$fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
547
		$join = isset( $clauses['join'] ) ? $clauses['join'] : '';
548
		$where = isset( $clauses['where'] ) ? $clauses['where'] : '';
549
		$orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
550
		$limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
551
		$groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
552
553
		if ( $where ) {
554
			$where = 'WHERE ' . $where;
555
		}
556
557
		if ( $groupby ) {
558
			$groupby = 'GROUP BY ' . $groupby;
559
		}
560
561
		if ( $orderby ) {
562
			$orderby = "ORDER BY $orderby";
563
		}
564
565
		$found_rows = '';
566
		if ( ! $this->query_vars['no_found_rows'] ) {
567
			$found_rows = 'SQL_CALC_FOUND_ROWS';
568
		}
569
570
		$this->sql_clauses['select']  = "SELECT $found_rows $fields";
571
		$this->sql_clauses['from']    = "FROM $wpdb->blogs $join";
572
		$this->sql_clauses['groupby'] = $groupby;
573
		$this->sql_clauses['orderby'] = $orderby;
574
		$this->sql_clauses['limits']  = $limits;
575
576
		$this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
577
578
		if ( $this->query_vars['count'] ) {
579
			return intval( $wpdb->get_var( $this->request ) );
580
		}
581
582
		$site_ids = $wpdb->get_col( $this->request );
583
584
		return array_map( 'intval', $site_ids );
585
	}
586
587
	/**
588
	 * Populates found_sites and max_num_pages properties for the current query
589
	 * if the limit clause was used.
590
	 *
591
	 * @since 4.6.0
592
	 * @access private
593
	 *
594
	 * @global wpdb $wpdb WordPress database abstraction object.
595
	 */
596 View Code Duplication
	private function set_found_sites() {
597
		global $wpdb;
598
599
		if ( $this->query_vars['number'] && ! $this->query_vars['no_found_rows'] ) {
600
			/**
601
			 * Filters the query used to retrieve found site count.
602
			 *
603
			 * @since 4.6.0
604
			 *
605
			 * @param string        $found_sites_query SQL query. Default 'SELECT FOUND_ROWS()'.
606
			 * @param WP_Site_Query $site_query        The `WP_Site_Query` instance.
607
			 */
608
			$found_sites_query = apply_filters( 'found_sites_query', 'SELECT FOUND_ROWS()', $this );
609
610
			$this->found_sites = (int) $wpdb->get_var( $found_sites_query );
611
		}
612
	}
613
614
	/**
615
	 * Used internally to generate an SQL string for searching across multiple columns.
616
	 *
617
	 * @since 4.6.0
618
	 * @access protected
619
	 *
620
	 * @global wpdb  $wpdb WordPress database abstraction object.
621
	 *
622
	 * @param string $string  Search string.
623
	 * @param array  $columns Columns to search.
624
	 * @return string Search SQL.
625
	 */
626
	protected function get_search_sql( $string, $columns ) {
627
		global $wpdb;
628
629
		if ( false !== strpos( $string, '*' ) ) {
630
			$like = '%' . implode( '%', array_map( array( $wpdb, 'esc_like' ), explode( '*', $string ) ) ) . '%';
631
		} else {
632
			$like = '%' . $wpdb->esc_like( $string ) . '%';
633
		}
634
635
		$searches = array();
636
		foreach ( $columns as $column ) {
637
			$searches[] = $wpdb->prepare( "$column LIKE %s", $like );
638
		}
639
640
		return '(' . implode( ' OR ', $searches ) . ')';
641
	}
642
643
	/**
644
	 * Parses and sanitizes 'orderby' keys passed to the site query.
645
	 *
646
	 * @since 4.6.0
647
	 * @access protected
648
	 *
649
	 * @global wpdb $wpdb WordPress database abstraction object.
650
	 *
651
	 * @param string $orderby Alias for the field to order by.
652
	 * @return string|false Value to used in the ORDER clause. False otherwise.
653
	 */
654
	protected function parse_orderby( $orderby ) {
655
		global $wpdb;
656
657
		$parsed = false;
658
659
		switch ( $orderby ) {
660 View Code Duplication
			case 'site__in':
661
				$site__in = implode( ',', array_map( 'absint', $this->query_vars['site__in'] ) );
662
				$parsed = "FIELD( {$wpdb->blogs}.blog_id, $site__in )";
663
				break;
664 View Code Duplication
			case 'network__in':
665
				$network__in = implode( ',', array_map( 'absint', $this->query_vars['network__in'] ) );
666
				$parsed = "FIELD( {$wpdb->blogs}.site_id, $network__in )";
667
				break;
668
			case 'domain':
669
			case 'last_updated':
670
			case 'path':
671
			case 'registered':
672
				$parsed = $orderby;
673
				break;
674
			case 'network_id':
675
				$parsed = 'site_id';
676
				break;
677
			case 'domain_length':
678
				$parsed = 'CHAR_LENGTH(domain)';
679
				break;
680
			case 'path_length':
681
				$parsed = 'CHAR_LENGTH(path)';
682
				break;
683
			case 'id':
684
				$parsed = 'blog_id';
685
				break;
686
		}
687
688
		return $parsed;
689
	}
690
691
	/**
692
	 * Parses an 'order' query variable and cast it to 'ASC' or 'DESC' as necessary.
693
	 *
694
	 * @since 4.6.0
695
	 * @access protected
696
	 *
697
	 * @param string $order The 'order' query variable.
698
	 * @return string The sanitized 'order' query variable.
699
	 */
700 View Code Duplication
	protected function parse_order( $order ) {
701
		if ( ! is_string( $order ) || empty( $order ) ) {
702
			return 'ASC';
703
		}
704
705
		if ( 'ASC' === strtoupper( $order ) ) {
706
			return 'ASC';
707
		} else {
708
			return 'DESC';
709
		}
710
	}
711
}
712