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-comment-query.php (6 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
 * Comment API: WP_Comment_Query class
4
 *
5
 * @package WordPress
6
 * @subpackage Comments
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used for querying comments.
12
 *
13
 * @since 3.1.0
14
 *
15
 * @see WP_Comment_Query::__construct() for accepted arguments.
16
 */
17
class WP_Comment_Query {
18
19
	/**
20
	 * SQL for database query.
21
	 *
22
	 * @since 4.0.1
23
	 * @access public
24
	 * @var string
25
	 */
26
	public $request;
27
28
	/**
29
	 * Metadata query container
30
	 *
31
	 * @since 3.5.0
32
	 * @access public
33
	 * @var object WP_Meta_Query
34
	 */
35
	public $meta_query = false;
36
37
	/**
38
	 * Metadata query clauses.
39
	 *
40
	 * @since 4.4.0
41
	 * @access protected
42
	 * @var array
43
	 */
44
	protected $meta_query_clauses;
45
46
	/**
47
	 * SQL query clauses.
48
	 *
49
	 * @since 4.4.0
50
	 * @access protected
51
	 * @var array
52
	 */
53
	protected $sql_clauses = array(
54
		'select'  => '',
55
		'from'    => '',
56
		'where'   => array(),
57
		'groupby' => '',
58
		'orderby' => '',
59
		'limits'  => '',
60
	);
61
62
	/**
63
	 * SQL WHERE clause.
64
	 *
65
	 * Stored after the {@see 'comments_clauses'} filter is run on the compiled WHERE sub-clauses.
66
	 *
67
	 * @since 4.4.2
68
	 * @access protected
69
	 * @var string
70
	 */
71
	protected $filtered_where_clause;
72
73
	/**
74
	 * Date query container
75
	 *
76
	 * @since 3.7.0
77
	 * @access public
78
	 * @var object WP_Date_Query
79
	 */
80
	public $date_query = false;
81
82
	/**
83
	 * Query vars set by the user.
84
	 *
85
	 * @since 3.1.0
86
	 * @access public
87
	 * @var array
88
	 */
89
	public $query_vars;
90
91
	/**
92
	 * Default values for query vars.
93
	 *
94
	 * @since 4.2.0
95
	 * @access public
96
	 * @var array
97
	 */
98
	public $query_var_defaults;
99
100
	/**
101
	 * List of comments located by the query.
102
	 *
103
	 * @since 4.0.0
104
	 * @access public
105
	 * @var array
106
	 */
107
	public $comments;
108
109
	/**
110
	 * The amount of found comments for the current query.
111
	 *
112
	 * @since 4.4.0
113
	 * @access public
114
	 * @var int
115
	 */
116
	public $found_comments = 0;
117
118
	/**
119
	 * The number of pages.
120
	 *
121
	 * @since 4.4.0
122
	 * @access public
123
	 * @var int
124
	 */
125
	public $max_num_pages = 0;
126
127
	/**
128
	 * Make private/protected methods readable for backward compatibility.
129
	 *
130
	 * @since 4.0.0
131
	 * @access public
132
	 *
133
	 * @param callable $name      Method to call.
134
	 * @param array    $arguments Arguments to pass when calling.
135
	 * @return mixed|false Return value of the callback, false otherwise.
136
	 */
137
	public function __call( $name, $arguments ) {
138
		if ( 'get_search_sql' === $name ) {
139
			return call_user_func_array( array( $this, $name ), $arguments );
140
		}
141
		return false;
142
	}
143
144
	/**
145
	 * Constructor.
146
	 *
147
	 * Sets up the comment query, based on the query vars passed.
148
	 *
149
	 * @since 4.2.0
150
	 * @since 4.4.0 `$parent__in` and `$parent__not_in` were added.
151
	 * @since 4.4.0 Order by `comment__in` was added. `$update_comment_meta_cache`, `$no_found_rows`,
152
	 *              `$hierarchical`, and `$update_comment_post_cache` were added.
153
	 * @since 4.5.0 Introduced the `$author_url` argument.
154
	 * @since 4.6.0 Introduced the `$cache_domain` argument.
155
	 * @access public
156
	 *
157
	 * @param string|array $query {
158
	 *     Optional. Array or query string of comment query parameters. Default empty.
159
	 *
160
	 *     @type string       $author_email              Comment author email address. Default empty.
161
	 *     @type string       $author_url                Comment author URL. Default empty.
162
	 *     @type array        $author__in                Array of author IDs to include comments for. Default empty.
163
	 *     @type array        $author__not_in            Array of author IDs to exclude comments for. Default empty.
164
	 *     @type array        $comment__in               Array of comment IDs to include. Default empty.
165
	 *     @type array        $comment__not_in           Array of comment IDs to exclude. Default empty.
166
	 *     @type bool         $count                     Whether to return a comment count (true) or array of
167
	 *                                                   comment objects (false). Default false.
168
	 *     @type array        $date_query                Date query clauses to limit comments by. See WP_Date_Query.
169
	 *                                                   Default null.
170
	 *     @type string       $fields                    Comment fields to return. Accepts 'ids' for comment IDs
171
	 *                                                   only or empty for all fields. Default empty.
172
	 *     @type int          $ID                        Currently unused.
173
	 *     @type array        $include_unapproved        Array of IDs or email addresses of users whose unapproved
174
	 *                                                   comments will be returned by the query regardless of
175
	 *                                                   `$status`. Default empty.
176
	 *     @type int          $karma                     Karma score to retrieve matching comments for.
177
	 *                                                   Default empty.
178
	 *     @type string       $meta_key                  Include comments with a matching comment meta key.
179
	 *                                                   Default empty.
180
	 *     @type string       $meta_value                Include comments with a matching comment meta value.
181
	 *                                                   Requires `$meta_key` to be set. Default empty.
182
	 *     @type array        $meta_query                Meta query clauses to limit retrieved comments by.
183
	 *                                                   See WP_Meta_Query. Default empty.
184
	 *     @type int          $number                    Maximum number of comments to retrieve.
185
	 *                                                   Default empty (no limit).
186
	 *     @type int          $offset                    Number of comments to offset the query. Used to build
187
	 *                                                   LIMIT clause. Default 0.
188
	 *     @type bool         $no_found_rows             Whether to disable the `SQL_CALC_FOUND_ROWS` query.
189
	 *                                                   Default: true.
190
	 *     @type string|array $orderby                   Comment status or array of statuses. To use 'meta_value'
191
	 *                                                   or 'meta_value_num', `$meta_key` must also be defined.
192
	 *                                                   To sort by a specific `$meta_query` clause, use that
193
	 *                                                   clause's array key. Accepts 'comment_agent',
194
	 *                                                   'comment_approved', 'comment_author',
195
	 *                                                   'comment_author_email', 'comment_author_IP',
196
	 *                                                   'comment_author_url', 'comment_content', 'comment_date',
197
	 *                                                   'comment_date_gmt', 'comment_ID', 'comment_karma',
198
	 *                                                   'comment_parent', 'comment_post_ID', 'comment_type',
199
	 *                                                   'user_id', 'comment__in', 'meta_value', 'meta_value_num',
200
	 *                                                   the value of $meta_key, and the array keys of
201
	 *                                                   `$meta_query`. Also accepts false, an empty array, or
202
	 *                                                   'none' to disable `ORDER BY` clause.
203
	 *                                                   Default: 'comment_date_gmt'.
204
	 *     @type string       $order                     How to order retrieved comments. Accepts 'ASC', 'DESC'.
205
	 *                                                   Default: 'DESC'.
206
	 *     @type int          $parent                    Parent ID of comment to retrieve children of.
207
	 *                                                   Default empty.
208
	 *     @type array        $parent__in                Array of parent IDs of comments to retrieve children for.
209
	 *                                                   Default empty.
210
	 *     @type array        $parent__not_in            Array of parent IDs of comments *not* to retrieve
211
	 *                                                   children for. Default empty.
212
	 *     @type array        $post_author__in           Array of author IDs to retrieve comments for.
213
	 *                                                   Default empty.
214
	 *     @type array        $post_author__not_in       Array of author IDs *not* to retrieve comments for.
215
	 *                                                   Default empty.
216
	 *     @type int          $post_ID                   Currently unused.
217
	 *     @type int          $post_id                   Limit results to those affiliated with a given post ID.
218
	 *                                                   Default 0.
219
	 *     @type array        $post__in                  Array of post IDs to include affiliated comments for.
220
	 *                                                   Default empty.
221
	 *     @type array        $post__not_in              Array of post IDs to exclude affiliated comments for.
222
	 *                                                   Default empty.
223
	 *     @type int          $post_author               Post author ID to limit results by. Default empty.
224
	 *     @type string|array $post_status               Post status or array of post statuses to retrieve
225
	 *                                                   affiliated comments for. Pass 'any' to match any value.
226
	 *                                                   Default empty.
227
	 *     @type string       $post_type                 Post type or array of post types to retrieve affiliated
228
	 *                                                   comments for. Pass 'any' to match any value. Default empty.
229
	 *     @type string       $post_name                 Post name to retrieve affiliated comments for.
230
	 *                                                   Default empty.
231
	 *     @type int          $post_parent               Post parent ID to retrieve affiliated comments for.
232
	 *                                                   Default empty.
233
	 *     @type string       $search                    Search term(s) to retrieve matching comments for.
234
	 *                                                   Default empty.
235
	 *     @type string       $status                    Comment status to limit results by. Accepts 'hold'
236
	 *                                                   (`comment_status=0`), 'approve' (`comment_status=1`),
237
	 *                                                   'all', or a custom comment status. Default 'all'.
238
	 *     @type string|array $type                      Include comments of a given type, or array of types.
239
	 *                                                   Accepts 'comment', 'pings' (includes 'pingback' and
240
	 *                                                   'trackback'), or anycustom type string. Default empty.
241
	 *     @type array        $type__in                  Include comments from a given array of comment types.
242
	 *                                                   Default empty.
243
	 *     @type array        $type__not_in              Exclude comments from a given array of comment types.
244
	 *                                                   Default empty.
245
	 *     @type int          $user_id                   Include comments for a specific user ID. Default empty.
246
	 *     @type bool|string  $hierarchical              Whether to include comment descendants in the results.
247
	 *                                                   'threaded' returns a tree, with each comment's children
248
	 *                                                   stored in a `children` property on the `WP_Comment`
249
	 *                                                   object. 'flat' returns a flat array of found comments plus
250
	 *                                                   their children. Pass `false` to leave out descendants.
251
	 *                                                   The parameter is ignored (forced to `false`) when
252
	 *                                                   `$fields` is 'ids' or 'counts'. Accepts 'threaded',
253
	 *                                                   'flat', or false. Default: false.
254
 	 *     @type string       $cache_domain              Unique cache key to be produced when this query is stored in
255
	 *                                                   an object cache. Default is 'core'.
256
	 *     @type bool         $update_comment_meta_cache Whether to prime the metadata cache for found comments.
257
	 *                                                   Default true.
258
	 *     @type bool         $update_comment_post_cache Whether to prime the cache for comment posts.
259
	 *                                                   Default false.
260
	 * }
261
	 */
262
	public function __construct( $query = '' ) {
263
		$this->query_var_defaults = array(
264
			'author_email' => '',
265
			'author_url' => '',
266
			'author__in' => '',
267
			'author__not_in' => '',
268
			'include_unapproved' => '',
269
			'fields' => '',
270
			'ID' => '',
271
			'comment__in' => '',
272
			'comment__not_in' => '',
273
			'karma' => '',
274
			'number' => '',
275
			'offset' => '',
276
			'no_found_rows' => true,
277
			'orderby' => '',
278
			'order' => 'DESC',
279
			'parent' => '',
280
			'parent__in' => '',
281
			'parent__not_in' => '',
282
			'post_author__in' => '',
283
			'post_author__not_in' => '',
284
			'post_ID' => '',
285
			'post_id' => 0,
286
			'post__in' => '',
287
			'post__not_in' => '',
288
			'post_author' => '',
289
			'post_name' => '',
290
			'post_parent' => '',
291
			'post_status' => '',
292
			'post_type' => '',
293
			'status' => 'all',
294
			'type' => '',
295
			'type__in' => '',
296
			'type__not_in' => '',
297
			'user_id' => '',
298
			'search' => '',
299
			'count' => false,
300
			'meta_key' => '',
301
			'meta_value' => '',
302
			'meta_query' => '',
303
			'date_query' => null, // See WP_Date_Query
304
			'hierarchical' => false,
305
			'cache_domain' => 'core',
306
			'update_comment_meta_cache' => true,
307
			'update_comment_post_cache' => false,
308
		);
309
310
		if ( ! empty( $query ) ) {
311
			$this->query( $query );
312
		}
313
	}
314
315
	/**
316
	 * Parse arguments passed to the comment query with default query parameters.
317
	 *
318
	 * @since 4.2.0 Extracted from WP_Comment_Query::query().
319
	 *
320
	 * @access public
321
	 *
322
	 * @param string|array $query WP_Comment_Query arguments. See WP_Comment_Query::__construct()
323
	 */
324 View Code Duplication
	public function parse_query( $query = '' ) {
325
		if ( empty( $query ) ) {
326
			$query = $this->query_vars;
327
		}
328
329
		$this->query_vars = wp_parse_args( $query, $this->query_var_defaults );
330
331
		/**
332
		 * Fires after the comment query vars have been parsed.
333
		 *
334
		 * @since 4.2.0
335
		 *
336
		 * @param WP_Comment_Query &$this The WP_Comment_Query instance (passed by reference).
337
		 */
338
		do_action_ref_array( 'parse_comment_query', array( &$this ) );
339
	}
340
341
	/**
342
	 * Sets up the WordPress query for retrieving comments.
343
	 *
344
	 * @since 3.1.0
345
	 * @since 4.1.0 Introduced 'comment__in', 'comment__not_in', 'post_author__in',
346
	 *              'post_author__not_in', 'author__in', 'author__not_in', 'post__in',
347
	 *              'post__not_in', 'include_unapproved', 'type__in', and 'type__not_in'
348
	 *              arguments to $query_vars.
349
	 * @since 4.2.0 Moved parsing to WP_Comment_Query::parse_query().
350
	 * @access public
351
	 *
352
	 * @param string|array $query Array or URL query string of parameters.
353
	 * @return array|int List of comments, or number of comments when 'count' is passed as a query var.
354
	 */
355
	public function query( $query ) {
356
		$this->query_vars = wp_parse_args( $query );
357
		return $this->get_comments();
358
	}
359
360
	/**
361
	 * Get a list of comments matching the query vars.
362
	 *
363
	 * @since 4.2.0
364
	 * @access public
365
	 *
366
	 * @global wpdb $wpdb WordPress database abstraction object.
367
	 *
368
	 * @return int|array List of comments or number of found comments if `$count` argument is true.
369
	 */
370
	public function get_comments() {
371
		global $wpdb;
372
373
		$this->parse_query();
374
375
		// Parse meta query
376
		$this->meta_query = new WP_Meta_Query();
377
		$this->meta_query->parse_query_vars( $this->query_vars );
378
379
		/**
380
		 * Fires before comments are retrieved.
381
		 *
382
		 * @since 3.1.0
383
		 *
384
		 * @param WP_Comment_Query &$this Current instance of WP_Comment_Query, passed by reference.
385
		 */
386
		do_action_ref_array( 'pre_get_comments', array( &$this ) );
387
388
		// Reparse query vars, in case they were modified in a 'pre_get_comments' callback.
389
		$this->meta_query->parse_query_vars( $this->query_vars );
390
		if ( ! empty( $this->meta_query->queries ) ) {
391
			$this->meta_query_clauses = $this->meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $this );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->meta_query->get_s...s, 'comment_ID', $this) can also be of type false. However, the property $meta_query_clauses is declared as type array. 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...
392
		}
393
394
		// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
395
		$key = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ) ) );
