Make WordPress Core

Changeset 51276


Ignore:
Timestamp:
06/30/2021 04:59:10 AM (5 years 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.