WP_User_Query::fill_query_vars()   B
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 23
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 26
rs 8.8571
1
<?php
2
/**
3
 * User API: WP_User_Query class
4
 *
5
 * @package WordPress
6
 * @subpackage Users
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used for querying users.
12
 *
13
 * @since 3.1.0
14
 *
15
 * @see WP_User_Query::prepare_query() for information on accepted arguments.
16
 */
17
class WP_User_Query {
18
19
	/**
20
	 * Query vars, after parsing
21
	 *
22
	 * @since 3.5.0
23
	 * @access public
24
	 * @var array
25
	 */
26
	public $query_vars = array();
27
28
	/**
29
	 * List of found user ids
30
	 *
31
	 * @since 3.1.0
32
	 * @access private
33
	 * @var array
34
	 */
35
	private $results;
36
37
	/**
38
	 * Total number of found users for the current query
39
	 *
40
	 * @since 3.1.0
41
	 * @access private
42
	 * @var int
43
	 */
44
	private $total_users = 0;
45
46
	/**
47
	 * Metadata query container.
48
	 *
49
	 * @since 4.2.0
50
	 * @access public
51
	 * @var WP_Meta_Query
52
	 */
53
	public $meta_query = false;
54
55
	/**
56
	 * The SQL query used to fetch matching users.
57
	 *
58
	 * @since 4.4.0
59
	 * @access public
60
	 * @var string
61
	 */
62
	public $request;
63
64
	private $compat_fields = array( 'results', 'total_users' );
65
66
	// SQL clauses
67
	public $query_fields;
68
	public $query_from;
69
	public $query_where;
70
	public $query_orderby;
71
	public $query_limit;
72
73
	/**
74
	 * @since 4.7.0
75
	 * @access protected
76
	 * @var wpdb
77
	 */
78
	protected $db;
79
80
	/**
81
	 * PHP5 constructor.
82
	 *
83
	 * @since 3.1.0
84
	 *
85
	 * @param null|string|array $query Optional. The query variables.
86
	 */
87
	public function __construct( $query = null ) {
88
		$this->db = $GLOBALS['wpdb'];
89
90
		if ( ! empty( $query ) ) {
91
			$this->prepare_query( $query );
92
			$this->query();
93
		}
94
	}
95
96
	/**
97
	 * Fills in missing query variables with default values.
98
	 *
99
	 * @since 4.4.0
100
	 * @access public
101
	 *
102
	 * @param array $args Query vars, as passed to `WP_User_Query`.
103
	 * @return array Complete query variables with undefined ones filled in with defaults.
104
	 */
105
	public static function fill_query_vars( $args ) {
106
		$defaults = array(
107
			'blog_id' => $GLOBALS['blog_id'],
108
			'role' => '',
109
			'role__in' => array(),
110
			'role__not_in' => array(),
111
			'meta_key' => '',
112
			'meta_value' => '',
113
			'meta_compare' => '',
114
			'include' => array(),
115
			'exclude' => array(),
116
			'search' => '',
117
			'search_columns' => array(),
118
			'orderby' => 'login',
119
			'order' => 'ASC',
120
			'offset' => '',
121
			'number' => '',
122
			'paged' => 1,
123
			'count_total' => true,
124
			'fields' => 'all',
125
			'who' => '',
126
			'has_published_posts' => null,
127
		);
128
129
		return wp_parse_args( $args, $defaults );
130
	}
131
132
	/**
133
	 * Prepare the query variables.
134
	 *
135
	 * @since 3.1.0
136
	 * @since 4.1.0 Added the ability to order by the `include` value.
137
	 * @since 4.2.0 Added 'meta_value_num' support for `$orderby` parameter. Added multi-dimensional array syntax
138
	 *              for `$orderby` parameter.
139
	 * @since 4.3.0 Added 'has_published_posts' parameter.
140
	 * @since 4.4.0 Added 'paged', 'role__in', and 'role__not_in' parameters. The 'role' parameter was updated to
141
	 *              permit an array or comma-separated list of values. The 'number' parameter was updated to support
142
	 *              querying for all users with using -1.
143
	 *
144
	 * @access public
145
	 *
146
	 * @global int  $blog_id
147
	 *
148
	 * @param string|array $query {
149
	 *     Optional. Array or string of Query parameters.
150
	 *
151
	 *     @type int          $blog_id             The site ID. Default is the current site.
152
	 *     @type string|array $role                An array or a comma-separated list of role names that users must match
153
	 *                                             to be included in results. Note that this is an inclusive list: users
154
	 *                                             must match *each* role. Default empty.
155
	 *     @type array        $role__in            An array of role names. Matched users must have at least one of these
156
	 *                                             roles. Default empty array.
157
	 *     @type array        $role__not_in        An array of role names to exclude. Users matching one or more of these
158
	 *                                             roles will not be included in results. Default empty array.
159
	 *     @type string       $meta_key            User meta key. Default empty.
160
	 *     @type string       $meta_value          User meta value. Default empty.
161
	 *     @type string       $meta_compare        Comparison operator to test the `$meta_value`. Accepts '=', '!=',
162
	 *                                             '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN',
163
	 *                                             'BETWEEN', 'NOT BETWEEN', 'EXISTS', 'NOT EXISTS', 'REGEXP',
164
	 *                                             'NOT REGEXP', or 'RLIKE'. Default '='.
165
	 *     @type array        $include             An array of user IDs to include. Default empty array.
166
	 *     @type array        $exclude             An array of user IDs to exclude. Default empty array.
167
	 *     @type string       $search              Search keyword. Searches for possible string matches on columns.
168
	 *                                             When `$search_columns` is left empty, it tries to determine which
169
	 *                                             column to search in based on search string. Default empty.
170
	 *     @type array        $search_columns      Array of column names to be searched. Accepts 'ID', 'login',
171
	 *                                             'nicename', 'email', 'url'. Default empty array.
172
	 *     @type string|array $orderby             Field(s) to sort the retrieved users by. May be a single value,
173
	 *                                             an array of values, or a multi-dimensional array with fields as
174
	 *                                             keys and orders ('ASC' or 'DESC') as values. Accepted values are
175
	 *                                             'ID', 'display_name' (or 'name'), 'include', 'user_login'
176
	 *                                             (or 'login'), 'user_nicename' (or 'nicename'), 'user_email'
177
	 *                                             (or 'email'), 'user_url' (or 'url'), 'user_registered'
178
	 *                                             or 'registered'), 'post_count', 'meta_value', 'meta_value_num',
179
	 *                                             the value of `$meta_key`, or an array key of `$meta_query`. To use
180
	 *                                             'meta_value' or 'meta_value_num', `$meta_key` must be also be
181
	 *                                             defined. Default 'user_login'.
182
	 *     @type string       $order               Designates ascending or descending order of users. Order values
183
	 *                                             passed as part of an `$orderby` array take precedence over this
184
	 *                                             parameter. Accepts 'ASC', 'DESC'. Default 'ASC'.
185
	 *     @type int          $offset              Number of users to offset in retrieved results. Can be used in
186
	 *                                             conjunction with pagination. Default 0.
187
	 *     @type int          $number              Number of users to limit the query for. Can be used in
188
	 *                                             conjunction with pagination. Value -1 (all) is supported, but
189
	 *                                             should be used with caution on larger sites.
190
	 *                                             Default empty (all users).
191
	 *     @type int          $paged               When used with number, defines the page of results to return.
192
	 *                                             Default 1.
193
	 *     @type bool         $count_total         Whether to count the total number of users found. If pagination
194
	 *                                             is not needed, setting this to false can improve performance.
195
	 *                                             Default true.
196
	 *     @type string|array $fields              Which fields to return. Single or all fields (string), or array
197
	 *                                             of fields. Accepts 'ID', 'display_name', 'user_login',
198
	 *                                             'user_nicename', 'user_email', 'user_url', 'user_registered'.
199
	 *                                             Use 'all' for all fields and 'all_with_meta' to include
200
	 *                                             meta fields. Default 'all'.
201
	 *     @type string       $who                 Type of users to query. Accepts 'authors'.
202
	 *                                             Default empty (all users).
203
	 *     @type bool|array   $has_published_posts Pass an array of post types to filter results to users who have
204
	 *                                             published posts in those post types. `true` is an alias for all
205
	 *                                             public post types.
206
	 * }
207
	 */
208
	public function prepare_query( $query = array() ) {
209
		if ( empty( $this->query_vars ) || ! empty( $query ) ) {
210
			$this->query_limit = null;
211
			$this->query_vars = $this->fill_query_vars( $query );
0 ignored issues
show
Bug introduced by
It seems like $query defined by parameter $query on line 208 can also be of type string; however, WP_User_Query::fill_query_vars() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
212
		}
213
214
		/**
215
		 * Fires before the WP_User_Query has been parsed.
216
		 *
217
		 * The passed WP_User_Query object contains the query variables, not
218
		 * yet passed into SQL.
219
		 *
220
		 * @since 4.0.0
221
		 *
222
		 * @param WP_User_Query $this The current WP_User_Query instance,
223
		 *                            passed by reference.
224
		 */
225
		do_action( 'pre_get_users', $this );
226
227
		// Ensure that query vars are filled after 'pre_get_users'.
228
		$qv =& $this->query_vars;
229
		$qv =  $this->fill_query_vars( $qv );
230
231
		if ( is_array( $qv['fields'] ) ) {
232
			$qv['fields'] = array_unique( $qv['fields'] );
233
234
			$this->query_fields = array();
235
			foreach ( $qv['fields'] as $field ) {
236
				$field = 'ID' === $field ? 'ID' : sanitize_key( $field );
237
				$this->query_fields[] = "{$this->db->users}.$field";
238
			}
239
			$this->query_fields = implode( ',', $this->query_fields );
240
		} elseif ( 'all' == $qv['fields'] ) {
241
			$this->query_fields = "{$this->db->users}.*";
242
		} else {
243
			$this->query_fields = "{$this->db->users}.ID";
244
		}
245
246
		if ( isset( $qv['count_total'] ) && $qv['count_total'] )
247
			$this->query_fields = 'SQL_CALC_FOUND_ROWS ' . $this->query_fields;
248
249
		$this->query_from = "FROM {$this->db->users}";
250
		$this->query_where = "WHERE 1=1";
251
252
		// Parse and sanitize 'include', for use by 'orderby' as well as 'include' below.
253
		if ( ! empty( $qv['include'] ) ) {
254
			$include = wp_parse_id_list( $qv['include'] );
255
		} else {
256
			$include = false;
257
		}
258
259
		$blog_id = 0;
260
		if ( isset( $qv['blog_id'] ) ) {
261
			$blog_id = absint( $qv['blog_id'] );
262
		}
263
264
		if ( $qv['has_published_posts'] && $blog_id ) {
265
			if ( true === $qv['has_published_posts'] ) {
266
				$post_types = get_post_types( array( 'public' => true ) );
267
			} else {
268
				$post_types = (array) $qv['has_published_posts'];
269
			}
270
271
			foreach ( $post_types as &$post_type ) {
272
				$post_type = $this->db->prepare( '%s', $post_type );
273
			}
274
275
			$posts_table = $this->db->get_blog_prefix( $blog_id ) . 'posts';
276
			$this->query_where .= " AND {$this->db->users}.ID IN ( SELECT DISTINCT $posts_table.post_author FROM $posts_table WHERE $posts_table.post_status = 'publish' AND $posts_table.post_type IN ( " . join( ", ", $post_types ) . " ) )";
277
		}
278
279
		// Meta query.
280
		$this->meta_query = new WP_Meta_Query();
281
		$this->meta_query->parse_query_vars( $qv );
282
283
		if ( isset( $qv['who'] ) && 'authors' == $qv['who'] && $blog_id ) {
284
			$who_query = array(
285
				'key' => $this->db->get_blog_prefix( $blog_id ) . 'user_level',
286
				'value' => 0,
287
				'compare' => '!=',
288
			);
289
290
			// Prevent extra meta query.
291
			$qv['blog_id'] = $blog_id = 0;
292
293 View Code Duplication
			if ( empty( $this->meta_query->queries ) ) {
294
				$this->meta_query->queries = array( $who_query );
295
			} else {
296
				// Append the cap query to the original queries and reparse the query.
297
				$this->meta_query->queries = array(
298
					'relation' => 'AND',
299
					array( $this->meta_query->queries, $who_query ),
300
				);
301
			}
302
303
			$this->meta_query->parse_query_vars( $this->meta_query->queries );
304
		}
305
306
		$roles = array();
307
		if ( isset( $qv['role'] ) ) {
308 View Code Duplication
			if ( is_array( $qv['role'] ) ) {
309
				$roles = $qv['role'];
310
			} elseif ( is_string( $qv['role'] ) && ! empty( $qv['role'] ) ) {
311
				$roles = array_map( 'trim', explode( ',', $qv['role'] ) );
312
			}
313
		}
314
315
		$role__in = array();
316
		if ( isset( $qv['role__in'] ) ) {
317
			$role__in = (array) $qv['role__in'];
318
		}
319
320
		$role__not_in = array();
321
		if ( isset( $qv['role__not_in'] ) ) {
322
			$role__not_in = (array) $qv['role__not_in'];
323
		}
324
325
		if ( $blog_id && ( ! empty( $roles ) || ! empty( $role__in ) || ! empty( $role__not_in ) || is_multisite() ) ) {
326
			$role_queries  = array();
327
328
			$roles_clauses = array( 'relation' => 'AND' );
329 View Code Duplication
			if ( ! empty( $roles ) ) {
330
				foreach ( $roles as $role ) {
331
					$roles_clauses[] = array(
332
						'key'     => $this->db->get_blog_prefix( $blog_id ) . 'capabilities',
333
						'value'   => '"' . $role . '"',
334
						'compare' => 'LIKE',
335
					);
336
				}
337
338
				$role_queries[] = $roles_clauses;
339
			}
340
341
			$role__in_clauses = array( 'relation' => 'OR' );
342 View Code Duplication
			if ( ! empty( $role__in ) ) {
343
				foreach ( $role__in as $role ) {
344
					$role__in_clauses[] = array(
345
						'key'     => $this->db->get_blog_prefix( $blog_id ) . 'capabilities',
346
						'value'   => '"' . $role . '"',
347
						'compare' => 'LIKE',
348
					);
349
				}
350
351
				$role_queries[] = $role__in_clauses;
352
			}
353
354
			$role__not_in_clauses = array( 'relation' => 'AND' );
355 View Code Duplication
			if ( ! empty( $role__not_in ) ) {
356
				foreach ( $role__not_in as $role ) {
357
					$role__not_in_clauses[] = array(
358
						'key'     => $this->db->get_blog_prefix( $blog_id ) . 'capabilities',
359
						'value'   => '"' . $role . '"',
360
						'compare' => 'NOT LIKE',
361
					);
362
				}
363
364
				$role_queries[] = $role__not_in_clauses;
365
			}
366
367
			// If there are no specific roles named, make sure the user is a member of the site.
368
			if ( empty( $role_queries ) ) {
369
				$role_queries[] = array(
370
					'key' => $this->db->get_blog_prefix( $blog_id ) . 'capabilities',
371
					'compare' => 'EXISTS',
372
				);
373
			}
374
375
			// Specify that role queries should be joined with AND.
376
			$role_queries['relation'] = 'AND';
377
378 View Code Duplication
			if ( empty( $this->meta_query->queries ) ) {
379
				$this->meta_query->queries = $role_queries;
380
			} else {
381
				// Append the cap query to the original queries and reparse the query.
382
				$this->meta_query->queries = array(
383
					'relation' => 'AND',
384
					array( $this->meta_query->queries, $role_queries ),
385
				);
386
			}
387
388
			$this->meta_query->parse_query_vars( $this->meta_query->queries );
389
		}
390
391
		if ( ! empty( $this->meta_query->queries ) ) {
392
			$clauses = $this->meta_query->get_sql( 'user', $this->db->users, 'ID', $this );
393
			$this->query_from .= $clauses['join'];
394
			$this->query_where .= $clauses['where'];
395
396
			if ( $this->meta_query->has_or_relation() ) {
397
				$this->query_fields = 'DISTINCT ' . $this->query_fields;
398
			}
399
		}
400
401
		// sorting
402
		$qv['order'] = isset( $qv['order'] ) ? strtoupper( $qv['order'] ) : '';
403
		$order = $this->parse_order( $qv['order'] );
404
405
		if ( empty( $qv['orderby'] ) ) {
406
			// Default order is by 'user_login'.
407
			$ordersby = array( 'user_login' => $order );
408
		} elseif ( is_array( $qv['orderby'] ) ) {
409
			$ordersby = $qv['orderby'];
410
		} else {
411
			// 'orderby' values may be a comma- or space-separated list.
412
			$ordersby = preg_split( '/[,\s]+/', $qv['orderby'] );
413
		}
414
415
		$orderby_array = array();
416
		foreach ( $ordersby as $_key => $_value ) {
417
			if ( ! $_value ) {
418
				continue;
419
			}
420
421
			if ( is_int( $_key ) ) {
422
				// Integer key means this is a flat array of 'orderby' fields.
423
				$_orderby = $_value;
424
				$_order = $order;
425
			} else {
426
				// Non-integer key means this the key is the field and the value is ASC/DESC.
427
				$_orderby = $_key;
428
				$_order = $_value;
429
			}
430
431
			$parsed = $this->parse_orderby( $_orderby );
432
433
			if ( ! $parsed ) {
434
				continue;
435
			}
436
437
			$orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
438
		}
439
440
		// If no valid clauses were found, order by user_login.
441
		if ( empty( $orderby_array ) ) {
442
			$orderby_array[] = "user_login $order";
443
		}
444
445
		$this->query_orderby = 'ORDER BY ' . implode( ', ', $orderby_array );
446
447
		// limit
448
		if ( isset( $qv['number'] ) && $qv['number'] > 0 ) {
449
			if ( $qv['offset'] ) {
450
				$this->query_limit = $this->db->prepare("LIMIT %d, %d", $qv['offset'], $qv['number']);
451
			} else {
452
				$this->query_limit = $this->db->prepare( "LIMIT %d, %d", $qv['number'] * ( $qv['paged'] - 1 ), $qv['number'] );
453
			}
454
		}
455
456
		$search = '';
457
		if ( isset( $qv['search'] ) )
458
			$search = trim( $qv['search'] );
459
460
		if ( $search ) {
461
			$leading_wild = ( ltrim($search, '*') != $search );
462
			$trailing_wild = ( rtrim($search, '*') != $search );
463
			if ( $leading_wild && $trailing_wild )
464
				$wild = 'both';
465
			elseif ( $leading_wild )
466
				$wild = 'leading';
467
			elseif ( $trailing_wild )
468
				$wild = 'trailing';
469
			else
470
				$wild = false;
471
			if ( $wild )
472
				$search = trim($search, '*');
473
474
			$search_columns = array();
475
			if ( $qv['search_columns'] )
476
				$search_columns = array_intersect( $qv['search_columns'], array( 'ID', 'user_login', 'user_email', 'user_url', 'user_nicename' ) );
477
			if ( ! $search_columns ) {
478
				if ( false !== strpos( $search, '@') )
479
					$search_columns = array('user_email');
480
				elseif ( is_numeric($search) )
481
					$search_columns = array('user_login', 'ID');
482
				elseif ( preg_match('|^https?://|', $search) && ! ( is_multisite() && wp_is_large_network( 'users' ) ) )
483
					$search_columns = array('user_url');
484
				else
485
					$search_columns = array('user_login', 'user_url', 'user_email', 'user_nicename', 'display_name');
486
			}
487
488
			/**
489
			 * Filters the columns to search in a WP_User_Query search.
490
			 *
491
			 * The default columns depend on the search term, and include 'user_email',
492
			 * 'user_login', 'ID', 'user_url', 'display_name', and 'user_nicename'.
493
			 *
494
			 * @since 3.6.0
495
			 *
496
			 * @param array         $search_columns Array of column names to be searched.
497
			 * @param string        $search         Text being searched.
498
			 * @param WP_User_Query $this           The current WP_User_Query instance.
499
			 */
500
			$search_columns = apply_filters( 'user_search_columns', $search_columns, $search, $this );
501
502
			$this->query_where .= $this->get_search_sql( $search, $search_columns, $wild );
0 ignored issues
show
Bug introduced by
It seems like $wild can also be of type string; however, WP_User_Query::get_search_sql() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
503
		}
504
505
		if ( ! empty( $include ) ) {
506
			// Sanitized earlier.
507
			$ids = implode( ',', $include );
508
			$this->query_where .= " AND {$this->db->users}.ID IN ($ids)";
509
		} elseif ( ! empty( $qv['exclude'] ) ) {
510
			$ids = implode( ',', wp_parse_id_list( $qv['exclude'] ) );
511
			$this->query_where .= " AND {$this->db->users}.ID NOT IN ($ids)";
512
		}
513
514
		// Date queries are allowed for the user_registered field.
515
		if ( ! empty( $qv['date_query'] ) && is_array( $qv['date_query'] ) ) {
516
			$date_query = new WP_Date_Query( $qv['date_query'], 'user_registered' );
517
			$this->query_where .= $date_query->get_sql();
518
		}
519
520
		/**
521
		 * Fires after the WP_User_Query has been parsed, and before
522
		 * the query is executed.
523
		 *
524
		 * The passed WP_User_Query object contains SQL parts formed
525
		 * from parsing the given query.
526
		 *
527
		 * @since 3.1.0
528
		 *
529
		 * @param WP_User_Query $this The current WP_User_Query instance,
530
		 *                            passed by reference.
531
		 */
532
		do_action_ref_array( 'pre_user_query', array( &$this ) );
533
	}
534
535
	/**
536
	 * Execute the query, with the current variables.
537
	 *
538
	 * @since 3.1.0
539
	 */
540
	public function query() {
541
		$qv =& $this->query_vars;
542
543
		$this->request = "SELECT $this->query_fields $this->query_from $this->query_where $this->query_orderby $this->query_limit";
544
545
		if ( is_array( $qv['fields'] ) || 'all' == $qv['fields'] ) {
546
			$this->results = $this->db->get_results( $this->request );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->get_results($this->request) can be null. However, the property $results is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
547
		} else {
548
			$this->results = $this->db->get_col( $this->request );
549
		}
550
551
		/**
552
		 * Filters SELECT FOUND_ROWS() query for the current WP_User_Query instance.
553
		 *
554
		 * @since 3.2.0
555
		 *
556
		 * @param string $sql The SELECT FOUND_ROWS() query for the current WP_User_Query.
557
		 */
558
		if ( isset( $qv['count_total'] ) && $qv['count_total'] ) {
559
			$this->total_users = $this->db->get_var( apply_filters( 'found_users_query', 'SELECT FOUND_ROWS()' ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->get_var(apply...'SELECT FOUND_ROWS()')) can also be of type string. However, the property $total_users is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
560
		}
561
562
		if ( ! $this->results ) {
563
			return;
564
		}
565
566
		if ( 'all_with_meta' == $qv['fields'] ) {
567
			cache_users( $this->results );
568
569
			$r = array();
570
			foreach ( $this->results as $userid )
571
				$r[ $userid ] = new WP_User( $userid, '', $qv['blog_id'] );
572
573
			$this->results = $r;
574
		} elseif ( 'all' == $qv['fields'] ) {
575
			foreach ( $this->results as $key => $user ) {
576
				$this->results[ $key ] = new WP_User( $user, '', $qv['blog_id'] );
577
			}
578
		}
579
	}
580
581
	/**
582
	 * Retrieve query variable.
583
	 *
584
	 * @since 3.5.0
585
	 * @access public
586
	 *
587
	 * @param string $query_var Query variable key.
588
	 * @return mixed
589
	 */
590
	public function get( $query_var ) {
591
		if ( isset( $this->query_vars[$query_var] ) )
592
			return $this->query_vars[$query_var];
593
594
		return null;
595
	}
596
597
	/**
598
	 * Set query variable.
599
	 *
600
	 * @since 3.5.0
601
	 * @access public
602
	 *
603
	 * @param string $query_var Query variable key.
604
	 * @param mixed $value Query variable value.
605
	 */
606
	public function set( $query_var, $value ) {
607
		$this->query_vars[$query_var] = $value;
608
	}
609
610
	/**
611
	 * Used internally to generate an SQL string for searching across multiple columns
612
	 *
613
	 * @access protected
614
	 * @since 3.1.0
615
	 *
616
	 * @param string $string
617
	 * @param array  $cols
618
	 * @param bool   $wild   Whether to allow wildcard searches. Default is false for Network Admin, true for single site.
619
	 *                       Single site allows leading and trailing wildcards, Network Admin only trailing.
620
	 * @return string
621
	 */
622
	protected function get_search_sql( $string, $cols, $wild = false ) {
623
		$searches = array();
624
		$leading_wild = ( 'leading' == $wild || 'both' == $wild ) ? '%' : '';
625
		$trailing_wild = ( 'trailing' == $wild || 'both' == $wild ) ? '%' : '';
626
		$like = $leading_wild . $this->db->esc_like( $string ) . $trailing_wild;
627
628
		foreach ( $cols as $col ) {
629
			if ( 'ID' == $col ) {
630
				$searches[] = $this->db->prepare( "$col = %s", $string );
631
			} else {
632
				$searches[] = $this->db->prepare( "$col LIKE %s", $like );
633
			}
634
		}
635
636
		return ' AND (' . implode(' OR ', $searches) . ')';
637
	}
638
639
	/**
640
	 * Return the list of users.
641
	 *
642
	 * @since 3.1.0
643
	 * @access public
644
	 *
645
	 * @return array Array of results.
646
	 */
647
	public function get_results() {
648
		return $this->results;
649
	}
650
651
	/**
652
	 * Return the total number of users for the current query.
653
	 *
654
	 * @since 3.1.0
655
	 * @access public
656
	 *
657
	 * @return int Number of total users.
658
	 */
659
	public function get_total() {
660
		return $this->total_users;
661
	}
662
663
	/**
664
	 * Parse and sanitize 'orderby' keys passed to the user query.
665
	 *
666
	 * @since 4.2.0
667
	 * @access protected
668
	 *
669
	 * @param string $orderby Alias for the field to order by.
670
	 * @return string Value to used in the ORDER clause, if `$orderby` is valid.
671
	 */
672
	protected function parse_orderby( $orderby ) {
673
		$meta_query_clauses = $this->meta_query->get_clauses();
674
675
		$_orderby = '';
676
		if ( in_array( $orderby, array( 'login', 'nicename', 'email', 'url', 'registered' ) ) ) {
677
			$_orderby = 'user_' . $orderby;
678
		} elseif ( in_array( $orderby, array( 'user_login', 'user_nicename', 'user_email', 'user_url', 'user_registered' ) ) ) {
679
			$_orderby = $orderby;
680
		} elseif ( 'name' == $orderby || 'display_name' == $orderby ) {
681
			$_orderby = 'display_name';
682
		} elseif ( 'post_count' == $orderby ) {
683
			// todo: avoid the JOIN
684
			$where = get_posts_by_author_sql( 'post' );
685
			$this->query_from .= " LEFT OUTER JOIN (
686
				SELECT post_author, COUNT(*) as post_count
687
				FROM {$this->db->posts}
688
				$where
689
				GROUP BY post_author
690
			) p ON ({$this->db->users}.ID = p.post_author)
691
			";
692
			$_orderby = 'post_count';
693
		} elseif ( 'ID' == $orderby || 'id' == $orderby ) {
694
			$_orderby = 'ID';
695
		} elseif ( 'meta_value' == $orderby || $this->get( 'meta_key' ) == $orderby ) {
696
			$_orderby = "{$this->db->usermeta}.meta_value";
697
		} elseif ( 'meta_value_num' == $orderby ) {
698
			$_orderby = "{$this->db->usermeta}.meta_value+0";
699
		} elseif ( 'include' === $orderby && ! empty( $this->query_vars['include'] ) ) {
700
			$include = wp_parse_id_list( $this->query_vars['include'] );
701
			$include_sql = implode( ',', $include );
702
			$_orderby = "FIELD( {$this->db->users}.ID, $include_sql )";
703
		} elseif ( isset( $meta_query_clauses[ $orderby ] ) ) {
704
			$meta_clause = $meta_query_clauses[ $orderby ];
705
			$_orderby = sprintf( "CAST(%s.meta_value AS %s)", esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) );
706
		}
707
708
		return $_orderby;
709
	}
