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