396
		$last_changed = wp_cache_get_last_changed( 'comment' );
397
398
399
		$cache_key   = "get_comments:$key:$last_changed";
400
		$cache_value = wp_cache_get( $cache_key, 'comment' );
401
		if ( false === $cache_value ) {
402
			$comment_ids = $this->get_comment_ids();
403
			if ( $comment_ids ) {
404
				$this->set_found_comments();
405
			}
406
407
			$cache_value = array(
408
				'comment_ids'    => $comment_ids,
409
				'found_comments' => $this->found_comments,
410
			);
411
			wp_cache_add( $cache_key, $cache_value, 'comment' );
412
		} else {
413
			$comment_ids          = $cache_value['comment_ids'];
414
			$this->found_comments = $cache_value['found_comments'];
415
		}
416
417
		if ( $this->found_comments && $this->query_vars['number'] ) {
418
			$this->max_num_pages = ceil( $this->found_comments / $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_commen...->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...
419
		}
420
421
		// If querying for a count only, there's nothing more to do.
422
		if ( $this->query_vars['count'] ) {
423
			// $comment_ids is actually a count in this case.
424
			return intval( $comment_ids );
425
		}
426
427
		$comment_ids = array_map( 'intval', $comment_ids );
428
429
		if ( 'ids' == $this->query_vars['fields'] ) {
430
			$this->comments = $comment_ids;
431
			return $this->comments;
432
		}
433
434
		_prime_comment_caches( $comment_ids, $this->query_vars['update_comment_meta_cache'] );
435
436
		// Fetch full comment objects from the primed cache.
437
		$_comments = array();
438
		foreach ( $comment_ids as $comment_id ) {
439
			if ( $_comment = get_comment( $comment_id ) ) {
440
				$_comments[] = $_comment;
441
			}
442
		}
443
444
		// Prime comment post caches.
445
		if ( $this->query_vars['update_comment_post_cache'] ) {
446
			$comment_post_ids = array();
447
			foreach ( $_comments as $_comment ) {
448
				$comment_post_ids[] = $_comment->comment_post_ID;
449
			}
450
451
			_prime_post_caches( $comment_post_ids, false, false );
452
		}
453
454
		/**
455
		 * Filters the comment query results.
456
		 *
457
		 * @since 3.1.0
458
		 *
459
		 * @param array            $results  An array of comments.
460
		 * @param WP_Comment_Query &$this    Current instance of WP_Comment_Query, passed by reference.
461
		 */
462
		$_comments = apply_filters_ref_array( 'the_comments', array( $_comments, &$this ) );
463
464
		// Convert to WP_Comment instances
465
		$comments = array_map( 'get_comment', $_comments );
466
467
		if ( $this->query_vars['hierarchical'] ) {
468
			$comments = $this->fill_descendants( $comments );
469
		}
470
471
		$this->comments = $comments;
472
		return $this->comments;
473
	}
