Make WordPress Core

Changeset 34546


Ignore:
Timestamp:
09/25/2015 03:12:09 PM (9 years ago)
Author:
boonebgorges
Message:

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.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-comment-query.php

    r34544 r34546  
    138138     * @since 4.2.0
    139139     * @since 4.4.0 `$parent__in` and `$parent__not_in` were added.
    140      * @since 4.4.0 Order by `comment__in` was added. `$update_comment_meta_cache` and `$no_found_rows` were added.
     140     * @since 4.4.0 Order by `comment__in` was added. `$update_comment_meta_cache`, `$no_found_rows`,
     141     *              and `$hierarchical` were added.
    141142     * @access public
    142143     *
     
    207208     *     @type array        $type__not_in        Exclude comments from a given array of comment types. Default empty.
    208209     *     @type int          $user_id             Include comments for a specific user ID. Default empty.
     210     *     @type bool|string  $hierarchical        Whether to include comment descendants in the results.
     211     *                                             'threaded' returns a tree, with each comment's children stored
     212     *                                             in a `children` property on the `WP_Comment` object. 'flat'
     213     *                                             returns a flat array of found comments plus their children.
     214     *                                             Pass `false` to leave out descendants. The parameter is ignored
     215     *                                             (forced to `false`) when `$fields` is 'ids' or 'counts'.
     216     *                                             Accepts 'threaded', 'flat', or false. Default: false.
    209217     *     @type bool         $update_comment_meta_cache Whether to prime the metadata cache for found comments.
    210218     *                                                   Default true.
     
    250258            'meta_query' => '',
    251259            'date_query' => null, // See WP_Date_Query
     260            'hierarchical' => false,
    252261            'update_comment_meta_cache' => true,
    253262        );
     
    397406        $comments = array_map( 'get_comment', $_comments );
    398407
     408        if ( $this->query_vars['hierarchical'] ) {
     409            $comments = $this->fill_descendants( $comments );
     410        }
     411
    399412        $this->comments = $comments;
    400413        return $this->comments;
     
    664677                $this->sql_clauses['where']['comment_type__' . strtolower( str_replace( ' ', '_', $operator ) ) ] = "comment_type $operator ($types_sql)";
    665678            }
     679        }
     680
     681        if ( $this->query_vars['hierarchical'] && ! $this->query_vars['parent'] ) {
     682            $this->query_vars['parent'] = 0;
    666683        }
    667684
     
    796813            return array_map( 'intval', $comment_ids );
    797814        }
     815    }
     816
     817    /**
     818     * Fetch descendants for located comments.
     819     *
     820     * Instead of calling `get_children()` separately on each child comment, we do a single set of queries to fetch
     821     * the descendant trees for all matched top-level comments.
     822     *
     823     * @since 4.4.0
     824     *
     825     * @param array $comments Array of top-level comments whose descendants should be filled in.
     826     * @return array
     827     */
     828    protected function fill_descendants( $comments ) {
     829        global $wpdb;
     830
     831        $levels = array(
     832            0 => wp_list_pluck( $comments, 'comment_ID' ),
     833        );
     834
     835        $where_clauses = $this->sql_clauses['where'];
     836        unset(
     837            $where_clauses['parent'],
     838            $where_clauses['parent__in'],
     839            $where_clauses['parent__not_in']
     840        );
     841
     842        // Fetch an entire level of the descendant tree at a time.
     843        $level = 0;
     844        do {
     845            $parent_ids = $levels[ $level ];
     846            $where = 'WHERE ' . implode( ' AND ', $where_clauses ) . ' AND comment_parent IN (' . implode( ',', array_map( 'intval', $parent_ids ) ) . ')';
     847            $comment_ids = $wpdb->get_col( "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']}" );
     848
     849            $level++;
     850            $levels[ $level ] = $comment_ids;
     851        } while ( $comment_ids );
     852
     853        // Prime comment caches for non-top-level comments.
     854        $descendant_ids = array();
     855        for ( $i = 1; $i < count( $levels ); $i++ ) {
     856            $descendant_ids = array_merge( $descendant_ids, $levels[ $i ] );
     857        }
     858
     859        _prime_comment_caches( $descendant_ids, $this->query_vars['update_comment_meta_cache'] );
     860
     861        // Assemble a flat array of all comments + descendants.
     862        $all_comments = $comments;
     863        foreach ( $descendant_ids as $descendant_id ) {
     864            $all_comments[] = get_comment( $descendant_id );
     865        }
     866
     867        // If a threaded representation was requested, build the tree.
     868        if ( 'threaded' === $this->query_vars['hierarchical'] ) {
     869            $threaded_comments = $ref = array();
     870            foreach ( $all_comments as $k => $c ) {
     871                $_c = get_comment( $c->comment_ID );
     872
     873                // If the comment isn't in the reference array, it goes in the top level of the thread.
     874                if ( ! isset( $ref[ $c->comment_parent ] ) ) {
     875                    $threaded_comments[ $_c->comment_ID ] = $_c;
     876                    $ref[ $_c->comment_ID ] = $threaded_comments[ $_c->comment_ID ];
     877
     878                // Otherwise, set it as a child of its parent.
     879                } else {
     880
     881                    $ref[ $_c->comment_parent ]->add_child( $_c );
     882//                  $ref[ $c->comment_parent ]->children[ $c->comment_ID ] = $c;
     883                    $ref[ $_c->comment_ID ] = $ref[ $_c->comment_parent ]->get_child( $_c->comment_ID );
     884                }
     885            }
     886
     887            $comments = $threaded_comments;
     888        } else {
     889            $comments = $all_comments;
     890        }
     891
     892        return $comments;
    798893    }
    799894
  • trunk/src/wp-includes/class-wp-comment.php

    r34409 r34546  
    151151
    152152    /**
     153     * Comment children.
     154     *
     155     * @since 4.4.0
     156     * @access protected
     157     * @var array
     158     */
     159    protected $children;
     160
     161    /**
    153162     * Retrieves a WP_Comment instance.
    154163     *
     
    212221        return get_object_vars( $this );
    213222    }
     223
     224    /**
     225     * Get the children of a comment.
     226     *
     227     * @since 4.4.0
     228     * @access public
     229     *
     230     * @return array Array of `WP_Comment` objects.
     231     */
     232    public function get_children() {
     233        if ( is_null( $this->children ) ) {
     234            $this->children = get_comments( array(
     235                'parent' => $this->comment_ID,
     236                'hierarchical' => 'threaded',
     237            ) );
     238        }
     239
     240        return $this->children;
     241    }
     242
     243    /**
     244     * Add a child to the comment.
     245     *
     246     * Used by `WP_Comment_Query` when bulk-filling descendants.
     247     *
     248     * @since 4.4.0
     249     * @access public
     250     *
     251     * @param WP_Comment $child Child comment.
     252     */
     253    public function add_child( WP_Comment $child ) {
     254        $this->comments[ $child->comment_ID ] = $child;
     255    }
     256
     257    /**
     258     * Get a child comment by ID.
     259     *
     260     * @since 4.4.0
     261     * @access public
     262     *
     263     * @param int $child_id ID of the child.
     264     * @return WP_Comment|bool Returns the comment object if found, otherwise false.
     265     */
     266    public function get_child( $child_id ) {
     267        if ( isset( $this->comments[ $child_id ] ) ) {
     268            return $this->comments[ $child_id ];
     269        }
     270
     271        return false;
     272    }
    214273}
  • trunk/tests/phpunit/tests/comment.php

    r34533 r34546  
    289289        $this->assertEquals( 'fire', get_comment_meta( $c, 'sauce', true ) );
    290290    }
     291
     292    /**
     293     * @ticket 8071
     294     */
     295    public function test_wp_comment_get_children_should_fill_children() {
     296
     297        $p = $this->factory->post->create();
     298
     299        $c1 = $this->factory->comment->create( array(
     300            'comment_post_ID' => $p,
     301            'comment_approved' => '1',
     302        ) );
     303
     304        $c2 = $this->factory->comment->create( array(
     305            'comment_post_ID' => $p,
     306            'comment_approved' => '1',
     307            'comment_parent' => $c1,
     308        ) );
     309
     310        $c3 = $this->factory->comment->create( array(
     311            'comment_post_ID' => $p,
     312            'comment_approved' => '1',
     313            'comment_parent' => $c2,
     314        ) );
     315
     316        $c4 = $this->factory->comment->create( array(
     317            'comment_post_ID' => $p,
     318            'comment_approved' => '1',
     319            'comment_parent' => $c1,
     320        ) );
     321
     322        $c5 = $this->factory->comment->create( array(
     323            'comment_post_ID' => $p,
     324            'comment_approved' => '1',
     325        ) );
     326
     327        $c6 = $this->factory->comment->create( array(
     328            'comment_post_ID' => $p,
     329            'comment_approved' => '1',
     330            'comment_parent' => $c5,
     331        ) );
     332
     333        $comment = get_comment( $c1 );
     334        $children = $comment->get_children();
     335
     336        // Direct descendants of $c1.
     337        $this->assertEquals( array( $c2, $c4 ), array_values( wp_list_pluck( $children, 'comment_ID' ) ) );
     338
     339        // Direct descendants of $c2.
     340        $this->assertEquals( array( $c3 ), array_values( wp_list_pluck( $children[ $c2 ]->get_children(), 'comment_ID' ) ) );
     341    }
    291342}
  • trunk/tests/phpunit/tests/comment/query.php

    r34544 r34546  
    19211921        $this->assertEquals( 2, $q->max_num_pages );
    19221922    }
     1923
     1924    /**
     1925     * @ticket 8071
     1926     */
     1927    public function test_hierarchical_should_skip_child_comments_in_offset() {
     1928        $top_level_0 = $this->factory->comment->create( array(
     1929            'comment_post_ID' => $this->post_id,
     1930            'comment_approved' => '1',
     1931        ) );
     1932
     1933        $child_of_0 = $this->factory->comment->create( array(
     1934            'comment_post_ID' => $this->post_id,
     1935            'comment_approved' => '1',
     1936            'comment_parent' => $top_level_0,
     1937        ) );
     1938
     1939        $top_level_comments = $this->factory->comment->create_many( 3, array(
     1940            'comment_post_ID' => $this->post_id,
     1941            'comment_approved' => '1',
     1942        ) );
     1943
     1944        $q = new WP_Comment_Query( array(
     1945            'post_id' => $this->post_id,
     1946            'hierarchical' => 'flat',
     1947            'number' => 2,
     1948            'offset' => 1,
     1949            'orderby' => 'comment_ID',
     1950            'order' => 'ASC',
     1951            'fields' => 'ids',
     1952        ) );
     1953
     1954        $this->assertEquals( array( $top_level_comments[0], $top_level_comments[1] ), $q->comments );
     1955    }
     1956
     1957    /**
     1958     * @ticket 8071
     1959     */
     1960    public function test_hierarchical_should_not_include_child_comments_in_number() {
     1961        $top_level_0 = $this->factory->comment->create( array(
     1962            'comment_post_ID' => $this->post_id,
     1963            'comment_approved' => '1',
     1964        ) );
     1965
     1966        $child_of_0 = $this->factory->comment->create( array(
     1967            'comment_post_ID' => $this->post_id,
     1968            'comment_approved' => '1',
     1969            'comment_parent' => $top_level_0,
     1970        ) );
     1971
     1972        $top_level_comments = $this->factory->comment->create_many( 3, array(
     1973            'comment_post_ID' => $this->post_id,
     1974            'comment_approved' => '1',
     1975        ) );
     1976
     1977        $q = new WP_Comment_Query( array(
     1978            'post_id' => $this->post_id,
     1979            'hierarchical' => 'flat',
     1980            'number' => 2,
     1981            'orderby' => 'comment_ID',
     1982            'order' => 'ASC',
     1983        ) );
     1984
     1985        $this->assertEqualSets( array( $top_level_0, $child_of_0, $top_level_comments[0] ), wp_list_pluck( $q->comments, 'comment_ID' ) );
     1986    }
     1987
     1988    /**
     1989     * @ticket 8071
     1990     */
     1991    public function test_hierarchical_threaded() {
     1992        $c1 = $this->factory->comment->create( array(
     1993            'comment_post_ID' => $this->post_id,
     1994            'comment_approved' => '1',
     1995        ) );
     1996
     1997        $c2 = $this->factory->comment->create( array(
     1998            'comment_post_ID' => $this->post_id,
     1999            'comment_approved' => '1',
     2000            'comment_parent' => $c1,
     2001        ) );
     2002
     2003        $c3 = $this->factory->comment->create( array(
     2004            'comment_post_ID' => $this->post_id,
     2005            'comment_approved' => '1',
     2006            'comment_parent' => $c2,
     2007        ) );
     2008
     2009        $c4 = $this->factory->comment->create( array(
     2010            'comment_post_ID' => $this->post_id,
     2011            'comment_approved' => '1',
     2012            'comment_parent' => $c1,
     2013        ) );
     2014
     2015        $c5 = $this->factory->comment->create( array(
     2016            'comment_post_ID' => $this->post_id,
     2017            'comment_approved' => '1',
     2018        ) );
     2019
     2020        $c6 = $this->factory->comment->create( array(
     2021            'comment_post_ID' => $this->post_id,
     2022            'comment_approved' => '1',
     2023            'comment_parent' => $c5,
     2024        ) );
     2025
     2026        $q = new WP_Comment_Query( array(
     2027            'post_id' => $this->post_id,
     2028            'hierarchical' => 'threaded',
     2029            'orderby' => 'comment_ID',
     2030            'order' => 'ASC',
     2031        ) );
     2032
     2033        // Top-level comments.
     2034        $this->assertEquals( array( $c1, $c5 ), array_values( wp_list_pluck( $q->comments, 'comment_ID' ) ) );
     2035
     2036        // Direct descendants of $c1.
     2037        $this->assertEquals( array( $c2, $c4 ), array_values( wp_list_pluck( $q->comments[ $c1 ]->get_children(), 'comment_ID' ) ) );
     2038
     2039        // Direct descendants of $c2.
     2040        $this->assertEquals( array( $c3 ), array_values( wp_list_pluck( $q->comments[ $c1 ]->get_child( $c2 )->get_children(), 'comment_ID' ) ) );
     2041
     2042        // Direct descendants of $c5.
     2043        $this->assertEquals( array( $c6 ), array_values( wp_list_pluck( $q->comments[ $c5 ]->get_children(), 'comment_ID' ) ) );
     2044    }
    19232045}
Note: See TracChangeset for help on using the changeset viewer.