angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-comment-query.php (45483B)


      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 	 * @var string
     24 	 */
     25 	public $request;
     26 
     27 	/**
     28 	 * Metadata query container
     29 	 *
     30 	 * @since 3.5.0
     31 	 * @var WP_Meta_Query A meta query instance.
     32 	 */
     33 	public $meta_query = false;
     34 
     35 	/**
     36 	 * Metadata query clauses.
     37 	 *
     38 	 * @since 4.4.0
     39 	 * @var array
     40 	 */
     41 	protected $meta_query_clauses;
     42 
     43 	/**
     44 	 * SQL query clauses.
     45 	 *
     46 	 * @since 4.4.0
     47 	 * @var array
     48 	 */
     49 	protected $sql_clauses = array(
     50 		'select'  => '',
     51 		'from'    => '',
     52 		'where'   => array(),
     53 		'groupby' => '',
     54 		'orderby' => '',
     55 		'limits'  => '',
     56 	);
     57 
     58 	/**
     59 	 * SQL WHERE clause.
     60 	 *
     61 	 * Stored after the {@see 'comments_clauses'} filter is run on the compiled WHERE sub-clauses.
     62 	 *
     63 	 * @since 4.4.2
     64 	 * @var string
     65 	 */
     66 	protected $filtered_where_clause;
     67 
     68 	/**
     69 	 * Date query container
     70 	 *
     71 	 * @since 3.7.0
     72 	 * @var WP_Date_Query A date query instance.
     73 	 */
     74 	public $date_query = false;
     75 
     76 	/**
     77 	 * Query vars set by the user.
     78 	 *
     79 	 * @since 3.1.0
     80 	 * @var array
     81 	 */
     82 	public $query_vars;
     83 
     84 	/**
     85 	 * Default values for query vars.
     86 	 *
     87 	 * @since 4.2.0
     88 	 * @var array
     89 	 */
     90 	public $query_var_defaults;
     91 
     92 	/**
     93 	 * List of comments located by the query.
     94 	 *
     95 	 * @since 4.0.0
     96 	 * @var array
     97 	 */
     98 	public $comments;
     99 
    100 	/**
    101 	 * The amount of found comments for the current query.
    102 	 *
    103 	 * @since 4.4.0
    104 	 * @var int
    105 	 */
    106 	public $found_comments = 0;
    107 
    108 	/**
    109 	 * The number of pages.
    110 	 *
    111 	 * @since 4.4.0
    112 	 * @var int
    113 	 */
    114 	public $max_num_pages = 0;
    115 
    116 	/**
    117 	 * Make private/protected methods readable for backward compatibility.
    118 	 *
    119 	 * @since 4.0.0
    120 	 *
    121 	 * @param string $name      Method to call.
    122 	 * @param array  $arguments Arguments to pass when calling.
    123 	 * @return mixed|false Return value of the callback, false otherwise.
    124 	 */
    125 	public function __call( $name, $arguments ) {
    126 		if ( 'get_search_sql' === $name ) {
    127 			return $this->get_search_sql( ...$arguments );
    128 		}
    129 		return false;
    130 	}
    131 
    132 	/**
    133 	 * Constructor.
    134 	 *
    135 	 * Sets up the comment query, based on the query vars passed.
    136 	 *
    137 	 * @since 4.2.0
    138 	 * @since 4.4.0 `$parent__in` and `$parent__not_in` were added.
    139 	 * @since 4.4.0 Order by `comment__in` was added. `$update_comment_meta_cache`, `$no_found_rows`,
    140 	 *              `$hierarchical`, and `$update_comment_post_cache` were added.
    141 	 * @since 4.5.0 Introduced the `$author_url` argument.
    142 	 * @since 4.6.0 Introduced the `$cache_domain` argument.
    143 	 * @since 4.9.0 Introduced the `$paged` argument.
    144 	 *
    145 	 * @param string|array $query {
    146 	 *     Optional. Array or query string of comment query parameters. Default empty.
    147 	 *
    148 	 *     @type string       $author_email              Comment author email address. Default empty.
    149 	 *     @type string       $author_url                Comment author URL. Default empty.
    150 	 *     @type int[]        $author__in                Array of author IDs to include comments for. Default empty.
    151 	 *     @type int[]        $author__not_in            Array of author IDs to exclude comments for. Default empty.
    152 	 *     @type int[]        $comment__in               Array of comment IDs to include. Default empty.
    153 	 *     @type int[]        $comment__not_in           Array of comment IDs to exclude. Default empty.
    154 	 *     @type bool         $count                     Whether to return a comment count (true) or array of
    155 	 *                                                   comment objects (false). Default false.
    156 	 *     @type array        $date_query                Date query clauses to limit comments by. See WP_Date_Query.
    157 	 *                                                   Default null.
    158 	 *     @type string       $fields                    Comment fields to return. Accepts 'ids' for comment IDs
    159 	 *                                                   only or empty for all fields. Default empty.
    160 	 *     @type int          $ID                        Currently unused.
    161 	 *     @type array        $include_unapproved        Array of IDs or email addresses of users whose unapproved
    162 	 *                                                   comments will be returned by the query regardless of
    163 	 *                                                   `$status`. Default empty.
    164 	 *     @type int          $karma                     Karma score to retrieve matching comments for.
    165 	 *                                                   Default empty.
    166 	 *     @type string       $meta_key                  Include comments with a matching comment meta key.
    167 	 *                                                   Default empty.
    168 	 *     @type string       $meta_value                Include comments with a matching comment meta value.
    169 	 *                                                   Requires `$meta_key` to be set. Default empty.
    170 	 *     @type array        $meta_query                Meta query clauses to limit retrieved comments by.
    171 	 *                                                   See WP_Meta_Query. Default empty.
    172 	 *     @type int          $number                    Maximum number of comments to retrieve.
    173 	 *                                                   Default empty (no limit).
    174 	 *     @type int          $paged                     When used with $number, defines the page of results to return.
    175 	 *                                                   When used with $offset, $offset takes precedence. Default 1.
    176 	 *     @type int          $offset                    Number of comments to offset the query. Used to build
    177 	 *                                                   LIMIT clause. Default 0.
    178 	 *     @type bool         $no_found_rows             Whether to disable the `SQL_CALC_FOUND_ROWS` query.
    179 	 *                                                   Default: true.
    180 	 *     @type string|array $orderby                   Comment status or array of statuses. To use 'meta_value'
    181 	 *                                                   or 'meta_value_num', `$meta_key` must also be defined.
    182 	 *                                                   To sort by a specific `$meta_query` clause, use that
    183 	 *                                                   clause's array key. Accepts 'comment_agent',
    184 	 *                                                   'comment_approved', 'comment_author',
    185 	 *                                                   'comment_author_email', 'comment_author_IP',
    186 	 *                                                   'comment_author_url', 'comment_content', 'comment_date',
    187 	 *                                                   'comment_date_gmt', 'comment_ID', 'comment_karma',
    188 	 *                                                   'comment_parent', 'comment_post_ID', 'comment_type',
    189 	 *                                                   'user_id', 'comment__in', 'meta_value', 'meta_value_num',
    190 	 *                                                   the value of $meta_key, and the array keys of
    191 	 *                                                   `$meta_query`. Also accepts false, an empty array, or
    192 	 *                                                   'none' to disable `ORDER BY` clause.
    193 	 *                                                   Default: 'comment_date_gmt'.
    194 	 *     @type string       $order                     How to order retrieved comments. Accepts 'ASC', 'DESC'.
    195 	 *                                                   Default: 'DESC'.
    196 	 *     @type int          $parent                    Parent ID of comment to retrieve children of.
    197 	 *                                                   Default empty.
    198 	 *     @type int[]        $parent__in                Array of parent IDs of comments to retrieve children for.
    199 	 *                                                   Default empty.
    200 	 *     @type int[]        $parent__not_in            Array of parent IDs of comments *not* to retrieve
    201 	 *                                                   children for. Default empty.
    202 	 *     @type int[]        $post_author__in           Array of author IDs to retrieve comments for.
    203 	 *                                                   Default empty.
    204 	 *     @type int[]        $post_author__not_in       Array of author IDs *not* to retrieve comments for.
    205 	 *                                                   Default empty.
    206 	 *     @type int          $post_ID                   Currently unused.
    207 	 *     @type int          $post_id                   Limit results to those affiliated with a given post ID.
    208 	 *                                                   Default 0.
    209 	 *     @type int[]        $post__in                  Array of post IDs to include affiliated comments for.
    210 	 *                                                   Default empty.
    211 	 *     @type int[]        $post__not_in              Array of post IDs to exclude affiliated comments for.
    212 	 *                                                   Default empty.
    213 	 *     @type int          $post_author               Post author ID to limit results by. Default empty.
    214 	 *     @type string|array $post_status               Post status or array of post statuses to retrieve
    215 	 *                                                   affiliated comments for. Pass 'any' to match any value.
    216 	 *                                                   Default empty.
    217 	 *     @type string       $post_type                 Post type or array of post types to retrieve affiliated
    218 	 *                                                   comments for. Pass 'any' to match any value. Default empty.
    219 	 *     @type string       $post_name                 Post name to retrieve affiliated comments for.
    220 	 *                                                   Default empty.
    221 	 *     @type int          $post_parent               Post parent ID to retrieve affiliated comments for.
    222 	 *                                                   Default empty.
    223 	 *     @type string       $search                    Search term(s) to retrieve matching comments for.
    224 	 *                                                   Default empty.
    225 	 *     @type string|array $status                    Comment statuses to limit results by. Accepts an array
    226 	 *                                                   or space/comma-separated list of 'hold' (`comment_status=0`),
    227 	 *                                                   'approve' (`comment_status=1`), 'all', or a custom
    228 	 *                                                   comment status. Default 'all'.
    229 	 *     @type string|array $type                      Include comments of a given type, or array of types.
    230 	 *                                                   Accepts 'comment', 'pings' (includes 'pingback' and
    231 	 *                                                   'trackback'), or any custom type string. Default empty.
    232 	 *     @type string[]     $type__in                  Include comments from a given array of comment types.
    233 	 *                                                   Default empty.
    234 	 *     @type string[]     $type__not_in              Exclude comments from a given array of comment types.
    235 	 *                                                   Default empty.
    236 	 *     @type int          $user_id                   Include comments for a specific user ID. Default empty.
    237 	 *     @type bool|string  $hierarchical              Whether to include comment descendants in the results.
    238 	 *                                                   - 'threaded' returns a tree, with each comment's children
    239 	 *                                                   stored in a `children` property on the `WP_Comment` object.
    240 	 *                                                   - 'flat' returns a flat array of found comments plus
    241 	 *                                                   their children.
    242 	 *                                                   - Boolean `false` leaves out descendants.
    243 	 *                                                   The parameter is ignored (forced to `false`) when
    244 	 *                                                   `$fields` is 'ids' or 'counts'. Accepts 'threaded',
    245 	 *                                                   'flat', or false. Default: false.
    246 	 *     @type string       $cache_domain              Unique cache key to be produced when this query is stored in
    247 	 *                                                   an object cache. Default is 'core'.
    248 	 *     @type bool         $update_comment_meta_cache Whether to prime the metadata cache for found comments.
    249 	 *                                                   Default true.
    250 	 *     @type bool         $update_comment_post_cache Whether to prime the cache for comment posts.
    251 	 *                                                   Default false.
    252 	 * }
    253 	 */
    254 	public function __construct( $query = '' ) {
    255 		$this->query_var_defaults = array(
    256 			'author_email'              => '',
    257 			'author_url'                => '',
    258 			'author__in'                => '',
    259 			'author__not_in'            => '',
    260 			'include_unapproved'        => '',
    261 			'fields'                    => '',
    262 			'ID'                        => '',
    263 			'comment__in'               => '',
    264 			'comment__not_in'           => '',
    265 			'karma'                     => '',
    266 			'number'                    => '',
    267 			'offset'                    => '',
    268 			'no_found_rows'             => true,
    269 			'orderby'                   => '',
    270 			'order'                     => 'DESC',
    271 			'paged'                     => 1,
    272 			'parent'                    => '',
    273 			'parent__in'                => '',
    274 			'parent__not_in'            => '',
    275 			'post_author__in'           => '',
    276 			'post_author__not_in'       => '',
    277 			'post_ID'                   => '',
    278 			'post_id'                   => 0,
    279 			'post__in'                  => '',
    280 			'post__not_in'              => '',
    281 			'post_author'               => '',
    282 			'post_name'                 => '',
    283 			'post_parent'               => '',
    284 			'post_status'               => '',
    285 			'post_type'                 => '',
    286 			'status'                    => 'all',
    287 			'type'                      => '',
    288 			'type__in'                  => '',
    289 			'type__not_in'              => '',
    290 			'user_id'                   => '',
    291 			'search'                    => '',
    292 			'count'                     => false,
    293 			'meta_key'                  => '',
    294 			'meta_value'                => '',
    295 			'meta_query'                => '',
    296 			'date_query'                => null, // See WP_Date_Query.
    297 			'hierarchical'              => false,
    298 			'cache_domain'              => 'core',
    299 			'update_comment_meta_cache' => true,
    300 			'update_comment_post_cache' => false,
    301 		);
    302 
    303 		if ( ! empty( $query ) ) {
    304 			$this->query( $query );
    305 		}
    306 	}
    307 
    308 	/**
    309 	 * Parse arguments passed to the comment query with default query parameters.
    310 	 *
    311 	 * @since 4.2.0 Extracted from WP_Comment_Query::query().
    312 	 *
    313 	 * @param string|array $query WP_Comment_Query arguments. See WP_Comment_Query::__construct()
    314 	 */
    315 	public function parse_query( $query = '' ) {
    316 		if ( empty( $query ) ) {
    317 			$query = $this->query_vars;
    318 		}
    319 
    320 		$this->query_vars = wp_parse_args( $query, $this->query_var_defaults );
    321 
    322 		/**
    323 		 * Fires after the comment query vars have been parsed.
    324 		 *
    325 		 * @since 4.2.0
    326 		 *
    327 		 * @param WP_Comment_Query $this The WP_Comment_Query instance (passed by reference).
    328 		 */
    329 		do_action_ref_array( 'parse_comment_query', array( &$this ) );
    330 	}
    331 
    332 	/**
    333 	 * Sets up the WordPress query for retrieving comments.
    334 	 *
    335 	 * @since 3.1.0
    336 	 * @since 4.1.0 Introduced 'comment__in', 'comment__not_in', 'post_author__in',
    337 	 *              'post_author__not_in', 'author__in', 'author__not_in', 'post__in',
    338 	 *              'post__not_in', 'include_unapproved', 'type__in', and 'type__not_in'
    339 	 *              arguments to $query_vars.
    340 	 * @since 4.2.0 Moved parsing to WP_Comment_Query::parse_query().
    341 	 *
    342 	 * @param string|array $query Array or URL query string of parameters.
    343 	 * @return array|int List of comments, or number of comments when 'count' is passed as a query var.
    344 	 */
    345 	public function query( $query ) {
    346 		$this->query_vars = wp_parse_args( $query );
    347 		return $this->get_comments();
    348 	}
    349 
    350 	/**
    351 	 * Get a list of comments matching the query vars.
    352 	 *
    353 	 * @since 4.2.0
    354 	 *
    355 	 * @global wpdb $wpdb WordPress database abstraction object.
    356 	 *
    357 	 * @return int|array List of comments or number of found comments if `$count` argument is true.
    358 	 */
    359 	public function get_comments() {
    360 		global $wpdb;
    361 
    362 		$this->parse_query();
    363 
    364 		// Parse meta query.
    365 		$this->meta_query = new WP_Meta_Query();
    366 		$this->meta_query->parse_query_vars( $this->query_vars );
    367 
    368 		/**
    369 		 * Fires before comments are retrieved.
    370 		 *
    371 		 * @since 3.1.0
    372 		 *
    373 		 * @param WP_Comment_Query $this Current instance of WP_Comment_Query (passed by reference).
    374 		 */
    375 		do_action_ref_array( 'pre_get_comments', array( &$this ) );
    376 
    377 		// Reparse query vars, in case they were modified in a 'pre_get_comments' callback.
    378 		$this->meta_query->parse_query_vars( $this->query_vars );
    379 		if ( ! empty( $this->meta_query->queries ) ) {
    380 			$this->meta_query_clauses = $this->meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $this );
    381 		}
    382 
    383 		$comment_data = null;
    384 
    385 		/**
    386 		 * Filters the comments data before the query takes place.
    387 		 *
    388 		 * Return a non-null value to bypass WordPress' default comment queries.
    389 		 *
    390 		 * The expected return type from this filter depends on the value passed
    391 		 * in the request query vars:
    392 		 * - When `$this->query_vars['count']` is set, the filter should return
    393 		 *   the comment count as an integer.
    394 		 * - When `'ids' === $this->query_vars['fields']`, the filter should return
    395 		 *   an array of comment IDs.
    396 		 * - Otherwise the filter should return an array of WP_Comment objects.
    397 		 *
    398 		 * Note that if the filter returns an array of comment data, it will be assigned
    399 		 * to the `comments` property of the current WP_Comment_Query instance.
    400 		 *
    401 		 * Filtering functions that require pagination information are encouraged to set
    402 		 * the `found_comments` and `max_num_pages` properties of the WP_Comment_Query object,
    403 		 * passed to the filter by reference. If WP_Comment_Query does not perform a database
    404 		 * query, it will not have enough information to generate these values itself.
    405 		 *
    406 		 * @since 5.3.0
    407 		 * @since 5.6.0 The returned array of comment data is assigned to the `comments` property
    408 		 *              of the current WP_Comment_Query instance.
    409 		 *
    410 		 * @param array|int|null   $comment_data Return an array of comment data to short-circuit WP's comment query,
    411 		 *                                       the comment count as an integer if `$this->query_vars['count']` is set,
    412 		 *                                       or null to allow WP to run its normal queries.
    413 		 * @param WP_Comment_Query $query        The WP_Comment_Query instance, passed by reference.
    414 		 */
    415 		$comment_data = apply_filters_ref_array( 'comments_pre_query', array( $comment_data, &$this ) );
    416 
    417 		if ( null !== $comment_data ) {
    418 			if ( is_array( $comment_data ) && ! $this->query_vars['count'] ) {
    419 				$this->comments = $comment_data;
    420 			}
    421 
    422 			return $comment_data;
    423 		}
    424 
    425 		/*
    426 		 * Only use the args defined in the query_var_defaults to compute the key,
    427 		 * but ignore 'fields', which does not affect query results.
    428 		 */
    429 		$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
    430 		unset( $_args['fields'] );
    431 
    432 		$key          = md5( serialize( $_args ) );
    433 		$last_changed = wp_cache_get_last_changed( 'comment' );
    434 
    435 		$cache_key   = "get_comments:$key:$last_changed";
    436 		$cache_value = wp_cache_get( $cache_key, 'comment' );
    437 		if ( false === $cache_value ) {
    438 			$comment_ids = $this->get_comment_ids();
    439 			if ( $comment_ids ) {
    440 				$this->set_found_comments();
    441 			}
    442 
    443 			$cache_value = array(
    444 				'comment_ids'    => $comment_ids,
    445 				'found_comments' => $this->found_comments,
    446 			);
    447 			wp_cache_add( $cache_key, $cache_value, 'comment' );
    448 		} else {
    449 			$comment_ids          = $cache_value['comment_ids'];
    450 			$this->found_comments = $cache_value['found_comments'];
    451 		}
    452 
    453 		if ( $this->found_comments && $this->query_vars['number'] ) {
    454 			$this->max_num_pages = ceil( $this->found_comments / $this->query_vars['number'] );
    455 		}
    456 
    457 		// If querying for a count only, there's nothing more to do.
    458 		if ( $this->query_vars['count'] ) {
    459 			// $comment_ids is actually a count in this case.
    460 			return (int) $comment_ids;
    461 		}
    462 
    463 		$comment_ids = array_map( 'intval', $comment_ids );
    464 
    465 		if ( 'ids' === $this->query_vars['fields'] ) {
    466 			$this->comments = $comment_ids;
    467 			return $this->comments;
    468 		}
    469 
    470 		_prime_comment_caches( $comment_ids, $this->query_vars['update_comment_meta_cache'] );
    471 
    472 		// Fetch full comment objects from the primed cache.
    473 		$_comments = array();
    474 		foreach ( $comment_ids as $comment_id ) {
    475 			$_comment = get_comment( $comment_id );
    476 			if ( $_comment ) {
    477 				$_comments[] = $_comment;
    478 			}
    479 		}
    480 
    481 		// Prime comment post caches.
    482 		if ( $this->query_vars['update_comment_post_cache'] ) {
    483 			$comment_post_ids = array();
    484 			foreach ( $_comments as $_comment ) {
    485 				$comment_post_ids[] = $_comment->comment_post_ID;
    486 			}
    487 
    488 			_prime_post_caches( $comment_post_ids, false, false );
    489 		}
    490 
    491 		/**
    492 		 * Filters the comment query results.
    493 		 *
    494 		 * @since 3.1.0
    495 		 *
    496 		 * @param WP_Comment[]     $_comments An array of comments.
    497 		 * @param WP_Comment_Query $query     Current instance of WP_Comment_Query (passed by reference).
    498 		 */
    499 		$_comments = apply_filters_ref_array( 'the_comments', array( $_comments, &$this ) );
    500 
    501 		// Convert to WP_Comment instances.
    502 		$comments = array_map( 'get_comment', $_comments );
    503 
    504 		if ( $this->query_vars['hierarchical'] ) {
    505 			$comments = $this->fill_descendants( $comments );
    506 		}
    507 
    508 		$this->comments = $comments;
    509 		return $this->comments;
    510 	}
    511 
    512 	/**
    513 	 * Used internally to get a list of comment IDs matching the query vars.
    514 	 *
    515 	 * @since 4.4.0
    516 	 *
    517 	 * @global wpdb $wpdb WordPress database abstraction object.
    518 	 *
    519 	 * @return int|array A single count of comment IDs if a count query. An array of comment IDs if a full query.
    520 	 */
    521 	protected function get_comment_ids() {
    522 		global $wpdb;
    523 
    524 		// Assemble clauses related to 'comment_approved'.
    525 		$approved_clauses = array();
    526 
    527 		// 'status' accepts an array or a comma-separated string.
    528 		$status_clauses = array();
    529 		$statuses       = wp_parse_list( $this->query_vars['status'] );
    530 
    531 		// Empty 'status' should be interpreted as 'all'.
    532 		if ( empty( $statuses ) ) {
    533 			$statuses = array( 'all' );
    534 		}
    535 
    536 		// 'any' overrides other statuses.
    537 		if ( ! in_array( 'any', $statuses, true ) ) {
    538 			foreach ( $statuses as $status ) {
    539 				switch ( $status ) {
    540 					case 'hold':
    541 						$status_clauses[] = "comment_approved = '0'";
    542 						break;
    543 
    544 					case 'approve':
    545 						$status_clauses[] = "comment_approved = '1'";
    546 						break;
    547 
    548 					case 'all':
    549 					case '':
    550 						$status_clauses[] = "( comment_approved = '0' OR comment_approved = '1' )";
    551 						break;
    552 
    553 					default:
    554 						$status_clauses[] = $wpdb->prepare( 'comment_approved = %s', $status );
    555 						break;
    556 				}
    557 			}
    558 
    559 			if ( ! empty( $status_clauses ) ) {
    560 				$approved_clauses[] = '( ' . implode( ' OR ', $status_clauses ) . ' )';
    561 			}
    562 		}
    563 
    564 		// User IDs or emails whose unapproved comments are included, regardless of $status.
    565 		if ( ! empty( $this->query_vars['include_unapproved'] ) ) {
    566 			$include_unapproved = wp_parse_list( $this->query_vars['include_unapproved'] );
    567 
    568 			$unapproved_ids    = array();
    569 			$unapproved_emails = array();
    570 			foreach ( $include_unapproved as $unapproved_identifier ) {
    571 				// Numeric values are assumed to be user IDs.
    572 				if ( is_numeric( $unapproved_identifier ) ) {
    573 					$approved_clauses[] = $wpdb->prepare( "( user_id = %d AND comment_approved = '0' )", $unapproved_identifier );
    574 				} else {
    575 					// Otherwise we match against email addresses.
    576 					if ( ! empty( $_GET['unapproved'] ) && ! empty( $_GET['moderation-hash'] ) ) {
    577 						// Only include requested comment.
    578 						$approved_clauses[] = $wpdb->prepare( "( comment_author_email = %s AND comment_approved = '0' AND comment_ID = %d )", $unapproved_identifier, (int) $_GET['unapproved'] );
    579 					} else {
    580 						// Include all of the author's unapproved comments.
    581 						$approved_clauses[] = $wpdb->prepare( "( comment_author_email = %s AND comment_approved = '0' )", $unapproved_identifier );
    582 					}
    583 				}
    584 			}
    585 		}
    586 
    587 		// Collapse comment_approved clauses into a single OR-separated clause.
    588 		if ( ! empty( $approved_clauses ) ) {
    589 			if ( 1 === count( $approved_clauses ) ) {
    590 				$this->sql_clauses['where']['approved'] = $approved_clauses[0];
    591 			} else {
    592 				$this->sql_clauses['where']['approved'] = '( ' . implode( ' OR ', $approved_clauses ) . ' )';
    593 			}
    594 		}
    595 
    596 		$order = ( 'ASC' === strtoupper( $this->query_vars['order'] ) ) ? 'ASC' : 'DESC';
    597 
    598 		// Disable ORDER BY with 'none', an empty array, or boolean false.
    599 		if ( in_array( $this->query_vars['orderby'], array( 'none', array(), false ), true ) ) {
    600 			$orderby = '';
    601 		} elseif ( ! empty( $this->query_vars['orderby'] ) ) {
    602 			$ordersby = is_array( $this->query_vars['orderby'] ) ?
    603 				$this->query_vars['orderby'] :
    604 				preg_split( '/[,\s]/', $this->query_vars['orderby'] );
    605 
    606 			$orderby_array            = array();
    607 			$found_orderby_comment_id = false;
    608 			foreach ( $ordersby as $_key => $_value ) {
    609 				if ( ! $_value ) {
    610 					continue;
    611 				}
    612 
    613 				if ( is_int( $_key ) ) {
    614 					$_orderby = $_value;
    615 					$_order   = $order;
    616 				} else {
    617 					$_orderby = $_key;
    618 					$_order   = $_value;
    619 				}
    620 
    621 				if ( ! $found_orderby_comment_id && in_array( $_orderby, array( 'comment_ID', 'comment__in' ), true ) ) {
    622 					$found_orderby_comment_id = true;
    623 				}
    624 
    625 				$parsed = $this->parse_orderby( $_orderby );
    626 
    627 				if ( ! $parsed ) {
    628 					continue;
    629 				}
    630 
    631 				if ( 'comment__in' === $_orderby ) {
    632 					$orderby_array[] = $parsed;
    633 					continue;
    634 				}
    635 
    636 				$orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
    637 			}
    638 
    639 			// If no valid clauses were found, order by comment_date_gmt.
    640 			if ( empty( $orderby_array ) ) {
    641 				$orderby_array[] = "$wpdb->comments.comment_date_gmt $order";
    642 			}
    643 
    644 			// To ensure determinate sorting, always include a comment_ID clause.
    645 			if ( ! $found_orderby_comment_id ) {
    646 				$comment_id_order = '';
    647 
    648 				// Inherit order from comment_date or comment_date_gmt, if available.
    649 				foreach ( $orderby_array as $orderby_clause ) {
    650 					if ( preg_match( '/comment_date(?:_gmt)*\ (ASC|DESC)/', $orderby_clause, $match ) ) {
    651 						$comment_id_order = $match[1];
    652 						break;
    653 					}
    654 				}
    655 
    656 				// If no date-related order is available, use the date from the first available clause.
    657 				if ( ! $comment_id_order ) {
    658 					foreach ( $orderby_array as $orderby_clause ) {
    659 						if ( false !== strpos( 'ASC', $orderby_clause ) ) {
    660 							$comment_id_order = 'ASC';
    661 						} else {
    662 							$comment_id_order = 'DESC';
    663 						}
    664 
    665 						break;
    666 					}
    667 				}
    668 
    669 				// Default to DESC.
    670 				if ( ! $comment_id_order ) {
    671 					$comment_id_order = 'DESC';
    672 				}
    673 
    674 				$orderby_array[] = "$wpdb->comments.comment_ID $comment_id_order";
    675 			}
    676 
    677 			$orderby = implode( ', ', $orderby_array );
    678 		} else {
    679 			$orderby = "$wpdb->comments.comment_date_gmt $order";
    680 		}
    681 
    682 		$number = absint( $this->query_vars['number'] );
    683 		$offset = absint( $this->query_vars['offset'] );
    684 		$paged  = absint( $this->query_vars['paged'] );
    685 		$limits = '';
    686 
    687 		if ( ! empty( $number ) ) {
    688 			if ( $offset ) {
    689 				$limits = 'LIMIT ' . $offset . ',' . $number;
    690 			} else {
    691 				$limits = 'LIMIT ' . ( $number * ( $paged - 1 ) ) . ',' . $number;
    692 			}
    693 		}
    694 
    695 		if ( $this->query_vars['count'] ) {
    696 			$fields = 'COUNT(*)';
    697 		} else {
    698 			$fields = "$wpdb->comments.comment_ID";
    699 		}
    700 
    701 		$post_id = absint( $this->query_vars['post_id'] );
    702 		if ( ! empty( $post_id ) ) {
    703 			$this->sql_clauses['where']['post_id'] = $wpdb->prepare( 'comment_post_ID = %d', $post_id );
    704 		}
    705 
    706 		// Parse comment IDs for an IN clause.
    707 		if ( ! empty( $this->query_vars['comment__in'] ) ) {
    708 			$this->sql_clauses['where']['comment__in'] = "$wpdb->comments.comment_ID IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['comment__in'] ) ) . ' )';
    709 		}
    710 
    711 		// Parse comment IDs for a NOT IN clause.
    712 		if ( ! empty( $this->query_vars['comment__not_in'] ) ) {
    713 			$this->sql_clauses['where']['comment__not_in'] = "$wpdb->comments.comment_ID NOT IN ( " . implode( ',', wp_parse_id_list( $this->query_vars['comment__not_in'] ) ) . ' )';
    714 		}
    715 
    716 		// Parse comment parent IDs for an IN clause.
    717 		if ( ! empty( $this->query_vars['parent__in'] ) ) {
    718 			$this->sql_clauses['where']['parent__in'] = 'comment_parent IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['parent__in'] ) ) . ' )';
    719 		}
    720 
    721 		// Parse comment parent IDs for a NOT IN clause.
    722 		if ( ! empty( $this->query_vars['parent__not_in'] ) ) {
    723 			$this->sql_clauses['where']['parent__not_in'] = 'comment_parent NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['parent__not_in'] ) ) . ' )';
    724 		}
    725 
    726 		// Parse comment post IDs for an IN clause.
    727 		if ( ! empty( $this->query_vars['post__in'] ) ) {
    728 			$this->sql_clauses['where']['post__in'] = 'comment_post_ID IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__in'] ) ) . ' )';
    729 		}
    730 
    731 		// Parse comment post IDs for a NOT IN clause.
    732 		if ( ! empty( $this->query_vars['post__not_in'] ) ) {
    733 			$this->sql_clauses['where']['post__not_in'] = 'comment_post_ID NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post__not_in'] ) ) . ' )';
    734 		}
    735 
    736 		if ( '' !== $this->query_vars['author_email'] ) {
    737 			$this->sql_clauses['where']['author_email'] = $wpdb->prepare( 'comment_author_email = %s', $this->query_vars['author_email'] );
    738 		}
    739 
    740 		if ( '' !== $this->query_vars['author_url'] ) {
    741 			$this->sql_clauses['where']['author_url'] = $wpdb->prepare( 'comment_author_url = %s', $this->query_vars['author_url'] );
    742 		}
    743 
    744 		if ( '' !== $this->query_vars['karma'] ) {
    745 			$this->sql_clauses['where']['karma'] = $wpdb->prepare( 'comment_karma = %d', $this->query_vars['karma'] );
    746 		}
    747 
    748 		// Filtering by comment_type: 'type', 'type__in', 'type__not_in'.
    749 		$raw_types = array(
    750 			'IN'     => array_merge( (array) $this->query_vars['type'], (array) $this->query_vars['type__in'] ),
    751 			'NOT IN' => (array) $this->query_vars['type__not_in'],
    752 		);
    753 
    754 		$comment_types = array();
    755 		foreach ( $raw_types as $operator => $_raw_types ) {
    756 			$_raw_types = array_unique( $_raw_types );
    757 
    758 			foreach ( $_raw_types as $type ) {
    759 				switch ( $type ) {
    760 					// An empty translates to 'all', for backward compatibility.
    761 					case '':
    762 					case 'all':
    763 						break;
    764 
    765 					case 'comment':
    766 					case 'comments':
    767 						$comment_types[ $operator ][] = "''";
    768 						$comment_types[ $operator ][] = "'comment'";
    769 						break;
    770 
    771 					case 'pings':
    772 						$comment_types[ $operator ][] = "'pingback'";
    773 						$comment_types[ $operator ][] = "'trackback'";
    774 						break;
    775 
    776 					default:
    777 						$comment_types[ $operator ][] = $wpdb->prepare( '%s', $type );
    778 						break;
    779 				}
    780 			}
    781 
    782 			if ( ! empty( $comment_types[ $operator ] ) ) {
    783 				$types_sql = implode( ', ', $comment_types[ $operator ] );
    784 				$this->sql_clauses['where'][ 'comment_type__' . strtolower( str_replace( ' ', '_', $operator ) ) ] = "comment_type $operator ($types_sql)";
    785 			}
    786 		}
    787 
    788 		$parent = $this->query_vars['parent'];
    789 		if ( $this->query_vars['hierarchical'] && ! $parent ) {
    790 			$parent = 0;
    791 		}
    792 
    793 		if ( '' !== $parent ) {
    794 			$this->sql_clauses['where']['parent'] = $wpdb->prepare( 'comment_parent = %d', $parent );
    795 		}
    796 
    797 		if ( is_array( $this->query_vars['user_id'] ) ) {
    798 			$this->sql_clauses['where']['user_id'] = 'user_id IN (' . implode( ',', array_map( 'absint', $this->query_vars['user_id'] ) ) . ')';
    799 		} elseif ( '' !== $this->query_vars['user_id'] ) {
    800 			$this->sql_clauses['where']['user_id'] = $wpdb->prepare( 'user_id = %d', $this->query_vars['user_id'] );
    801 		}
    802 
    803 		// Falsey search strings are ignored.
    804 		if ( strlen( $this->query_vars['search'] ) ) {
    805 			$search_sql = $this->get_search_sql(
    806 				$this->query_vars['search'],
    807 				array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_content' )
    808 			);
    809 
    810 			// Strip leading 'AND'.
    811 			$this->sql_clauses['where']['search'] = preg_replace( '/^\s*AND\s*/', '', $search_sql );
    812 		}
    813 
    814 		// If any post-related query vars are passed, join the posts table.
    815 		$join_posts_table = false;
    816 		$plucked          = wp_array_slice_assoc( $this->query_vars, array( 'post_author', 'post_name', 'post_parent' ) );
    817 		$post_fields      = array_filter( $plucked );
    818 
    819 		if ( ! empty( $post_fields ) ) {
    820 			$join_posts_table = true;
    821 			foreach ( $post_fields as $field_name => $field_value ) {
    822 				// $field_value may be an array.
    823 				$esses = array_fill( 0, count( (array) $field_value ), '%s' );
    824 
    825 				// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
    826 				$this->sql_clauses['where'][ $field_name ] = $wpdb->prepare( " {$wpdb->posts}.{$field_name} IN (" . implode( ',', $esses ) . ')', $field_value );
    827 			}
    828 		}
    829 
    830 		// 'post_status' and 'post_type' are handled separately, due to the specialized behavior of 'any'.
    831 		foreach ( array( 'post_status', 'post_type' ) as $field_name ) {
    832 			$q_values = array();
    833 			if ( ! empty( $this->query_vars[ $field_name ] ) ) {
    834 				$q_values = $this->query_vars[ $field_name ];
    835 				if ( ! is_array( $q_values ) ) {
    836 					$q_values = explode( ',', $q_values );
    837 				}
    838 
    839 				// 'any' will cause the query var to be ignored.
    840 				if ( in_array( 'any', $q_values, true ) || empty( $q_values ) ) {
    841 					continue;
    842 				}
    843 
    844 				$join_posts_table = true;
    845 
    846 				$esses = array_fill( 0, count( $q_values ), '%s' );
    847 
    848 				// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
    849 				$this->sql_clauses['where'][ $field_name ] = $wpdb->prepare( " {$wpdb->posts}.{$field_name} IN (" . implode( ',', $esses ) . ')', $q_values );
    850 			}
    851 		}
    852 
    853 		// Comment author IDs for an IN clause.
    854 		if ( ! empty( $this->query_vars['author__in'] ) ) {
    855 			$this->sql_clauses['where']['author__in'] = 'user_id IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__in'] ) ) . ' )';
    856 		}
    857 
    858 		// Comment author IDs for a NOT IN clause.
    859 		if ( ! empty( $this->query_vars['author__not_in'] ) ) {
    860 			$this->sql_clauses['where']['author__not_in'] = 'user_id NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['author__not_in'] ) ) . ' )';
    861 		}
    862 
    863 		// Post author IDs for an IN clause.
    864 		if ( ! empty( $this->query_vars['post_author__in'] ) ) {
    865 			$join_posts_table                              = true;
    866 			$this->sql_clauses['where']['post_author__in'] = 'post_author IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__in'] ) ) . ' )';
    867 		}
    868 
    869 		// Post author IDs for a NOT IN clause.
    870 		if ( ! empty( $this->query_vars['post_author__not_in'] ) ) {
    871 			$join_posts_table                                  = true;
    872 			$this->sql_clauses['where']['post_author__not_in'] = 'post_author NOT IN ( ' . implode( ',', wp_parse_id_list( $this->query_vars['post_author__not_in'] ) ) . ' )';
    873 		}
    874 
    875 		$join    = '';
    876 		$groupby = '';
    877 
    878 		if ( $join_posts_table ) {
    879 			$join .= "JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
    880 		}
    881 
    882 		if ( ! empty( $this->meta_query_clauses ) ) {
    883 			$join .= $this->meta_query_clauses['join'];
    884 
    885 			// Strip leading 'AND'.
    886 			$this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $this->meta_query_clauses['where'] );
    887 
    888 			if ( ! $this->query_vars['count'] ) {
    889 				$groupby = "{$wpdb->comments}.comment_ID";
    890 			}
    891 		}
    892 
    893 		if ( ! empty( $this->query_vars['date_query'] ) && is_array( $this->query_vars['date_query'] ) ) {
    894 			$this->date_query                         = new WP_Date_Query( $this->query_vars['date_query'], 'comment_date' );
    895 			$this->sql_clauses['where']['date_query'] = preg_replace( '/^\s*AND\s*/', '', $this->date_query->get_sql() );
    896 		}
    897 
    898 		$where = implode( ' AND ', $this->sql_clauses['where'] );
    899 
    900 		$pieces = array( 'fields', 'join', 'where', 'orderby', 'limits', 'groupby' );
    901 		/**
    902 		 * Filters the comment query clauses.
    903 		 *
    904 		 * @since 3.1.0
    905 		 *
    906 		 * @param string[]         $pieces An associative array of comment query clauses.
    907 		 * @param WP_Comment_Query $query  Current instance of WP_Comment_Query (passed by reference).
    908 		 */
    909 		$clauses = apply_filters_ref_array( 'comments_clauses', array( compact( $pieces ), &$this ) );
    910 
    911 		$fields  = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
    912 		$join    = isset( $clauses['join'] ) ? $clauses['join'] : '';
    913 		$where   = isset( $clauses['where'] ) ? $clauses['where'] : '';
    914 		$orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
    915 		$limits  = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
    916 		$groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
    917 
    918 		$this->filtered_where_clause = $where;
    919 
    920 		if ( $where ) {
    921 			$where = 'WHERE ' . $where;
    922 		}
    923 
    924 		if ( $groupby ) {
    925 			$groupby = 'GROUP BY ' . $groupby;
    926 		}
    927 
    928 		if ( $orderby ) {
    929 			$orderby = "ORDER BY $orderby";
    930 		}
    931 
    932 		$found_rows = '';
    933 		if ( ! $this->query_vars['no_found_rows'] ) {
    934 			$found_rows = 'SQL_CALC_FOUND_ROWS';
    935 		}
    936 
    937 		$this->sql_clauses['select']  = "SELECT $found_rows $fields";
    938 		$this->sql_clauses['from']    = "FROM $wpdb->comments $join";
    939 		$this->sql_clauses['groupby'] = $groupby;
    940 		$this->sql_clauses['orderby'] = $orderby;
    941 		$this->sql_clauses['limits']  = $limits;
    942 
    943 		$this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
    944 
    945 		if ( $this->query_vars['count'] ) {
    946 			return (int) $wpdb->get_var( $this->request );
    947 		} else {
    948 			$comment_ids = $wpdb->get_col( $this->request );
    949 			return array_map( 'intval', $comment_ids );
    950 		}
    951 	}
    952 
    953 	/**
    954 	 * Populates found_comments and max_num_pages properties for the current
    955 	 * query if the limit clause was used.
    956 	 *
    957 	 * @since 4.6.0
    958 	 *
    959 	 * @global wpdb $wpdb WordPress database abstraction object.
    960 	 */
    961 	private function set_found_comments() {
    962 		global $wpdb;
    963 
    964 		if ( $this->query_vars['number'] && ! $this->query_vars['no_found_rows'] ) {
    965 			/**
    966 			 * Filters the query used to retrieve found comment count.
    967 			 *
    968 			 * @since 4.4.0
    969 			 *
    970 			 * @param string           $found_comments_query SQL query. Default 'SELECT FOUND_ROWS()'.
    971 			 * @param WP_Comment_Query $comment_query        The `WP_Comment_Query` instance.
    972 			 */
    973 			$found_comments_query = apply_filters( 'found_comments_query', 'SELECT FOUND_ROWS()', $this );
    974 
    975 			$this->found_comments = (int) $wpdb->get_var( $found_comments_query );
    976 		}
    977 	}
    978 
    979 	/**
    980 	 * Fetch descendants for located comments.
    981 	 *
    982 	 * Instead of calling `get_children()` separately on each child comment, we do a single set of queries to fetch
    983 	 * the descendant trees for all matched top-level comments.
    984 	 *
    985 	 * @since 4.4.0
    986 	 *
    987 	 * @global wpdb $wpdb WordPress database abstraction object.
    988 	 *
    989 	 * @param WP_Comment[] $comments Array of top-level comments whose descendants should be filled in.
    990 	 * @return array
    991 	 */
    992 	protected function fill_descendants( $comments ) {
    993 		global $wpdb;
    994 
    995 		$levels = array(
    996 			0 => wp_list_pluck( $comments, 'comment_ID' ),
    997 		);
    998 
    999 		$key          = md5( serialize( wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) ) ) );
   1000 		$last_changed = wp_cache_get_last_changed( 'comment' );
   1001 
   1002 		// Fetch an entire level of the descendant tree at a time.
   1003 		$level        = 0;
   1004 		$exclude_keys = array( 'parent', 'parent__in', 'parent__not_in' );
   1005 		do {
   1006 			// Parent-child relationships may be cached. Only query for those that are not.
   1007 			$child_ids           = array();
   1008 			$uncached_parent_ids = array();
   1009 			$_parent_ids         = $levels[ $level ];
   1010 			foreach ( $_parent_ids as $parent_id ) {
   1011 				$cache_key        = "get_comment_child_ids:$parent_id:$key:$last_changed";
   1012 				$parent_child_ids = wp_cache_get( $cache_key, 'comment' );
   1013 				if ( false !== $parent_child_ids ) {
   1014 					$child_ids = array_merge( $child_ids, $parent_child_ids );
   1015 				} else {
   1016 					$uncached_parent_ids[] = $parent_id;
   1017 				}
   1018 			}
   1019 
   1020 			if ( $uncached_parent_ids ) {
   1021 				// Fetch this level of comments.
   1022 				$parent_query_args = $this->query_vars;
   1023 				foreach ( $exclude_keys as $exclude_key ) {
   1024 					$parent_query_args[ $exclude_key ] = '';
   1025 				}
   1026 				$parent_query_args['parent__in']    = $uncached_parent_ids;
   1027 				$parent_query_args['no_found_rows'] = true;
   1028 				$parent_query_args['hierarchical']  = false;
   1029 				$parent_query_args['offset']        = 0;
   1030 				$parent_query_args['number']        = 0;
   1031 
   1032 				$level_comments = get_comments( $parent_query_args );
   1033 
   1034 				// Cache parent-child relationships.
   1035 				$parent_map = array_fill_keys( $uncached_parent_ids, array() );
   1036 				foreach ( $level_comments as $level_comment ) {
   1037 					$parent_map[ $level_comment->comment_parent ][] = $level_comment->comment_ID;
   1038 					$child_ids[]                                    = $level_comment->comment_ID;
   1039 				}
   1040 
   1041 				foreach ( $parent_map as $parent_id => $children ) {
   1042 					$cache_key = "get_comment_child_ids:$parent_id:$key:$last_changed";
   1043 					wp_cache_set( $cache_key, $children, 'comment' );
   1044 				}
   1045 			}
   1046 
   1047 			$level++;
   1048 			$levels[ $level ] = $child_ids;
   1049 		} while ( $child_ids );
   1050 
   1051 		// Prime comment caches for non-top-level comments.
   1052 		$descendant_ids = array();
   1053 		for ( $i = 1, $c = count( $levels ); $i < $c; $i++ ) {
   1054 			$descendant_ids = array_merge( $descendant_ids, $levels[ $i ] );
   1055 		}
   1056 
   1057 		_prime_comment_caches( $descendant_ids, $this->query_vars['update_comment_meta_cache'] );
   1058 
   1059 		// Assemble a flat array of all comments + descendants.
   1060 		$all_comments = $comments;
   1061 		foreach ( $descendant_ids as $descendant_id ) {
   1062 			$all_comments[] = get_comment( $descendant_id );
   1063 		}
   1064 
   1065 		// If a threaded representation was requested, build the tree.
   1066 		if ( 'threaded' === $this->query_vars['hierarchical'] ) {
   1067 			$threaded_comments = array();
   1068 			$ref               = array();
   1069 			foreach ( $all_comments as $k => $c ) {
   1070 				$_c = get_comment( $c->comment_ID );
   1071 
   1072 				// If the comment isn't in the reference array, it goes in the top level of the thread.
   1073 				if ( ! isset( $ref[ $c->comment_parent ] ) ) {
   1074 					$threaded_comments[ $_c->comment_ID ] = $_c;
   1075 					$ref[ $_c->comment_ID ]               = $threaded_comments[ $_c->comment_ID ];
   1076 
   1077 					// Otherwise, set it as a child of its parent.
   1078 				} else {
   1079 
   1080 					$ref[ $_c->comment_parent ]->add_child( $_c );
   1081 					$ref[ $_c->comment_ID ] = $ref[ $_c->comment_parent ]->get_child( $_c->comment_ID );
   1082 				}
   1083 			}
   1084 
   1085 			// Set the 'populated_children' flag, to ensure additional database queries aren't run.
   1086 			foreach ( $ref as $_ref ) {
   1087 				$_ref->populated_children( true );
   1088 			}
   1089 
   1090 			$comments = $threaded_comments;
   1091 		} else {
   1092 			$comments = $all_comments;
   1093 		}
   1094 
   1095 		return $comments;
   1096 	}
   1097 
   1098 	/**
   1099 	 * Used internally to generate an SQL string for searching across multiple columns
   1100 	 *
   1101 	 * @since 3.1.0
   1102 	 *
   1103 	 * @global wpdb $wpdb WordPress database abstraction object.
   1104 	 *
   1105 	 * @param string $string
   1106 	 * @param array  $cols
   1107 	 * @return string
   1108 	 */
   1109 	protected function get_search_sql( $string, $cols ) {
   1110 		global $wpdb;
   1111 
   1112 		$like = '%' . $wpdb->esc_like( $string ) . '%';
   1113 
   1114 		$searches = array();
   1115 		foreach ( $cols as $col ) {
   1116 			$searches[] = $wpdb->prepare( "$col LIKE %s", $like );
   1117 		}
   1118 
   1119 		return ' AND (' . implode( ' OR ', $searches ) . ')';
   1120 	}
   1121 
   1122 	/**
   1123 	 * Parse and sanitize 'orderby' keys passed to the comment query.
   1124 	 *
   1125 	 * @since 4.2.0
   1126 	 *
   1127 	 * @global wpdb $wpdb WordPress database abstraction object.
   1128 	 *
   1129 	 * @param string $orderby Alias for the field to order by.
   1130 	 * @return string|false Value to used in the ORDER clause. False otherwise.
   1131 	 */
   1132 	protected function parse_orderby( $orderby ) {
   1133 		global $wpdb;
   1134 
   1135 		$allowed_keys = array(
   1136 			'comment_agent',
   1137 			'comment_approved',
   1138 			'comment_author',
   1139 			'comment_author_email',
   1140 			'comment_author_IP',
   1141 			'comment_author_url',
   1142 			'comment_content',
   1143 			'comment_date',
   1144 			'comment_date_gmt',
   1145 			'comment_ID',
   1146 			'comment_karma',
   1147 			'comment_parent',
   1148 			'comment_post_ID',
   1149 			'comment_type',
   1150 			'user_id',
   1151 		);
   1152 
   1153 		if ( ! empty( $this->query_vars['meta_key'] ) ) {
   1154 			$allowed_keys[] = $this->query_vars['meta_key'];
   1155 			$allowed_keys[] = 'meta_value';
   1156 			$allowed_keys[] = 'meta_value_num';
   1157 		}
   1158 
   1159 		$meta_query_clauses = $this->meta_query->get_clauses();
   1160 		if ( $meta_query_clauses ) {
   1161 			$allowed_keys = array_merge( $allowed_keys, array_keys( $meta_query_clauses ) );
   1162 		}
   1163 
   1164 		$parsed = false;
   1165 		if ( $this->query_vars['meta_key'] === $orderby || 'meta_value' === $orderby ) {
   1166 			$parsed = "$wpdb->commentmeta.meta_value";
   1167 		} elseif ( 'meta_value_num' === $orderby ) {
   1168 			$parsed = "$wpdb->commentmeta.meta_value+0";
   1169 		} elseif ( 'comment__in' === $orderby ) {
   1170 			$comment__in = implode( ',', array_map( 'absint', $this->query_vars['comment__in'] ) );
   1171 			$parsed      = "FIELD( {$wpdb->comments}.comment_ID, $comment__in )";
   1172 		} elseif ( in_array( $orderby, $allowed_keys, true ) ) {
   1173 
   1174 			if ( isset( $meta_query_clauses[ $orderby ] ) ) {
   1175 				$meta_clause = $meta_query_clauses[ $orderby ];
   1176 				$parsed      = sprintf( 'CAST(%s.meta_value AS %s)', esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) );
   1177 			} else {
   1178 				$parsed = "$wpdb->comments.$orderby";
   1179 			}
   1180 		}
   1181 
   1182 		return $parsed;
   1183 	}
   1184 
   1185 	/**
   1186 	 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
   1187 	 *
   1188 	 * @since 4.2.0
   1189 	 *
   1190 	 * @param string $order The 'order' query variable.
   1191 	 * @return string The sanitized 'order' query variable.
   1192 	 */
   1193 	protected function parse_order( $order ) {
   1194 		if ( ! is_string( $order ) || empty( $order ) ) {
   1195 			return 'DESC';
   1196 		}
   1197 
   1198 		if ( 'ASC' === strtoupper( $order ) ) {
   1199 			return 'ASC';
   1200 		} else {
   1201 			return 'DESC';
   1202 		}
   1203 	}
   1204 }