710
711
	/**
712
	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
713
	 *
714
	 * @since 4.2.0
715
	 * @access protected
716
	 *
717
	 * @param string $order The 'order' query variable.
718
	 * @return string The sanitized 'order' query variable.
719
	 */
720 View Code Duplication
	protected function parse_order( $order ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
721
		if ( ! is_string( $order ) || empty( $order ) ) {
722
			return 'DESC';
723
		}
724
725
		if ( 'ASC' === strtoupper( $order ) ) {
726
			return 'ASC';
727
		} else {
728
			return 'DESC';
729
		}
730
	}
731
732
	/**
733
	 * Make private properties readable for backward compatibility.
734
	 *
735
	 * @since 4.0.0
736
	 * @access public
737
	 *
738
	 * @param string $name Property to get.
739
	 * @return mixed Property.
740
	 */
741
	public function __get( $name ) {
742
		if ( in_array( $name, $this->compat_fields ) ) {
743
			return $this->$name;
744
		}
745
	}
746
747
	/**
748
	 * Make private properties settable for backward compatibility.
749
	 *
750
	 * @since 4.0.0
751
	 * @access public
752
	 *
753
	 * @param string $name  Property to check if set.
754
	 * @param mixed  $value Property value.
755
	 * @return mixed Newly-set property.
756
	 */
