Make WordPress Core

Changeset 34561


Ignore:
Timestamp:
09/25/2015 08:39:18 PM (11 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.

Location:
trunk
Files:
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/schema.php

    r34529 r34561  
    483483        'thread_comments' => 1,
    484484        'thread_comments_depth' => 5,
    485         'page_comments' => 0,
     485        'page_comments' => 1,
    486486        'comments_per_page' => 50,
    487487        'default_comments_page' => 'newest',
  • trunk/src/wp-admin/options-discussion.php

    r34022 r34561  
    9999?></label>
    100100<br />
    101 <label for="page_comments">
    102 <input name="page_comments" type="checkbox" id="page_comments" value="1" <?php checked('1', get_option('page_comments')); ?> />
    103 <?php
    104 
     101<?php
    105102$default_comments_page = '</label><label for="default_comments_page"><select name="default_comments_page" id="default_comments_page"><option value="newest"';
    106103if ( 'newest' == get_option('default_comments_page') ) $default_comments_page .= ' selected="selected"';
     
    109106$default_comments_page .= '>' . __('first') . '</option></select>';
    110107
    111 printf( __('Break comments into pages with %1$s top level comments per page and the %2$s page displayed by default'), '</label><label for="comments_per_page"><input name="comments_per_page" type="number" step="1" min="0" id="comments_per_page" value="' . esc_attr(get_option('comments_per_page')) . '" class="small-text" />', $default_comments_page );
     108printf( __('Break comments into pages with %1$s top level comments per page and the %2$s page displayed by default'), '<label for="comments_per_page"><input name="comments_per_page" type="number" step="1" min="0" id="comments_per_page" value="' . esc_attr(get_option('comments_per_page')) . '" class="small-text" />', $default_comments_page );
    112109
    113110?></label>
  • trunk/src/wp-admin/options.php

    r34315 r34561  
    8484$whitelist_options = array(
    8585        'general' => array( 'blogname', 'blogdescription', 'gmt_offset', 'date_format', 'time_format', 'start_of_week', 'timezone_string', 'WPLANG' ),
    86         'discussion' => array( 'default_pingback_flag', 'default_ping_status', 'default_comment_status', 'comments_notify', 'moderation_notify', 'comment_moderation', 'require_name_email', 'comment_whitelist', 'comment_max_links', 'moderation_keys', 'blacklist_keys', 'show_avatars', 'avatar_rating', 'avatar_default', 'close_comments_for_old_posts', 'close_comments_days_old', 'thread_comments', 'thread_comments_depth', 'page_comments', 'comments_per_page', 'default_comments_page', 'comment_order', 'comment_registration' ),
     86        'discussion' => array( 'default_pingback_flag', 'default_ping_status', 'default_comment_status', 'comments_notify', 'moderation_notify', 'comment_moderation', 'require_name_email', 'comment_whitelist', 'comment_max_links', 'moderation_keys', 'blacklist_keys', 'show_avatars', 'avatar_rating', 'avatar_default', 'close_comments_for_old_posts', 'close_comments_days_old', 'thread_comments', 'thread_comments_depth', 'comments_per_page', 'default_comments_page', 'comment_order', 'comment_registration' ),
    8787        'media' => array( 'thumbnail_size_w', 'thumbnail_size_h', 'thumbnail_crop', 'medium_size_w', 'medium_size_h', 'large_size_w', 'large_size_h', 'image_default_size', 'image_default_align', 'image_default_link_type' ),
    8888        'reading' => array( 'posts_per_page', 'posts_per_rss', 'rss_use_excerpt', 'show_on_front', 'page_on_front', 'page_for_posts', 'blog_public' ),
  • trunk/src/wp-includes/canonical.php

    r34492 r34561  
    322322                        }
    323323
    324                         if ( get_option('page_comments') && ( ( 'newest' == get_option('default_comments_page') && get_query_var('cpage') > 0 ) || ( 'newest' != get_option('default_comments_page') && get_query_var('cpage') > 1 ) ) ) {
     324                        if ( ( 'newest' == get_option('default_comments_page') && get_query_var('cpage') > 0 ) || ( 'newest' != get_option('default_comments_page') && get_query_var('cpage') > 1 ) ) {
    325325                                $addl_path = ( !empty( $addl_path ) ? trailingslashit($addl_path) : '' ) . user_trailingslashit( $wp_rewrite->comments_pagination_base . '-' . get_query_var('cpage'), 'commentpaged' );
    326326                                $redirect['query'] = remove_query_arg( 'cpage', $redirect['query'] );
  • trunk/src/wp-includes/class-wp-comment-query.php

    r34549 r34561  
    866866                do {
    867867                        $parent_ids = $levels[ $level ];
     868                        if ( ! $parent_ids ) {
     869                                break;
     870                        }
     871
    868872                        $where = 'WHERE ' . implode( ' AND ', $where_clauses ) . ' AND comment_parent IN (' . implode( ',', array_map( 'intval', $parent_ids ) ) . ')';
    869873                        $comment_ids = $wpdb->get_col( "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']}" );
  • trunk/src/wp-includes/class-wp-comment.php

    r34546 r34561  
    228228         * @access public
    229229         *
     230         * @param string $format Return value format. 'tree' for a hierarchical tree, 'flat' for a flattened array.
     231         *                       Default 'tree'.
    230232         * @return array Array of `WP_Comment` objects.
    231233         */
    232         public function get_children() {
     234        public function get_children( $format = 'tree' ) {
    233235                if ( is_null( $this->children ) ) {
    234236                        $this->children = get_comments( array(
     
    238240                }
    239241
    240                 return $this->children;
     242                if ( 'flat' === $format ) {
     243                        $children = array();
     244                        foreach ( $this->children as $child ) {
     245                                $children = array_merge( $children, array( $child ), $child->get_children( 'flat' ) );
     246                        }
     247                } else {
     248                        $children = $this->children;
     249                }
     250
     251                return $children;
    241252        }
    242253
  • trunk/src/wp-includes/comment-functions.php

    r34545 r34561  
    808808                return 0;
    809809
    810         if ( ! get_option( 'page_comments' ) )
    811                 return 1;
    812 
    813810        if ( !isset($per_page) )
    814811                $per_page = (int) get_query_var('comments_per_page');
     
    859856        $args = wp_parse_args( $args, $defaults );
    860857
    861         if ( '' === $args['per_page'] && get_option('page_comments') )
     858        if ( '' === $args['per_page'] )
    862859                $args['per_page'] = get_query_var('comments_per_page');
    863860        if ( empty($args['per_page']) ) {
  • 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;
  • trunk/src/wp-includes/default-filters.php

    r34529 r34561  
    317317add_filter( 'default_option_embed_autourls', '__return_true' );
    318318
     319// This option no longer exists; tell plugins we want comment pagination.
     320add_filter( 'pre_option_page_comments', '__return_true' );
     321
    319322// Default settings for heartbeat
    320323add_filter( 'heartbeat_settings', 'wp_heartbeat_settings' );
  • trunk/src/wp-includes/link-template.php

    r34528 r34561  
    25912591        global $wp_query;
    25922592
    2593         if ( !is_singular() || !get_option('page_comments') )
     2593        if ( ! is_singular() )
    25942594                return;
    25952595
     
    26452645 */
    26462646function get_previous_comments_link( $label = '' ) {
    2647         if ( !is_singular() || !get_option('page_comments') )
     2647        if ( ! is_singular() )
    26482648                return;
    26492649
     
    26932693        global $wp_rewrite;
    26942694
    2695         if ( !is_singular() || !get_option('page_comments') )
     2695        if ( ! is_singular() )
    26962696                return;
    26972697
     
    27382738
    27392739        // Are there comments to navigate through?
    2740         if ( get_comment_pages_count() > 1 && get_option( 'page_comments' ) ) {
     2740        if ( get_comment_pages_count() > 1 ) {
    27412741                $args = wp_parse_args( $args, array(
    27422742                        'prev_text'          => __( 'Older comments' ),
  • trunk/tests/phpunit/tests/comment/getCommentsPagesCount.php

    r31623 r34561  
    6969                $this->assertEquals( 2, get_comment_pages_count( $comments, 10, true ) );
    7070                $this->assertEquals( 4, get_comment_pages_count( $comments, 4, true ) );
    71         }
    72 
    73         /**
    74          * Validate get_comments_pages_count for option page_comments
    75          */
    76         function test_option_page_comments() {
    77                 //setup post and comments
    78                 $post = $this->factory->post->create_and_get( array( 'post_title' => 'comment--post', 'post_type' => 'post' ) );
    79                 $comments = $this->factory->comment->create_post_comments( $post->ID, 15 );
    80 
    81                 // comment paging disabled
    82                 update_option( 'page_comments', false );
    83 
    84                 $this->assertEquals( 1, get_comment_pages_count( $comments, 10, false ) );
    85                 $this->assertEquals( 1, get_comment_pages_count( $comments, 0, true ) );
    86                 $this->assertEquals( 1, get_comment_pages_count( $comments, 2, true ) );
    87 
    88                 // comment paging enabled
    89                 update_option( 'page_comments', true );
    90 
    91                 $this->assertEquals( 2, get_comment_pages_count( $comments, 10, false ) );
    92                 $this->assertEquals( 3, get_comment_pages_count( $comments, 5, false ) );
    9371        }
    9472
  • trunk/tests/phpunit/tests/link/getNextCommentsLink.php

    r31617 r34561  
    1717
    1818        public function test_page_should_respect_value_of_cpage_query_var() {
    19                 update_option( 'page_comments', '1' );
    2019                $p = $this->factory->post->create();
    2120                $this->go_to( get_permalink( $p ) );
     
    3534         */
    3635        public function test_page_should_default_to_1_when_no_cpage_query_var_is_found() {
    37                 update_option( 'page_comments', '1' );
    3836                $p = $this->factory->post->create();
    3937                $this->go_to( get_permalink( $p ) );
  • trunk/tests/phpunit/tests/link/getPreviousCommentsLink.php

    r31617 r34561  
    1717
    1818        public function test_page_should_respect_value_of_cpage_query_var() {
    19                 update_option( 'page_comments', '1' );
    2019                $p = $this->factory->post->create();
    2120                $this->go_to( get_permalink( $p ) );
     
    3231
    3332        public function test_page_should_default_to_1_when_no_cpage_query_var_is_found() {
    34                 update_option( 'page_comments', '1' );
    3533                $p = $this->factory->post->create();
    3634                $this->go_to( get_permalink( $p ) );
Note: See TracChangeset for help on using the changeset viewer.