From fc854a837f45397f2c40ee671657607635b689c6 Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Fri, 25 Sep 2015 15:13:24 +0000 Subject: [PATCH] Introduce hierarchical query support to `WP_Comment_Query`. Comments can be threaded. Now your query can be threaded too! Bonus: it's not totally insane. * The new `$hierarchical` parameter for `WP_Comment_Query` accepts three values: * `false` - Default value, and equivalent to current behavior. No descendants are fetched for matched comments. * `'flat'` - `WP_Comment_Query` will fetch the descendant tree for each comment matched by the query paramaters, and append them to the flat array of comments returned. Use this when you have a separate routine for constructing the tree - for example, when passing a list of comments to a `Walker` object. * `'threaded'` - `WP_Comment_Query` will fetch the descendant tree for each comment, and return it in a tree structure located in the `children` property of the `WP_Comment` objects. * `WP_Comment` now has a few utility methods for fetching the descendant tree (`get_children()`), fetching a single direct descendant comment (`get_child()`), and adding anothing `WP_Comment` object as a direct descendant (`add_child()`). Note that `add_child()` only modifies the comment object - it does not touch the database. Props boonebgorges, wonderboymusic. See #8071. Built from https://develop.svn.wordpress.org/trunk@34546 git-svn-id: http://core.svn.wordpress.org/trunk@34510 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-comment-query.php | 97 +++++++++++++++++++++++++- wp-includes/class-wp-comment.php | 59 ++++++++++++++++ wp-includes/version.php | 2 +- 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/wp-includes/class-wp-comment-query.php b/wp-includes/class-wp-comment-query.php index e9a5ec60c8..67098904d1 100644 --- a/wp-includes/class-wp-comment-query.php +++ b/wp-includes/class-wp-comment-query.php @@ -137,7 +137,8 @@ class WP_Comment_Query { * * @since 4.2.0 * @since 4.4.0 `$parent__in` and `$parent__not_in` were added. - * @since 4.4.0 Order by `comment__in` was added. `$update_comment_meta_cache` and `$no_found_rows` were added. + * @since 4.4.0 Order by `comment__in` was added. `$update_comment_meta_cache`, `$no_found_rows`, + * and `$hierarchical` were added. * @access public * * @param string|array $query { @@ -206,6 +207,13 @@ class WP_Comment_Query { * @type array $type__in Include comments from a given array of comment types. Default empty. * @type array $type__not_in Exclude comments from a given array of comment types. Default empty. * @type int $user_id Include comments for a specific user ID. Default empty. + * @type bool|string $hierarchical Whether to include comment descendants in the results. + * 'threaded' returns a tree, with each comment's children stored + * in a `children` property on the `WP_Comment` object. 'flat' + * returns a flat array of found comments plus their children. + * Pass `false` to leave out descendants. The parameter is ignored + * (forced to `false`) when `$fields` is 'ids' or 'counts'. + * Accepts 'threaded', 'flat', or false. Default: false. * @type bool $update_comment_meta_cache Whether to prime the metadata cache for found comments. * Default true. * } @@ -249,6 +257,7 @@ class WP_Comment_Query { 'meta_value' => '', 'meta_query' => '', 'date_query' => null, // See WP_Date_Query + 'hierarchical' => false, 'update_comment_meta_cache' => true, ); @@ -396,6 +405,10 @@ class WP_Comment_Query { // Convert to WP_Comment instances $comments = array_map( 'get_comment', $_comments ); + if ( $this->query_vars['hierarchical'] ) { + $comments = $this->fill_descendants( $comments ); + } + $this->comments = $comments; return $this->comments; } @@ -665,6 +678,10 @@ class WP_Comment_Query { } } + if ( $this->query_vars['hierarchical'] && ! $this->query_vars['parent'] ) { + $this->query_vars['parent'] = 0; + } + if ( '' !== $this->query_vars['parent'] ) { $this->sql_clauses['where']['parent'] = $wpdb->prepare( 'comment_parent = %d', $this->query_vars['parent'] ); } @@ -797,6 +814,84 @@ class WP_Comment_Query { } } + /** + * Fetch descendants for located comments. + * + * Instead of calling `get_children()` separately on each child comment, we do a single set of queries to fetch + * the descendant trees for all matched top-level comments. + * + * @since 4.4.0 + * + * @param array $comments Array of top-level comments whose descendants should be filled in. + * @return array + */ + protected function fill_descendants( $comments ) { + global $wpdb; + + $levels = array( + 0 => wp_list_pluck( $comments, 'comment_ID' ), + ); + + $where_clauses = $this->sql_clauses['where']; + unset( + $where_clauses['parent'], + $where_clauses['parent__in'], + $where_clauses['parent__not_in'] + ); + + // Fetch an entire level of the descendant tree at a time. + $level = 0; + do { + $parent_ids = $levels[ $level ]; + $where = 'WHERE ' . implode( ' AND ', $where_clauses ) . ' AND comment_parent IN (' . implode( ',', array_map( 'intval', $parent_ids ) ) . ')'; + $comment_ids = $wpdb->get_col( "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']}" ); + + $level++; + $levels[ $level ] = $comment_ids; + } while ( $comment_ids ); + + // Prime comment caches for non-top-level comments. + $descendant_ids = array(); + for ( $i = 1; $i < count( $levels ); $i++ ) { + $descendant_ids = array_merge( $descendant_ids, $levels[ $i ] ); + } + + _prime_comment_caches( $descendant_ids, $this->query_vars['update_comment_meta_cache'] ); + + // Assemble a flat array of all comments + descendants. + $all_comments = $comments; + foreach ( $descendant_ids as $descendant_id ) { + $all_comments[] = get_comment( $descendant_id ); + } + + // If a threaded representation was requested, build the tree. + if ( 'threaded' === $this->query_vars['hierarchical'] ) { + $threaded_comments = $ref = array(); + foreach ( $all_comments as $k => $c ) { + $_c = get_comment( $c->comment_ID ); + + // If the comment isn't in the reference array, it goes in the top level of the thread. + if ( ! isset( $ref[ $c->comment_parent ] ) ) { + $threaded_comments[ $_c->comment_ID ] = $_c; + $ref[ $_c->comment_ID ] = $threaded_comments[ $_c->comment_ID ]; + + // Otherwise, set it as a child of its parent. + } else { + + $ref[ $_c->comment_parent ]->add_child( $_c ); +// $ref[ $c->comment_parent ]->children[ $c->comment_ID ] = $c; + $ref[ $_c->comment_ID ] = $ref[ $_c->comment_parent ]->get_child( $_c->comment_ID ); + } + } + + $comments = $threaded_comments; + } else { + $comments = $all_comments; + } + + return $comments; + } + /** * Used internally to generate an SQL string for searching across multiple columns * diff --git a/wp-includes/class-wp-comment.php b/wp-includes/class-wp-comment.php index 581fca2db2..955925b1b8 100644 --- a/wp-includes/class-wp-comment.php +++ b/wp-includes/class-wp-comment.php @@ -149,6 +149,15 @@ final class WP_Comment { */ public $user_id = 0; + /** + * Comment children. + * + * @since 4.4.0 + * @access protected + * @var array + */ + protected $children; + /** * Retrieves a WP_Comment instance. * @@ -211,4 +220,54 @@ final class WP_Comment { public function to_array() { return get_object_vars( $this ); } + + /** + * Get the children of a comment. + * + * @since 4.4.0 + * @access public + * + * @return array Array of `WP_Comment` objects. + */ + public function get_children() { + if ( is_null( $this->children ) ) { + $this->children = get_comments( array( + 'parent' => $this->comment_ID, + 'hierarchical' => 'threaded', + ) ); + } + + return $this->children; + } + + /** + * Add a child to the comment. + * + * Used by `WP_Comment_Query` when bulk-filling descendants. + * + * @since 4.4.0 + * @access public + * + * @param WP_Comment $child Child comment. + */ + public function add_child( WP_Comment $child ) { + $this->comments[ $child->comment_ID ] = $child; + } + + /** + * Get a child comment by ID. + * + * @since 4.4.0 + * @access public + * + * @param int $child_id ID of the child. + * @return WP_Comment|bool Returns the comment object if found, otherwise false. + */ + public function get_child( $child_id ) { + if ( isset( $this->comments[ $child_id ] ) ) { + return $this->comments[ $child_id ]; + } + + return false; + } } diff --git a/wp-includes/version.php b/wp-includes/version.php index 4f5520f3dd..b5df01ef81 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.4-alpha-34545'; +$wp_version = '4.4-alpha-34546'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.