474
475
	/**
476
	 * Used internally to get a list of comment IDs matching the query vars.
477
	 *
478
	 * @since 4.4.0
479
	 * @access protected
480
	 *
481
	 * @global wpdb $wpdb WordPress database abstraction object.
482
	 */
483
	protected function get_comment_ids() {
484
		global $wpdb;
485
486
		// Assemble clauses related to 'comment_approved'.
487
		$approved_clauses = array();
488
489
		// 'status' accepts an array or a comma-separated string.
490
		$status_clauses = array();
491
		$statuses = $this->query_vars['status'];
492
		if ( ! is_array( $statuses ) ) {
493
			$statuses = preg_split( '/[\s,]+/', $statuses );
494
		}
495
496
		// 'any' overrides other statuses.
497
		if ( ! in_array( 'any', $statuses ) ) {
498
			foreach ( $statuses as $status ) {
499
				switch ( $status ) {
500
					case 'hold' :
501
						$status_clauses[] = "comment_approved = '0'";
502
						break;
503
504
					case 'approve' :
505
						$status_clauses[] = "comment_approved = '1'";
506
						break;
507
508
					case 'all' :
509
					case '' :
510
						$status_clauses[] = "( comment_approved = '0' OR comment_approved = '1' )";
511
						break;
512
513
					default :
514
						$status_clauses[] = $wpdb->prepare( "comment_approved = %s", $status );
515
						break;
516
				}
517
			}
518
519
			if ( ! empty( $status_clauses ) ) {
520
				$approved_clauses[] = '( ' . implode( ' OR ', $status_clauses ) . ' )';
521
			}
522
		}
523
524
		// User IDs or emails whose unapproved comments are included, regardless of $status.
525
		if ( ! empty( $this->query_vars['include_unapproved'] ) ) {
526
			$include_unapproved = $this->query_vars['include_unapproved'];
527
528
			// Accepts arrays or comma-separated strings.
529
			if ( ! is_array( $include_unapproved ) ) {
530
				$include_unapproved = preg_split( '/[\s,]+/', $include_unapproved );
531
			}
532
533
			$unapproved_ids = $unapproved_emails = array();
534
			foreach ( $include_unapproved as $unapproved_identifier ) {
535
				// Numeric values are assumed to be user ids.
536
				if ( is_numeric( $unapproved_identifier ) ) {
537
					$approved_clauses[] = $wpdb->prepare( "( user_id = %d AND comment_approved = '0' )", $unapproved_identifier );
538
539
				// Otherwise we match against email addresses.
540
				} else {
541
					$approved_clauses[] = $wpdb->prepare( "( comment_author_email = %s AND comment_approved = '0' )", $unapproved_identifier );
542
				}
543
			}
544
		}
545
546
		// Collapse comment_approved clauses into a single OR-separated clause.
547
		if ( ! empty( $approved_clauses ) ) {
548
			if ( 1 === count( $approved_clauses ) ) {
549
				$this->sql_clauses['where']['approved'] = $approved_clauses[0];
550
			} else {
551
				$this->sql_clauses['where']['approved'] = '( ' . implode( ' OR ', $approved_clauses ) . ' )';
552
			}
553
		}
554
555
		$order = ( 'ASC' == strtoupper( $this->query_vars['order'] ) ) ? 'ASC' : 'DESC';
556
557
		// Disable ORDER BY with 'none', an empty array, or boolean false.
558
		if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
559
			$orderby = '';
560
		} elseif ( ! empty( $this->query_vars['orderby'] ) ) {
561
			$ordersby = is_array( $this->query_vars['orderby'] ) ?
562
				$this->query_vars['orderby'] :
563
				preg_split( '/[,\s]/', $this->query_vars['orderby'] );
564
565
			$orderby_array = array();
566
			$found_orderby_comment_ID = false;
567
			foreach ( $ordersby as $_key => $_value ) {
568
				if ( ! $_value ) {
569
					continue;
570
				}
571
572
				if ( is_int( $_key ) ) {
573
					$_orderby = $_value;
574
					$_order = $order;
575
				} else {
576
					$_orderby = $_key;
577
					$_order = $_value;
578
				}
579
580
				if ( ! $found_orderby_comment_ID && in_array( $_orderby, array( 'comment_ID', 'comment__in' ) ) ) {
581
					$found_orderby_comment_ID = true;
582
				}
583
584
				$parsed = $this->parse_orderby( $_orderby );
585
586
				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...
587
					continue;
588
				}
589
590
				if ( 'comment__in' === $_orderby ) {
591
					$orderby_array[] = $parsed;
592
					continue;
593
				}
594
595
				$orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
596
			}
