WordPress.org

Make WordPress Core

Changeset 51276


Ignore:
Timestamp:
06/30/2021 04:59:10 AM (3 months ago)
Author:
peterwilsoncc
Message:

Query: Check each post-type's capabilities when querying multiple post-types.

When querying multiple post types, check the read_private_posts capability for each post type when determining which post statuses to return. This ensures private posts appear in search results and archives for users permitted to read them.

Props leogermani, hellofromTonya, jeffpaul, peterwilsoncc.
Fixes #48556.

Location:
trunk
Files:
3 edited

Legend:

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

    r50576 r51276  
    24212421        }
    24222422
     2423        $skip_post_status = false;
    24232424        if ( 'any' === $post_type ) {
    24242425            $in_search_post_types = get_post_types( array( 'exclude_from_search' => false ) );
    24252426            if ( empty( $in_search_post_types ) ) {
    2426                 $where .= ' AND 1=0 ';
     2427                $post_type_where  = ' AND 1=0 ';
     2428                $skip_post_status = true;
    24272429            } else {
    2428                 $where .= " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')";
     2430                $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')";
    24292431            }
    24302432        } elseif ( ! empty( $post_type ) && is_array( $post_type ) ) {
    2431             $where .= " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
     2433            $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
    24322434        } elseif ( ! empty( $post_type ) ) {
    2433             $where           .= $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type );
     2435            $post_type_where  = $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type );
    24342436            $post_type_object = get_post_type_object( $post_type );
    24352437        } elseif ( $this->is_attachment ) {
    2436             $where           .= " AND {$wpdb->posts}.post_type = 'attachment'";
     2438            $post_type_where  = " AND {$wpdb->posts}.post_type = 'attachment'";
    24372439            $post_type_object = get_post_type_object( 'attachment' );
    24382440        } elseif ( $this->is_page ) {
    2439             $where           .= " AND {$wpdb->posts}.post_type = 'page'";
     2441            $post_type_where  = " AND {$wpdb->posts}.post_type = 'page'";
    24402442            $post_type_object = get_post_type_object( 'page' );
    24412443        } else {
    2442             $where           .= " AND {$wpdb->posts}.post_type = 'post'";
     2444            $post_type_where  = " AND {$wpdb->posts}.post_type = 'post'";
    24432445            $post_type_object = get_post_type_object( 'post' );
    24442446        }
     
    24582460
    24592461        $q_status = array();
    2460         if ( ! empty( $q['post_status'] ) ) {
     2462        if ( $skip_post_status ) {
     2463            $where .= $post_type_where;
     2464        } elseif ( ! empty( $q['post_status'] ) ) {
     2465
     2466            $where .= $post_type_where;
     2467
    24612468            $statuswheres = array();
    24622469            $q_status     = $q['post_status'];
     
    25182525            }
    25192526        } elseif ( ! $this->is_singular ) {
    2520             $where .= " AND ({$wpdb->posts}.post_status = 'publish'";
    2521 
    2522             // Add public states.
    2523             $public_states = get_post_stati( array( 'public' => true ) );
    2524             foreach ( (array) $public_states as $state ) {
    2525                 if ( 'publish' === $state ) { // Publish is hard-coded above.
    2526                     continue;
    2527                 }
    2528                 $where .= " OR {$wpdb->posts}.post_status = '$state'";
    2529             }
    2530 
    2531             if ( $this->is_admin ) {
    2532                 // Add protected states that should show in the admin all list.
    2533                 $admin_all_states = get_post_stati(
    2534                     array(
    2535                         'protected'              => true,
    2536                         'show_in_admin_all_list' => true,
    2537                     )
    2538                 );
    2539                 foreach ( (array) $admin_all_states as $state ) {
    2540                     $where .= " OR {$wpdb->posts}.post_status = '$state'";
    2541                 }
    2542             }
    2543 
    2544             if ( is_user_logged_in() ) {
    2545                 // Add private states that are limited to viewing by the author of a post or someone who has caps to read private states.
    2546                 $private_states = get_post_stati( array( 'private' => true ) );
    2547                 foreach ( (array) $private_states as $state ) {
    2548                     $where .= current_user_can( $read_private_cap ) ? " OR {$wpdb->posts}.post_status = '$state'" : " OR {$wpdb->posts}.post_author = $user_id AND {$wpdb->posts}.post_status = '$state'";
    2549                 }
    2550             }
    2551 
    2552             $where .= ')';
     2527            if ( 'any' === $post_type ) {
     2528                $queried_post_types = get_post_types( array( 'exclude_from_search' => false ) );
     2529            } elseif ( is_array( $post_type ) ) {
     2530                $queried_post_types = $post_type;
     2531            } elseif ( ! empty( $post_type ) ) {
     2532                $queried_post_types = array( $post_type );
     2533            } else {
     2534                $queried_post_types = array( 'post' );
     2535            }
     2536
     2537            if ( ! empty( $queried_post_types ) ) {
     2538
     2539                $status_type_clauses = array();
     2540
     2541                foreach ( $queried_post_types as $queried_post_type ) {
     2542
     2543                    $queried_post_type_object = get_post_type_object( $queried_post_type );
     2544
     2545                    $type_where = '(' . $wpdb->prepare( "{$wpdb->posts}.post_type = %s AND (", $queried_post_type );
     2546
     2547                    // Public statuses.
     2548                    $public_statuses = get_post_stati( array( 'public' => true ) );
     2549                    $status_clauses  = array();
     2550                    foreach ( (array) $public_statuses as $public_status ) {
     2551                        $status_clauses[] = "{$wpdb->posts}.post_status = '$public_status'";
     2552                    }
     2553                    $type_where .= implode( ' OR ', $status_clauses );
     2554
     2555                    // Add protected states that should show in the admin all list.
     2556                    if ( $this->is_admin ) {
     2557                        $admin_all_statuses = get_post_stati(
     2558                            array(
     2559                                'protected'              => true,
     2560                                'show_in_admin_all_list' => true,
     2561                            )
     2562                        );
     2563                        foreach ( (array) $admin_all_statuses as $admin_all_status ) {
     2564                            $type_where .= " OR {$wpdb->posts}.post_status = '$admin_all_status'";
     2565                        }
     2566                    }
     2567
     2568                    // Add private states that are visible to current user.
     2569                    if ( is_user_logged_in() && $queried_post_type_object instanceof WP_Post_Type ) {
     2570                        $read_private_cap = $queried_post_type_object->cap->read_private_posts;
     2571                        $private_statuses = get_post_stati( array( 'private' => true ) );
     2572                        foreach ( (array) $private_statuses as $private_status ) {
     2573                            $type_where .= current_user_can( $read_private_cap ) ? " \nOR {$wpdb->posts}.post_status = '$private_status'" : " \nOR ({$wpdb->posts}.post_author = $user_id AND {$wpdb->posts}.post_status = '$private_status')";
     2574                        }
     2575                    }
     2576
     2577                    $type_where .= '))';
     2578
     2579                    $status_type_clauses[] = $type_where;
     2580                }
     2581
     2582                if ( ! empty( $status_type_clauses ) ) {
     2583                    $where .= ' AND (' . implode( ' OR ', $status_type_clauses ) . ')';
     2584                }
     2585            } else {
     2586                $where .= ' AND 1=0 ';
     2587            }
     2588        } else {
     2589            $where .= $post_type_where;
    25532590        }
    25542591
  • trunk/tests/phpunit/tests/query/invalidQueries.php

    r49902 r51276  
    100100
    101101    /**
     102     * Test WP Query with an invalid post type in a mutiple post type query.
     103     *
     104     * @ticket 48556
     105     */
     106    public function test_unregistered_post_type_wp_query_multiple_post_types() {
     107        global $wpdb;
     108
     109        $query = new WP_Query(
     110            array(
     111                'post_type' => array( 'unregistered_cpt', 'page' ),
     112            )
     113        );
     114        $posts = $query->get_posts();
     115
     116        $this->assertContains( "{$wpdb->posts}.post_type = 'unregistered_cpt'", self::$last_posts_request );
     117        $this->assertCount( 1, $posts, 'the valid `page` post type should still return one post' );
     118    }
     119
     120    /**
    102121     * Test WP Query with an invalid post type specified in the URL.
    103122     *
  • trunk/tests/phpunit/tests/query/postStatus.php

    r49899 r51276  
    77    public static $editor_user_id;
    88    public static $author_user_id;
    9     public static $editor_private_post;
    10     public static $author_private_post;
    11     public static $editor_privatefoo_post;
    12     public static $author_privatefoo_post;
     9    public static $subscriber_user_id;
     10    public static $post_ids;
    1311
    1412    public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
    15         self::$editor_user_id = $factory->user->create( array( 'role' => 'editor' ) );
    16         self::$author_user_id = $factory->user->create( array( 'role' => 'author' ) );
    17 
    18         self::$editor_private_post = $factory->post->create(
     13        self::$editor_user_id     = $factory->user->create( array( 'role' => 'editor' ) );
     14        self::$author_user_id     = $factory->user->create( array( 'role' => 'author' ) );
     15        self::$subscriber_user_id = $factory->user->create( array( 'role' => 'subscriber' ) );
     16
     17        self::$post_ids['editor_private_post'] = $factory->post->create(
    1918            array(
    2019                'post_author' => self::$editor_user_id,
     
    2221            )
    2322        );
    24         self::$author_private_post = $factory->post->create(
     23        self::$post_ids['author_private_post'] = $factory->post->create(
    2524            array(
    2625                'post_author' => self::$author_user_id,
     
    3130        // Custom status with private=true.
    3231        register_post_status( 'privatefoo', array( 'private' => true ) );
    33         self::$editor_privatefoo_post = $factory->post->create(
     32        self::$post_ids['editor_privatefoo_post'] = $factory->post->create(
    3433            array(
    3534                'post_author' => self::$editor_user_id,
     
    3736            )
    3837        );
    39         self::$author_privatefoo_post = $factory->post->create(
     38        self::$post_ids['author_privatefoo_post'] = $factory->post->create(
    4039            array(
    4140                'post_author' => self::$author_user_id,
     
    4443        );
    4544        _unregister_post_status( 'privatefoo' );
     45
     46        self::register_custom_post_objects();
     47
     48        self::$post_ids['wptests_pt1_p1'] = $factory->post->create(
     49            array(
     50                'post_type'   => 'wptests_pt1',
     51                'post_status' => 'private',
     52                'post_author' => self::$editor_user_id,
     53            )
     54        );
     55
     56        self::$post_ids['wptests_pt1_p2'] = $factory->post->create(
     57            array(
     58                'post_type'   => 'wptests_pt1',
     59                'post_status' => 'publish',
     60                'post_author' => self::$editor_user_id,
     61            )
     62        );
     63
     64        self::$post_ids['wptests_pt2_p1'] = $factory->post->create(
     65            array(
     66                'post_type'   => 'wptests_pt2',
     67                'post_status' => 'private',
     68                'post_author' => self::$editor_user_id,
     69            )
     70        );
     71
     72        self::$post_ids['wptests_pt2_p2'] = $factory->post->create(
     73            array(
     74                'post_type'   => 'wptests_pt2',
     75                'post_status' => 'publish',
     76                'post_author' => self::$editor_user_id,
     77            )
     78        );
     79    }
     80
     81    public function setUp() {
     82        parent::setUp();
     83        self::register_custom_post_objects();
     84    }
     85
     86    /**
     87     * Register custom post types and statuses used in multiple tests.
     88     *
     89     * CPTs and CPSs are reset between each test run so need to be registered
     90     * in both the wpSetUpBeforeClass() and setUp() methods.
     91     */
     92    public static function register_custom_post_objects() {
     93        register_post_type(
     94            'wptests_pt1',
     95            array(
     96                'exclude_from_search' => false,
     97                'capabilities'        => array(
     98                    'read_private_posts' => 'read_private_pt1s',
     99                ),
     100            )
     101        );
     102
     103        register_post_type(
     104            'wptests_pt2',
     105            array(
     106                'exclude_from_search' => false,
     107            )
     108        );
    46109    }
    47110
     
    79142
    80143        $expected = array(
    81             self::$editor_private_post,
    82             self::$author_private_post,
     144            self::$post_ids['editor_private_post'],
     145            self::$post_ids['author_private_post'],
    83146        );
    84147
     
    110173
    111174        $expected = array(
    112             self::$author_private_post,
     175            self::$post_ids['author_private_post'],
    113176        );
    114177
     
    127190
    128191        $expected = array(
    129             self::$author_private_post,
    130             self::$editor_private_post,
     192            self::$post_ids['author_private_post'],
     193            self::$post_ids['editor_private_post'],
    131194        );
    132195
     
    145208
    146209        $expected = array(
    147             self::$author_private_post,
     210            self::$post_ids['author_private_post'],
    148211        );
    149212
     
    162225
    163226        $expected = array(
    164             self::$author_private_post,
    165             self::$editor_private_post,
     227            self::$post_ids['author_private_post'],
     228            self::$post_ids['editor_private_post'],
    166229        );
    167230
     
    226289        );
    227290
    228         $this->assertContains( self::$author_privatefoo_post, wp_list_pluck( $q->posts, 'ID' ) );
    229         $this->assertContains( self::$editor_privatefoo_post, wp_list_pluck( $q->posts, 'ID' ) );
     291        $this->assertContains( self::$post_ids['author_privatefoo_post'], wp_list_pluck( $q->posts, 'ID' ) );
     292        $this->assertContains( self::$post_ids['editor_privatefoo_post'], wp_list_pluck( $q->posts, 'ID' ) );
    230293    }
    231294
     
    237300        $q = new WP_Query(
    238301            array(
    239                 'posts_per_page' => 2, // Or the query will short-circuit.
     302                'posts_per_page' => -1, // Or the query will short-circuit.
    240303            )
    241304        );
    242305
    243306        $expected = array(
    244             self::$author_privatefoo_post,
    245         );
    246 
    247         $this->assertContains( self::$author_privatefoo_post, wp_list_pluck( $q->posts, 'ID' ) );
    248         $this->assertNotContains( self::$editor_privatefoo_post, wp_list_pluck( $q->posts, 'ID' ) );
     307            self::$post_ids['author_privatefoo_post'],
     308        );
     309
     310        $this->assertContains( self::$post_ids['author_privatefoo_post'], wp_list_pluck( $q->posts, 'ID' ) );
     311        $this->assertNotContains( self::$post_ids['editor_privatefoo_post'], wp_list_pluck( $q->posts, 'ID' ) );
    249312    }
    250313
     
    458521        $this->assertContains( $p1, wp_list_pluck( $q->posts, 'ID' ) );
    459522    }
     523
     524    /**
     525     * @ticket 48556
     526     * @ticket 13509
     527     */
     528    public function test_non_singular_queries_using_post_type_any_should_respect_post_type_read_private_posts_cap() {
     529        $post_ids = self::$post_ids;
     530
     531        wp_set_current_user( 0 );
     532
     533        $q = new WP_Query(
     534            array(
     535                'post_type' => 'any',
     536            )
     537        );
     538
     539        $this->assertSameSets( array( $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     540
     541        wp_set_current_user( self::$subscriber_user_id );
     542        $GLOBALS['current_user']->add_cap( 'read_private_pt1s' );
     543
     544        $q = new WP_Query(
     545            array(
     546                'post_type' => 'any',
     547            )
     548        );
     549
     550        $this->assertSameSets( array( $post_ids['wptests_pt1_p1'], $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     551    }
     552
     553    /**
     554     * @ticket 48556
     555     * @ticket 13509
     556     */
     557    public function test_non_singular_queries_using_multiple_post_type_should_respect_post_type_read_private_posts_cap() {
     558        wp_set_current_user( 0 );
     559
     560        $post_ids = self::$post_ids;
     561
     562        $q = new WP_Query(
     563            array(
     564                'post_type'      => 'any',
     565                'posts_per_page' => -1,
     566            )
     567        );
     568
     569        $this->assertSameSets( array( $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     570
     571        wp_set_current_user( self::$subscriber_user_id );
     572        $GLOBALS['current_user']->add_cap( 'read_private_pt1s' );
     573
     574        $q = new WP_Query(
     575            array(
     576                'post_type'      => array( 'wptests_pt1', 'wptests_pt2' ),
     577                'posts_per_page' => -1,
     578            )
     579        );
     580
     581        $this->assertSameSets( array( $post_ids['wptests_pt1_p1'], $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     582    }
    460583}
Note: See TracChangeset for help on using the changeset viewer.