Make WordPress Core


Ignore:
Timestamp:
09/25/2015 08:39:18 PM (9 years ago)
Author:
boonebgorges
Message:

Force comment pagination on single posts.

Previously, the 'page_comments' toggle allowed users to disable comment
pagination. This toggle was only superficial, however. Even with
'page_comments' turned on, comments_template() loaded all of a post's
comments into memory, and passed them to wp_list_comments() and
Walker_Comment, the latter of which produced markup for only the
current page of comments. In other words, it was possible to enable
'page_comments', thereby showing only a subset of a post's comments on a given
page, but all comments continued to be loaded in the background. This technique
scaled poorly. Posts with hundreds or thousands of comments would load slowly,
or not at all, even when the 'comments_per_page' setting was set to a
reasonable number.

Recent changesets have addressed this problem through more efficient tree-
walking, better descendant caching, and more selective queries for top-level
post comments. The current changeset completes the project by addressing the
root issue: that loading a post causes all of its comments to be loaded too.

Here's the breakdown:

  • Comment pagination is now forced. Setting 'page_comments' to false leads to evil things when you have many comments. If you want to avoid pagination, set 'comments_per_page' to something high.
  • The 'page_comments' setting has been expunged from options-discussion.php, and from places in the codebase where it was referenced. For plugins relying on 'page_comments', we now force the value to true with a pre_option filter.
  • comments_template() now queries for an appropriately small number of comments. Usually, this means the comments_per_page value.
  • To preserve the current (odd) behavior for comment pagination links, some unholy hacks have been inserted into comments_template(). The ugliness is insulated in this function for backward compatibility and to minimize collateral damage. A side-effect is that, for certain settings of 'default_comments_page', up to 2x the value of comments_per_page might be fetched at a time.
  • In support of these changes, a $format parameter has been added to WP_Comment::get_children(). This param allows you to request a flattened array of comment children, suitable for feeding into Walker_Comment.
  • WP_Query loops are now informed about total available comment counts and comment pages by the WP_Comment_Query (found_comments, max_num_pages), instead of by Walker_Comment.

Aside from radical performance improvements in the case of a post with many
comments, this changeset fixes a bug that caused the first page of comments to
be partial (found_comments % comments_per_page), rather than the last, as
you'd expect.

Props boonebgorges, wonderboymusic.
Fixes #8071.

File:
1 edited