597
598
			// If no valid clauses were found, order by comment_date_gmt.
599
			if ( empty( $orderby_array ) ) {
600
				$orderby_array[] = "$wpdb->comments.comment_date_gmt $order";
601
			}
602
603
			// To ensure determinate sorting, always include a comment_ID clause.
604
			if ( ! $found_orderby_comment_ID ) {
605
				$comment_ID_order = '';
606
607
				// Inherit order from comment_date or comment_date_gmt, if available.
608
				foreach ( $orderby_array as $orderby_clause ) {
609
					if ( preg_match( '/comment_date(?:_gmt)*\ (ASC|DESC)/', $orderby_clause, $match ) ) {
610
						$comment_ID_order = $match[1];
611
						break;
612
					}
613
				}
614
615
				// If no date-related order is available, use the date from the first available clause.
616
				if ( ! $comment_ID_order ) {
617
					foreach ( $orderby_array as $orderby_clause ) {
618
						if ( false !== strpos( 'ASC', $orderby_clause ) ) {
619
							$comment_ID_order = 'ASC';
620
						} else {
621
							$comment_ID_order = 'DESC';
622
						}
623
624
						break;
625
					}
626
				}
627
628
				// Default to DESC.
629
				if ( ! $comment_ID_order ) {
630
					$comment_ID_order = 'DESC';
631
				}
632
633
				$orderby_array[] = "$wpdb->comments.comment_ID $comment_ID_order";
634
			}
