Make WordPress Core

Changeset 60788


Ignore:
Timestamp:
09/21/2025 05:25:49 AM (11 months ago)
Author:
westonruter
Message:

Posts, Post Types: Improve wp_count_posts() query performance for users who cannot read_private_posts.

The query is refactored to use two subqueries which can leverage DB indexes.

Props rcorrales, snehapatil02, sirlouen, sajjad67, pbearne, johnbillion, westonruter.
Fixes #61097.

Location:
trunk
Files:
2 edited

Legend:

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

    r60724 r60788  
    34013401        }
    34023402
    3403         $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
    3404 
    3405         if ( 'readable' === $perm && is_user_logged_in() ) {
    3406                 $post_type_object = get_post_type_object( $type );
    3407                 if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
    3408                         $query .= $wpdb->prepare(
    3409                                 " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
    3410                                 get_current_user_id()
    3411                         );
    3412                 }
    3413         }
    3414 
    3415         $query .= ' GROUP BY post_status';
    3416 
    3417         $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
     3403        if (
     3404                'readable' === $perm &&
     3405                is_user_logged_in() &&
     3406                ! current_user_can( get_post_type_object( $type )->cap->read_private_posts )
     3407        ) {
     3408                // Optimized query uses subqueries which can leverage DB indexes for better performance. See #61097.
     3409                $query = $wpdb->prepare(
     3410                        "
     3411                        SELECT post_status, COUNT(*) AS num_posts
     3412                        FROM (
     3413                                SELECT post_status
     3414                                FROM {$wpdb->posts}
     3415                                WHERE post_type = %s AND post_status != 'private'
     3416                                UNION ALL
     3417                                SELECT post_status
     3418                                FROM {$wpdb->posts}
     3419                                WHERE post_type = %s AND post_status = 'private' AND post_author = %d
     3420                        ) AS filtered_posts
     3421                        ",
     3422                        $type,
     3423                        $type,
     3424                        get_current_user_id()
     3425                );
     3426        } else {
     3427                $query = $wpdb->prepare(
     3428                        "
     3429                        SELECT post_status, COUNT(*) AS num_posts
     3430                        FROM {$wpdb->posts}
     3431                        WHERE post_type = %s
     3432                        ",
     3433                        $type
     3434                );
     3435        }
     3436
     3437        $query  .= ' GROUP BY post_status';
     3438        $results = (array) $wpdb->get_results( $query, ARRAY_A );
    34183439        $counts  = array_fill_keys( get_post_stati(), 0 );
    34193440
  • trunk/tests/phpunit/tests/post.php

    r60251 r60788  
    77 */
    88class Tests_Post extends WP_UnitTestCase {
    9         protected static $editor_id;
    109        protected static $grammarian_id;
    1110
     11        protected static $user_ids = array(
     12                'administrator' => null,
     13                'editor'        => null,
     14                'author'        => null,
     15                'contributor'   => null,
     16        );
     17
    1218        private $post_ids = array();
    1319
    1420        public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
    15                 self::$editor_id = $factory->user->create( array( 'role' => 'editor' ) );
     21
     22                self::$user_ids = array(
     23                        'administrator' => $factory->user->create(
     24                                array(
     25                                        'role' => 'administrator',
     26                                )
     27                        ),
     28                        'editor'        => $factory->user->create(
     29                                array(
     30                                        'role' => 'editor',
     31                                )
     32                        ),
     33                        'author'        => $factory->user->create(
     34                                array(
     35                                        'role' => 'author',
     36                                )
     37                        ),
     38                        'contributor'   => $factory->user->create(
     39                                array(
     40                                        'role' => 'contributor',
     41                                )
     42                        ),
     43                );
    1644
    1745                add_role(
     
    143171        /**
    144172         * @ticket 24803
     173         * @covers ::wp_count_posts
    145174         */
    146175        public function test_wp_count_posts() {
     
    162191        }
    163192
     193        /**
     194         * Ensure `wp_count_posts()` in 'readable' context excludes private posts
     195         * authored by other users when the current user lacks the capability to
     196         * read private posts.
     197         *
     198         * @ticket 61097
     199         * @covers ::wp_count_posts
     200         */
     201        public function test_wp_count_posts_readable_excludes_unreadable_private_posts() {
     202                $post_type = rand_str( 20 );
     203                register_post_type( $post_type );
     204
     205                $admin_user_id = self::$user_ids['administrator'];
     206
     207                self::factory()->post->create_many(
     208                        5,
     209                        array(
     210                                'post_type'   => $post_type,
     211                                'post_status' => 'publish',
     212                                'post_author' => $admin_user_id,
     213                        )
     214                );
     215
     216                self::factory()->post->create_many(
     217                        3,
     218                        array(
     219                                'post_type'   => $post_type,
     220                                'post_status' => 'private',
     221                                'post_author' => $admin_user_id,
     222                        )
     223                );
     224
     225                $current_user_id = self::$user_ids['author'];
     226                wp_set_current_user( $current_user_id );
     227
     228                $count = wp_count_posts( $post_type, 'readable' );
     229                $this->assertEquals( 5, $count->publish );
     230                _unregister_post_type( $post_type );
     231        }
     232
     233        /**
     234         * @covers ::wp_count_posts
     235         */
    164236        public function test_wp_count_posts_filtered() {
    165237                $post_type = rand_str( 20 );
     
    187259        }
    188260
     261        /**
     262         * @covers ::wp_count_posts
     263         */
    189264        public function test_wp_count_posts_insert_invalidation() {
    190265                $post_ids       = self::factory()->post->create_many( 3 );
     
    207282        }
    208283
     284        /**
     285         * @covers ::wp_count_posts
     286         */
    209287        public function test_wp_count_posts_trash_invalidation() {
    210288                $post_ids       = self::factory()->post->create_many( 3 );
     
    227305        /**
    228306         * @ticket 49685
     307         * @covers ::wp_count_posts
    229308         */
    230309        public function test_wp_count_posts_status_changes_visible() {
     
    253332                wp_set_object_terms( $post, 'foo', $tax );
    254333
    255                 wp_set_current_user( self::$editor_id );
     334                wp_set_current_user( self::$user_ids['editor'] );
    256335
    257336                $wp_tag_cloud = wp_tag_cloud(
     
    306385                );
    307386
    308                 wp_set_current_user( self::$editor_id );
     387                wp_set_current_user( self::$user_ids['editor'] );
    309388
    310389                edit_post( $data );
Note: See TracChangeset for help on using the changeset viewer.