Legend:

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

    r34525 r34561  
    684684    $args = wp_parse_args( $args, $defaults );
    685685
    686     if ( '' === $args['per_page'] && get_option('page_comments') )
     686    if ( '' === $args['per_page'] )
    687687        $args['per_page'] = get_option('comments_per_page');
    688688
     
    12151215
    12161216    $comment_args = array(
    1217         'order'   => 'ASC',
    12181217        'orderby' => 'comment_date_gmt',
    12191218        'status'  => 'approve',
    12201219        'post_id' => $post->ID,
     1220        'hierarchical' => 'threaded',
     1221        'no_found_rows' => false,
    12211222        'update_comment_meta_cache' => false, // We lazy-load comment meta for performance.
    12221223    );
     
    12281229    }
    12291230
    1230     $comments = get_comments( $comment_args );
     1231    $per_page = (int) get_query_var( 'comments_per_page' );
     1232    if ( 0 === $per_page ) {
     1233        $per_page = (int) get_option( 'comments_per_page' );
     1234    }
     1235
     1236    $flip_comment_order = $trim_comments_on_page = false;
     1237    if ( $post->comment_count > $per_page ) {
     1238        $comment_args['number'] = $per_page;
     1239
     1240        /*
     1241         * For legacy reasons, higher page numbers always mean more recent comments, regardless of sort order.
     1242         * Since we don't have full pagination info until after the query, we use some tricks to get the
     1243         * right comments for the current page.
     1244         *
     1245         * Abandon all hope, ye who enter here!
     1246         */
     1247        $page = (int) get_query_var( 'cpage' );
     1248        if ( 'newest' === get_option( 'default_comments_page' ) ) {
     1249            if ( $page ) {
     1250                $comment_args['order'] = 'ASC';
     1251
     1252                /*
     1253                 * We don't have enough data (namely, the total number of comments) to calculate an
     1254                 * exact offset. We'll fetch too many comments, and trim them as needed
     1255                 * after the query.
     1256                 */
     1257                $offset = ( $page - 2 ) * $per_page;
     1258                if ( 0 > $offset ) {
     1259                    // `WP_Comment_Query` doesn't support negative offsets.
     1260                    $comment_args['offset'] = 0;
     1261                } else {
     1262                    $comment_args['offset'] = $offset;
     1263                }
     1264
     1265                // Fetch double the number of comments we need.
     1266                $comment_args['number'] += $per_page;
     1267                $trim_comments_on_page = true;
     1268            } else {
     1269                $comment_args['order'] = 'DESC';
     1270                $comment_args['offset'] = 0;
     1271                $flip_comment_order = true;
     1272            }
     1273        } else {
     1274            $comment_args['order'] = 'ASC';
     1275            if ( $page ) {
     1276                $comment_args['offset'] = ( $page - 1 ) * $per_page;
     1277            } else {
     1278                $comment_args['offset'] = 0;
     1279            }
     1280        }
     1281    }
     1282
     1283    $comment_query = new WP_Comment_Query( $comment_args );
     1284    $_comments = $comment_query->comments;
     1285
     1286    // Delightful pagination quirk #1: first page of results sometimes needs reordering.
     1287    if ( $flip_comment_order ) {
     1288        $_comments = array_reverse( $_comments );
     1289    }
     1290
     1291    // Delightful pagination quirk #2: reverse chronological order requires page shifting.
     1292    if ( $trim_comments_on_page ) {
     1293        // Correct the value of max_num_pages, which is wrong because we manipulated the per_page 'number'.
     1294        $comment_query->max_num_pages = ceil( $comment_query->found_comments / $per_page );
     1295
     1296        // Identify the number of comments that should appear on page 1.
     1297        $page_1_count = $comment_query->found_comments - ( ( $comment_query->max_num_pages - 1 ) * $per_page );
     1298
     1299        // Use that value to shift the matched comments.
     1300        if ( 1 === $page ) {
     1301            $_comments = array_slice( $_comments, 0, $page_1_count );
     1302        } else {
     1303            $_comments = array_slice( $_comments, $page_1_count, $per_page );
     1304        }
     1305    }
     1306
     1307    // Trees must be flattened before they're passed to the walker.
     1308    $comments_flat = array();
     1309    foreach ( $_comments as $_comment ) {
     1310        $comments_flat = array_merge( $comments_flat, array( $_comment ), $_comment->get_children( 'flat' ) );
     1311    }
    12311312
    12321313    /**
     
    12381319     * @param int   $post_ID  Post ID.
    12391320     */
    1240     $wp_query->comments = apply_filters( 'comments_array', $comments, $post->ID );
     1321    $wp_query->comments = apply_filters( 'comments_array', $comments_flat, $post->ID );
    12411322    $comments = &$wp_query->comments;
    12421323    $wp_query->comment_count = count($wp_query->comments);
     1324    $wp_query->max_num_comment_pages = $comment_query->max_num_pages;
    12431325
    12441326    if ( $separate_comments ) {
     
    12501332
    12511333    $overridden_cpage = false;
    1252     if ( '' == get_query_var('cpage') && get_option('page_comments') ) {
     1334    if ( '' == get_query_var('cpage') ) {
    12531335        set_query_var( 'cpage', 'newest' == get_option('default_comments_page') ? get_comment_pages_count() : 1 );
    12541336        $overridden_cpage = true;
     
    18261908            $_comments = $wp_query->comments;
    18271909        }
    1828     }
    1829 
    1830     if ( '' === $r['per_page'] && get_option('page_comments') )
     1910
     1911        // Pagination is already handled by `WP_Comment_Query`, so we tell Walker not to bother.
     1912        if ( 1 < $wp_query->max_num_comment_pages ) {
     1913            $r['page'] = 1;
     1914        }
     1915    }
     1916
     1917    if ( '' === $r['per_page'] )
    18311918        $r['per_page'] = get_query_var('comments_per_page');
    18321919
     
    18671954
    18681955    $output = $walker->paged_walk( $_comments, $r['max_depth'], $r['page'], $r['per_page'], $r );
    1869     $wp_query->max_num_comment_pages = $walker->max_pages;
    18701956
    18711957    $in_comment_loop = false;
Note: See TracChangeset for help on using the changeset viewer.