635
636
			$orderby = implode( ', ', $orderby_array );
637
		} else {
638
			$orderby = "$wpdb->comments.comment_date_gmt $order";
639
		}
640
641
		$number = absint( $this->query_vars['number'] );
642
		$offset = absint( $this->query_vars['offset'] );
643
644 View Code Duplication
		if ( ! empty( $number ) ) {
645
			if ( $offset ) {
646
				$limits = 'LIMIT ' . $offset . ',' . $number;
647
			} else {
648
				$limits = 'LIMIT ' . $number;
649
			}
650
		}
651
652
		if ( $this->query_vars['count'] ) {
653
			$fields = 'COUNT(*)';
654
		} else {
655
			$fields = "$wpdb->comments.comment_ID";
656
		}
657
658
		$post_id = absint( $this->query_vars['post_id'] );
659
		if ( ! empty( $post_id ) ) {
660
			$this->sql_clauses['where']['post_id'] = $wpdb->prepare( 'comment_post_ID = %d', $post_id );
661
		}
662
663
		// Parse comment IDs for an IN clause.
664
		if ( ! empty( $this->query_vars['comment__in'] ) ) {
665
			$this->sql_clauses['where']['comment__in'] = "$wpdb->comments.comment_ID IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['comment__in'] ) ) . ' )';
666
		}
667
668
		// Parse comment IDs for a NOT IN clause.
669
		if ( ! empty( $this->query_vars['comment__not_in'] ) ) {
670
			$this->sql_clauses['where']['comment__not_in'] = "$wpdb->comments.comment_ID NOT IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['comment__not_in'] ) ) . ' )';
671
		}
672
673
		// Parse comment parent IDs for an IN clause.
674
		if ( ! empty( $this->query_vars['parent__in'] ) ) {
675
			$this->sql_clauses['where']['parent__in'] = 'comment_parent IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['parent__in'] ) ) . ' )';
676
		}
677
678
		// Parse comment parent IDs for a NOT IN clause.
679
		if ( ! empty( $this->query_vars['parent__not_in'] ) ) {
680
			$this->sql_clauses['where']['parent__not_in'] = 'comment_parent NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['parent__not_in'] ) ) . ' )';
681
		}
682
683
		// Parse comment post IDs for an IN clause.
684
		if ( ! empty( $this->query_vars['post__in'] ) ) {
685
			$this->sql_clauses['where']['post__in'] = 'comment_post_ID IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__in'] ) ) . ' )';
686
		}
687
688
		// Parse comment post IDs for a NOT IN clause.
689
		if ( ! empty( $this->query_vars['post__not_in'] ) ) {
690
			$this->sql_clauses['where']['post__not_in'] = 'comment_post_ID NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__not_in'] ) ) . ' )';
691
		}
692
693 View Code Duplication
		if ( '' !== $this->query_vars['author_email'] ) {
694
			$this->sql_clauses['where']['author_email'] = $wpdb->prepare( 'comment_author_email = %s', $this->query_vars['author_email'] );
695
		}
696
697 View Code Duplication
		if ( '' !== $this->query_vars['author_url'] ) {
698
			$this->sql_clauses['where']['author_url'] = $wpdb->prepare( 'comment_author_url = %s', $this->query_vars['author_url'] );
699
		}
700
701 View Code Duplication
		if ( '' !== $this->query_vars['karma'] ) {
702
			$this->sql_clauses['where']['karma'] = $wpdb->prepare( 'comment_karma = %d', $this->query_vars['karma'] );
703
		}
704
705
		// Filtering by comment_type: 'type', 'type__in', 'type__not_in'.
706
		$raw_types = array(
707
			'IN' => array_merge( (array) $this->query_vars['type'], (array) $this->query_vars['type__in'] ),
708
			'NOT IN' => (array) $this->query_vars['type__not_in'],
709
		);
710
711
		$comment_types = array();
712
		foreach ( $raw_types as $operator => $_raw_types ) {
713
			$_raw_types = array_unique( $_raw_types );
714
715
			foreach ( $_raw_types as $type ) {
716
				switch ( $type ) {
717
					// An empty translates to 'all', for backward compatibility
718
					case '':
719
					case 'all' :
720
						break;
721
722
					case 'comment':
723
					case 'comments':
724
						$comment_types[ $operator ][] = "''";
725
						break;
726
727
					case 'pings':
728
						$comment_types[ $operator ][] = "'pingback'";
729
						$comment_types[ $operator ][] = "'trackback'";
730
						break;
731
732
					default:
733
						$comment_types[ $operator ][] = $wpdb->prepare( '%s', $type );
734
						break;
735
				}
736
			}
737
738
			if ( ! empty( $comment_types[ $operator ] ) ) {
739
				$types_sql = implode( ', ', $comment_types[ $operator ] );
740
				$this->sql_clauses['where']['comment_type__' . strtolower( str_replace( ' ', '_', $operator ) ) ] = "comment_type $operator ($types_sql)";
741
			}
742
		}
