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. git-svn-id: https://develop.svn.wordpress.org/trunk@34546 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
27558ed678
commit
e1b44f5203
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -288,4 +288,55 @@ class Tests_Comment extends WP_UnitTestCase {
|
||||
|
||||
$this->assertEquals( 'fire', get_comment_meta( $c, 'sauce', true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 8071
|
||||
*/
|
||||
public function test_wp_comment_get_children_should_fill_children() {
|
||||
|
||||
$p = $this->factory->post->create();
|
||||
|
||||
$c1 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $p,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$c2 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $p,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c1,
|
||||
) );
|
||||
|
||||
$c3 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $p,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c2,
|
||||
) );
|
||||
|
||||
$c4 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $p,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c1,
|
||||
) );
|
||||
|
||||
$c5 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $p,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$c6 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $p,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c5,
|
||||
) );
|
||||
|
||||
$comment = get_comment( $c1 );
|
||||
$children = $comment->get_children();
|
||||
|
||||
// Direct descendants of $c1.
|
||||
$this->assertEquals( array( $c2, $c4 ), array_values( wp_list_pluck( $children, 'comment_ID' ) ) );
|
||||
|
||||
// Direct descendants of $c2.
|
||||
$this->assertEquals( array( $c3 ), array_values( wp_list_pluck( $children[ $c2 ]->get_children(), 'comment_ID' ) ) );
|
||||
}
|
||||
}
|
||||
|
@ -1920,4 +1920,126 @@ class Tests_Comment_Query extends WP_UnitTestCase {
|
||||
$this->assertEquals( 3, $q->found_comments );
|
||||
$this->assertEquals( 2, $q->max_num_pages );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 8071
|
||||
*/
|
||||
public function test_hierarchical_should_skip_child_comments_in_offset() {
|
||||
$top_level_0 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$child_of_0 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $top_level_0,
|
||||
) );
|
||||
|
||||
$top_level_comments = $this->factory->comment->create_many( 3, array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$q = new WP_Comment_Query( array(
|
||||
'post_id' => $this->post_id,
|
||||
'hierarchical' => 'flat',
|
||||
'number' => 2,
|
||||
'offset' => 1,
|
||||
'orderby' => 'comment_ID',
|
||||
'order' => 'ASC',
|
||||
'fields' => 'ids',
|
||||
) );
|
||||
|
||||
$this->assertEquals( array( $top_level_comments[0], $top_level_comments[1] ), $q->comments );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 8071
|
||||
*/
|
||||
public function test_hierarchical_should_not_include_child_comments_in_number() {
|
||||
$top_level_0 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$child_of_0 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $top_level_0,
|
||||
) );
|
||||
|
||||
$top_level_comments = $this->factory->comment->create_many( 3, array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$q = new WP_Comment_Query( array(
|
||||
'post_id' => $this->post_id,
|
||||
'hierarchical' => 'flat',
|
||||
'number' => 2,
|
||||
'orderby' => 'comment_ID',
|
||||
'order' => 'ASC',
|
||||
) );
|
||||
|
||||
$this->assertEqualSets( array( $top_level_0, $child_of_0, $top_level_comments[0] ), wp_list_pluck( $q->comments, 'comment_ID' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 8071
|
||||
*/
|
||||
public function test_hierarchical_threaded() {
|
||||
$c1 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$c2 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c1,
|
||||
) );
|
||||
|
||||
$c3 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c2,
|
||||
) );
|
||||
|
||||
$c4 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c1,
|
||||
) );
|
||||
|
||||
$c5 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
) );
|
||||
|
||||
$c6 = $this->factory->comment->create( array(
|
||||
'comment_post_ID' => $this->post_id,
|
||||
'comment_approved' => '1',
|
||||
'comment_parent' => $c5,
|
||||
) );
|
||||
|
||||
$q = new WP_Comment_Query( array(
|
||||
'post_id' => $this->post_id,
|
||||
'hierarchical' => 'threaded',
|
||||
'orderby' => 'comment_ID',
|
||||
'order' => 'ASC',
|
||||
) );
|
||||
|
||||
// Top-level comments.
|
||||
$this->assertEquals( array( $c1, $c5 ), array_values( wp_list_pluck( $q->comments, 'comment_ID' ) ) );
|
||||
|
||||
// Direct descendants of $c1.
|
||||
$this->assertEquals( array( $c2, $c4 ), array_values( wp_list_pluck( $q->comments[ $c1 ]->get_children(), 'comment_ID' ) ) );
|
||||
|
||||
// Direct descendants of $c2.
|
||||
$this->assertEquals( array( $c3 ), array_values( wp_list_pluck( $q->comments[ $c1 ]->get_child( $c2 )->get_children(), 'comment_ID' ) ) );
|
||||
|
||||
// Direct descendants of $c5.
|
||||
$this->assertEquals( array( $c6 ), array_values( wp_list_pluck( $q->comments[ $c5 ]->get_children(), 'comment_ID' ) ) );
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user