757
	public function __set( $name, $value ) {
758
		if ( in_array( $name, $this->compat_fields ) ) {
759
			return $this->$name = $value;
760
		}
761
	}
762
763
	/**
764
	 * Make private properties checkable for backward compatibility.
765
	 *
766
	 * @since 4.0.0
767
	 * @access public
768
	 *
769
	 * @param string $name Property to check if set.
770
	 * @return bool Whether the property is set.
771
	 */
772
	public function __isset( $name ) {
773
		if ( in_array( $name, $this->compat_fields ) ) {
774
			return isset( $this->$name );
775
		}
776
	}
777
778
	/**
779
	 * Make private properties un-settable for backward compatibility.
780
	 *
781
	 * @since 4.0.0
782
	 * @access public
783
	 *
784
	 * @param string $name Property to unset.
785
	 */
786
	public function __unset( $name ) {
787
		if ( in_array( $name, $this->compat_fields ) ) {
788
			unset( $this->$name );
789
		}
790
	}
791
792
	/**
793
	 * Make private/protected methods readable for backward compatibility.
794
	 *
795
	 * @since 4.0.0
796
	 * @access public
797
	 *
798
	 * @param callable $name      Method to call.
799
	 * @param array    $arguments Arguments to pass when calling.
800
	 * @return mixed Return value of the callback, false otherwise.
801
	 */
802
	public function __call( $name, $arguments ) {
803
		if ( 'get_search_sql' === $name ) {
804
			return call_user_func_array( array( $this, $name ), $arguments );
805
		}
806
		return false;
807
	}
808
}
809