743
744
		$parent = $this->query_vars['parent'];
745
		if ( $this->query_vars['hierarchical'] && ! $parent ) {
746
			$parent = 0;
747
		}
748
749
		if ( '' !== $parent ) {
750
			$this->sql_clauses['where']['parent'] = $wpdb->prepare( 'comment_parent = %d', $parent );
751
		}
752
753
		if ( is_array( $this->query_vars['user_id'] ) ) {
754
			$this->sql_clauses['where']['user_id'] = 'user_id IN (' . implode( ',', array_map( 'absint', $this->query_vars['user_id'] ) ) . ')';
755 View Code Duplication
		} elseif ( '' !== $this->query_vars['user_id'] ) {
756
			$this->sql_clauses['where']['user_id'] = $wpdb->prepare( 'user_id = %d', $this->query_vars['user_id'] );
757
		}
758
759
		// Falsy search strings are ignored.
760
		if ( strlen( $this->query_vars['search'] ) ) {
761
			$search_sql = $this->get_search_sql(
762
				$this->query_vars['search'],
763
				array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_content' )
764
			);
765
766
			// Strip leading 'AND'.
767
			$this->sql_clauses['where']['search'] = preg_replace( '/^\s*AND\s*/', '', $search_sql );
768
		}
769
770
		// If any post-related query vars are passed, join the posts table.
771
		$join_posts_table = false;
772
		$plucked = wp_array_slice_assoc( $this->query_vars, array( 'post_author', 'post_name', 'post_parent' ) );
773
		$post_fields = array_filter( $plucked );
774
775
		if ( ! empty( $post_fields ) ) {
776
			$join_posts_table = true;
777
			foreach ( $post_fields as $field_name => $field_value ) {
778
				// $field_value may be an array.
779
				$esses = array_fill( 0, count( (array) $field_value ), '%s' );
780
				$this->sql_clauses['where'][ $field_name ] = $wpdb->prepare( " {$wpdb->posts}.{$field_name} IN (" . implode( ',', $esses ) . ')', $field_value );
781
			}
782
		}
783
784
		// 'post_status' and 'post_type' are handled separately, due to the specialized behavior of 'any'.
785
		foreach ( array( 'post_status', 'post_type' ) as $field_name ) {
786
			$q_values = array();
787
			if ( ! empty( $this->query_vars[ $field_name ] ) ) {
788
				$q_values = $this->query_vars[ $field_name ];
789
				if ( ! is_array( $q_values ) ) {
790
					$q_values = explode( ',', $q_values );
791
				}
792
793
				// 'any' will cause the query var to be ignored.
794
				if ( in_array( 'any', $q_values, true ) || empty( $q_values ) ) {
795
					continue;
796
				}
797
798
				$join_posts_table = true;
799
800
				$esses = array_fill( 0, count( $q_values ), '%s' );
801
				$this->sql_clauses['where'][ $field_name ] = $wpdb->prepare( " {$wpdb->posts}.{$field_name} IN (" . implode( ',', $esses ) . ")", $q_values );
802
			}
803
		}
804
805
		// Comment author IDs for an IN clause.
806
		if ( ! empty( $this->query_vars['author__in'] ) ) {
807
			$this->sql_clauses['where']['author__in'] = 'user_id IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__in'] ) ) . ' )';
808
		}
809
810
		// Comment author IDs for a NOT IN clause.
811
		if ( ! empty( $this->query_vars['author__not_in'] ) ) {
812
			$this->sql_clauses['where']['author__not_in'] = 'user_id NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__not_in'] ) ) . ' )';
813
		}
814
815
		// Post author IDs for an IN clause.
816 View Code Duplication
		if ( ! empty( $this->query_vars['post_author__in'] ) ) {
817
			$join_posts_table = true;
818
			$this->sql_clauses['where']['post_author__in'] = 'post_author IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__in'] ) ) . ' )';
819
		}
820
821
		// Post author IDs for a NOT IN clause.
822 View Code Duplication
		if ( ! empty( $this->query_vars['post_author__not_in'] ) ) {
823
			$join_posts_table = true;
824
			$this->sql_clauses['where']['post_author__not_in'] = 'post_author NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__not_in'] ) ) . ' )';
825
		}
826
827
		$join = '';
828
829
		if ( $join_posts_table ) {
830
			$join .= "JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
831
		}
832
833
		if ( ! empty( $this->meta_query_clauses ) ) {
834
			$join .= $this->meta_query_clauses['join'];
835
836
			// Strip leading 'AND'.
837
			$this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $this->meta_query_clauses['where'] );
838
839
			if ( ! $this->query_vars['count'] ) {
840
				$groupby = "{$wpdb->comments}.comment_ID";
841
			}
842
		}
843
844
		if ( ! empty( $this->query_vars['date_query'] ) && is_array( $this->query_vars['date_query'] ) ) {
845
			$this->date_query = new WP_Date_Query( $this->query_vars['date_query'], 'comment_date' );
846
			$this->sql_clauses['where']['date_query'] = preg_replace( '/^\s*AND\s*/', '', $this->date_query->get_sql() );
847
		}
848
849
		$where = implode( ' AND ', $this->sql_clauses['where'] );
850
851
		$pieces = array( 'fields', 'join', 'where', 'orderby', 'limits', 'groupby' );
852
		/**
853
		 * Filters the comment query clauses.
854
		 *
855
		 * @since 3.1.0
856
		 *
857
		 * @param array            $pieces A compacted array of comment query clauses.
858
		 * @param WP_Comment_Query &$this  Current instance of WP_Comment_Query, passed by reference.
859
		 */
860
		$clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), &$this ) );
861
862
		$fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
863
		$join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
864
		$where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
865
		$orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
866
		$limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
867
		$groupby = isset( $clauses[ 'groupby' ] ) ? $clauses[ 'groupby' ] : '';
868
869
		$this->filtered_where_clause = $where;
870
871
		if ( $where ) {
872
			$where = 'WHERE ' . $where;
873
		}
874
875
		if ( $groupby ) {
876
			$groupby = 'GROUP BY ' . $groupby;
877
		}
878
879
		if ( $orderby ) {
880
			$orderby = "ORDER BY $orderby";
881
		}
882
883
		$found_rows = '';
884
		if ( ! $this->query_vars['no_found_rows'] ) {
885
			$found_rows = 'SQL_CALC_FOUND_ROWS';
886
		}
887
888
		$this->sql_clauses['select']  = "SELECT $found_rows $fields";
889
		$this->sql_clauses['from']    = "FROM $wpdb->comments $join";
890
		$this->sql_clauses['groupby'] = $groupby;
891
		$this->sql_clauses['orderby'] = $orderby;
892
		$this->sql_clauses['limits']  = $limits;
893
894
		$this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
895
896
		if ( $this->query_vars['count'] ) {
897
			return intval( $wpdb->get_var( $this->request ) );
898
		} else {
899
			$comment_ids = $wpdb->get_col( $this->request );
900
			return array_map( 'intval', $comment_ids );
901
		}
902
	}
903
904
	/**
905
	 * Populates found_comments and max_num_pages properties for the current
906
	 * query if the limit clause was used.
907
	 *
908
	 * @since 4.6.0
909
	 * @access private
910
	 *
911
	 * @global wpdb $wpdb WordPress database abstraction object.
912
	 */
913 View Code Duplication
	private function set_found_comments() {
914
		global $wpdb;
915
916
		if ( $this->query_vars['number'] && ! $this->query_vars['no_found_rows'] ) {
917
			/**
918
			 * Filters the query used to retrieve found comment count.
919
			 *
920
			 * @since 4.4.0
921
			 *
922
			 * @param string           $found_comments_query SQL query. Default 'SELECT FOUND_ROWS()'.
923
			 * @param WP_Comment_Query $comment_query        The `WP_Comment_Query` instance.
924
			 */
925
			$found_comments_query = apply_filters( 'found_comments_query', 'SELECT FOUND_ROWS()', $this );
926
927
			$this->found_comments = (int) $wpdb->get_var( $found_comments_query );
928
		}
929
	}
930
931
	/**
932
	 * Fetch descendants for located comments.
933
	 *
934
	 * Instead of calling `get_children()` separately on each child comment, we do a single set of queries to fetch
935
	 * the descendant trees for all matched top-level comments.
936
	 *
937
	 * @since 4.4.0
938
	 *
939
	 * @param array $comments Array of top-level comments whose descendants should be filled in.
940
	 * @return array
941
	 */
942
	protected function fill_descendants( $comments ) {
943
		global $wpdb;
944
945
		$levels = array(
946
			0 => wp_list_pluck( $comments, 'comment_ID' ),
947
		);
948
949
		$key = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ) ) );
950
		$last_changed = wp_cache_get_last_changed( 'comment' );
951
952
		// Fetch an entire level of the descendant tree at a time.
953
		$level = 0;
954
		$exclude_keys = array( 'parent', 'parent__in', 'parent__not_in' );
955
		do {
956
			// Parent-child relationships may be cached. Only query for those that are not.
957
			$child_ids = $uncached_parent_ids = array();
958
			$_parent_ids = $levels[ $level ];
959
			foreach ( $_parent_ids as $parent_id ) {
960
				$cache_key = "get_comment_child_ids:$parent_id:$key:$last_changed";
961
				$parent_child_ids = wp_cache_get( $cache_key, 'comment' );
962
				if ( false !== $parent_child_ids ) {
963
					$child_ids = array_merge( $child_ids, $parent_child_ids );
964
				} else {
965
					$uncached_parent_ids[] = $parent_id;
966
				}
967
			}
968
969
			if ( $uncached_parent_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uncached_parent_ids 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...
970
				// Fetch this level of comments.
971
				$parent_query_args = $this->query_vars;
972
				foreach ( $exclude_keys as $exclude_key ) {
973
					$parent_query_args[ $exclude_key ] = '';
974
				}
975
				$parent_query_args['parent__in']    = $uncached_parent_ids;
976
				$parent_query_args['no_found_rows'] = true;
977
				$parent_query_args['hierarchical']  = false;
978
				$parent_query_args['offset']        = 0;
979
				$parent_query_args['number']        = 0;
980
981
				$level_comments = get_comments( $parent_query_args );
982
983
				// Cache parent-child relationships.
984
				$parent_map = array_fill_keys( $uncached_parent_ids, array() );
985
				foreach ( $level_comments as $level_comment ) {
0 ignored issues
show
The expression $level_comments of type array|integer is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
986
					$parent_map[ $level_comment->comment_parent ][] = $level_comment->comment_ID;
987
					$child_ids[] = $level_comment->comment_ID;
988
				}
989
990
				foreach ( $parent_map as $parent_id => $children ) {
991
					$cache_key = "get_comment_child_ids:$parent_id:$key:$last_changed";
992
					wp_cache_set( $cache_key, $children, 'comment' );
993
				}
994
			}
995
996
			$level++;
997
			$levels[ $level ] = $child_ids;
998
		} while ( $child_ids );
0 ignored issues
show
Bug Best Practice introduced by
The expression $child_ids 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...
999
1000
		// Prime comment caches for non-top-level comments.
1001
		$descendant_ids = array();
1002
		for ( $i = 1, $c = count( $levels ); $i < $c; $i++ ) {
1003
			$descendant_ids = array_merge( $descendant_ids, $levels[ $i ] );
1004
		}
1005
1006
		_prime_comment_caches( $descendant_ids, $this->query_vars['update_comment_meta_cache'] );
1007
1008
		// Assemble a flat array of all comments + descendants.
1009
		$all_comments = $comments;
1010
		foreach ( $descendant_ids as $descendant_id ) {
1011
			$all_comments[] = get_comment( $descendant_id );
1012
		}
1013
1014
		// If a threaded representation was requested, build the tree.
1015
		if ( 'threaded' === $this->query_vars['hierarchical'] ) {
1016
			$threaded_comments = $ref = array();
1017
			foreach ( $all_comments as $k => $c ) {
1018
				$_c = get_comment( $c->comment_ID );
1019
1020
				// If the comment isn't in the reference array, it goes in the top level of the thread.
1021
				if ( ! isset( $ref[ $c->comment_parent ] ) ) {
1022
					$threaded_comments[ $_c->comment_ID ] = $_c;
1023
					$ref[ $_c->comment_ID ] = $threaded_comments[ $_c->comment_ID ];
1024
1025
				// Otherwise, set it as a child of its parent.
1026
				} else {
1027
1028
					$ref[ $_c->comment_parent ]->add_child( $_c );
1029
					$ref[ $_c->comment_ID ] = $ref[ $_c->comment_parent ]->get_child( $_c->comment_ID );
1030
				}
1031
			}
1032
1033
			// Set the 'populated_children' flag, to ensure additional database queries aren't run.
1034
			foreach ( $ref as $_ref ) {
1035
				$_ref->populated_children( true );
1036
			}
1037
1038
			$comments = $threaded_comments;
1039
		} else {
1040
			$comments = $all_comments;
1041
		}
1042
1043
		return $comments;
1044
	}
1045
1046
	/**
1047
	 * Used internally to generate an SQL string for searching across multiple columns
1048
	 *
1049
	 * @since 3.1.0
1050
	 * @access protected
1051
	 *
1052
	 * @global wpdb $wpdb WordPress database abstraction object.
1053
	 *
1054
	 * @param string $string
1055
	 * @param array $cols
1056
	 * @return string
1057
	 */
1058 View Code Duplication
	protected function get_search_sql( $string, $cols ) {
1059
		global $wpdb;
1060
1061
		$like = '%' . $wpdb->esc_like( $string ) . '%';
1062
1063
		$searches = array();
1064
		foreach ( $cols as $col ) {
1065
			$searches[] = $wpdb->prepare( "$col LIKE %s", $like );
1066
		}
1067
1068
		return ' AND (' . implode(' OR ', $searches) . ')';
1069
	}
1070
1071
	/**
1072
	 * Parse and sanitize 'orderby' keys passed to the comment query.
1073
	 *
1074
	 * @since 4.2.0
1075
	 * @access protected
1076
	 *
1077
	 * @global wpdb $wpdb WordPress database abstraction object.
1078
	 *
1079
	 * @param string $orderby Alias for the field to order by.
1080
	 * @return string|false Value to used in the ORDER clause. False otherwise.
1081
	 */
1082
	protected function parse_orderby( $orderby ) {
1083
		global $wpdb;
1084
1085
		$allowed_keys = array(
1086
			'comment_agent',
1087
			'comment_approved',
1088
			'comment_author',
1089
			'comment_author_email',
1090
			'comment_author_IP',
1091
			'comment_author_url',
1092
			'comment_content',
1093
			'comment_date',
1094
			'comment_date_gmt',
1095
			'comment_ID',
1096
			'comment_karma',
1097
			'comment_parent',
1098
			'comment_post_ID',
1099
			'comment_type',
1100
			'user_id',
1101
		);
1102
1103
		if ( ! empty( $this->query_vars['meta_key'] ) ) {
1104
			$allowed_keys[] = $this->query_vars['meta_key'];
1105
			$allowed_keys[] = 'meta_value';
1106
			$allowed_keys[] = 'meta_value_num';
1107
		}
1108
1109
		$meta_query_clauses = $this->meta_query->get_clauses();
1110
		if ( $meta_query_clauses ) {
1111
			$allowed_keys = array_merge( $allowed_keys, array_keys( $meta_query_clauses ) );
1112
		}
1113
1114
		$parsed = false;
1115
		if ( $orderby == $this->query_vars['meta_key'] || $orderby == 'meta_value' ) {
1116
			$parsed = "$wpdb->commentmeta.meta_value";
1117
		} elseif ( $orderby == 'meta_value_num' ) {
1118
			$parsed = "$wpdb->commentmeta.meta_value+0";
1119
		} elseif ( $orderby == 'comment__in' ) {
1120
			$comment__in = implode( ',', array_map( 'absint', $this->query_vars['comment__in'] ) );
1121
			$parsed = "FIELD( {$wpdb->comments}.comment_ID, $comment__in )";
1122
		} elseif ( in_array( $orderby, $allowed_keys ) ) {
1123
1124
			if ( isset( $meta_query_clauses[ $orderby ] ) ) {
1125
				$meta_clause = $meta_query_clauses[ $orderby ];
1126
				$parsed = sprintf( "CAST(%s.meta_value AS %s)", esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) );
1127
			} else {
1128
				$parsed = "$wpdb->comments.$orderby";
1129
			}
1130
		}
1131
1132
		return $parsed;
1133
	}
1134
1135
	/**
1136
	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
1137
	 *
1138
	 * @since 4.2.0
1139
	 * @access protected
1140
	 *
1141
	 * @param string $order The 'order' query variable.
1142
	 * @return string The sanitized 'order' query variable.
1143
	 */
1144 View Code Duplication
	protected function parse_order( $order ) {
1145
		if ( ! is_string( $order ) || empty( $order ) ) {
1146
			return 'DESC';
1147
		}
1148
1149
		if ( 'ASC' === strtoupper( $order ) ) {
1150
			return 'ASC';
1151
		} else {
1152
			return 'DESC';
1153
		}
1154
	}
1155
}
1156