Make WordPress Core

Changeset 49830


Ignore:
Timestamp:
12/17/2020 04:15:38 PM (6 years ago)
Author:
boonebgorges
Message:

Query: Respect post-type specific capabilities when querying for multiple post types.

After this change, the relevant read_private_posts capability is checked for
each queried post type. This ensures that private posts appear in search and
archive queries for users who have the ability to view those posts.

Props leogermani.

Fixes #13509, #48968, #48556.

Location:
trunk
Files:
2 edited

Legend:

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

    r49792 r49830  
    24212421                }
    24222422
     2423                $has_valid_post_types = true;
    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                                $has_valid_post_types = 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
     2463                if ( ! $has_valid_post_types ) {
     2464                        // When there are no public post types, there's no need to assemble the post_status clause.
     2465                        $where .= $post_type_where;
     2466                } elseif ( ! empty( $q['post_status'] ) ) {
     2467                        $where .= $post_type_where;
     2468
    24612469                        $statuswheres = array();
    24622470                        $q_status     = $q['post_status'];
     
    25172525                                $where .= " AND ($where_status)";
    25182526                        }
     2527
    25192528                } 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 .= ')';
     2529                        if ( 'any' === $post_type ) {
     2530                                $queried_post_types = get_post_types( array( 'exclude_from_search' => false ) );
     2531                        } elseif ( is_array( $post_type ) ) {
     2532                                $queried_post_types = $post_type;
     2533                        } elseif ( ! empty( $post_type ) ) {
     2534                                $queried_post_types = array( $post_type );
     2535                        } else {
     2536                                $queried_post_types = array( 'post' );
     2537                        }
     2538
     2539                        if ( ! empty( $queried_post_types ) ) {
     2540                                $status_type_clauses = array();
     2541
     2542                                // Assemble a post_status clause for each post type.
     2543                                foreach ( $queried_post_types as $queried_post_type ) {
     2544                                        $queried_post_type_object = get_post_type_object( $queried_post_type );
     2545                                        if ( ! $queried_post_type_object instanceof \WP_Post_Type ) {
     2546                                                continue;
     2547                                        }
     2548
     2549                                        $type_where = '(' . $wpdb->prepare( "{$wpdb->posts}.post_type = %s AND (", $queried_post_type );
     2550
     2551                                        // Public statuses.
     2552                                        $public_statuses = get_post_stati( array( 'public' => true ) );
     2553                                        $status_clauses  = [];
     2554                                        foreach ( (array) $public_statuses as $public_status ) {
     2555                                                $status_clauses[] = "{$wpdb->posts}.post_status = '$public_status'";
     2556                                        }
     2557                                        $type_where .= implode( ' OR ', $status_clauses );
     2558
     2559                                        // Add protected states that should show in the admin all list.
     2560                                        if ( $this->is_admin ) {
     2561                                                $admin_all_statuses = get_post_stati(
     2562                                                        array(
     2563                                                                'protected'              => true,
     2564                                                                'show_in_admin_all_list' => true,
     2565                                                        )
     2566                                                );
     2567                                                foreach ( (array) $admin_all_statuses as $admin_all_status ) {
     2568                                                        $type_where .= " OR {$wpdb->posts}.post_status = '$admin_all_status'";
     2569                                                }
     2570                                        }
     2571
     2572                                        // Add private states that are visible to current user.
     2573                                        if ( is_user_logged_in() ) {
     2574                                                $read_private_cap = $queried_post_type_object->cap->read_private_posts;
     2575                                                $private_statuses = get_post_stati( array( 'private' => true ) );
     2576                                                foreach ( (array) $private_statuses as $private_status ) {
     2577                                                        $type_where .= current_user_can( $read_private_cap ) ? " OR {$wpdb->posts}.post_status = '$private_status'" : " OR ({$wpdb->posts}.post_author = $user_id AND {$wpdb->posts}.post_status = '$private_status')";
     2578                                                }
     2579                                        }
     2580
     2581                                        $type_where .= '))';
     2582
     2583                                        $status_type_clauses[] = $type_where;
     2584                                }
     2585
     2586                                if ( ! empty( $status_type_clauses ) ) {
     2587                                        $where .= ' AND (' . implode( ' OR ', $status_type_clauses ) . ')';
     2588                                }
     2589                        } else {
     2590                                $where .= ' AND 1=0 ';
     2591                        }
     2592
     2593                } else {
     2594                        $where .= $post_type_where;
    25532595                }
    25542596
  • trunk/tests/phpunit/tests/query/postStatus.php

    r49603 r49830  
    77        public static $editor_user_id;
    88        public static $author_user_id;
     9        public static $subscriber_user_id;
    910        public static $editor_private_post;
    1011        public static $author_private_post;
     
    1314
    1415        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' ) );
     16                self::$editor_user_id     = $factory->user->create( array( 'role' => 'editor' ) );
     17                self::$author_user_id     = $factory->user->create( array( 'role' => 'author' ) );
     18                self::$subscriber_user_id = $factory->user->create( array( 'role' => 'subscriber' ) );
    1719
    1820                self::$editor_private_post = $factory->post->create(
     
    458460                $this->assertContains( $p1, wp_list_pluck( $q->posts, 'ID' ) );
    459461        }
     462
     463        /**
     464         * @ticket 48556
     465         * @ticket 13509
     466         */
     467        public function test_non_singular_queries_using_post_type_any_should_respect_post_type_read_private_posts_cap() {
     468                register_post_type(
     469                        'wptests_pt1',
     470                        array(
     471                                'exclude_from_search' => false,
     472                                'capabilities'        => [
     473                                        'read_private_posts' => 'read_private_pt1s',
     474                                ],
     475                        )
     476                );
     477
     478                register_post_type(
     479                        'wptests_pt2',
     480                        array(
     481                                'exclude_from_search' => false,
     482                        )
     483                );
     484
     485                $post_ids = array();
     486
     487                $post_ids['wptests_pt1_p1'] = $this->factory->post->create(
     488                        array(
     489                                'post_type'   => 'wptests_pt1',
     490                                'post_status' => 'private',
     491                                'post_author' => self::$editor_user_id,
     492                        )
     493                );
     494
     495                $post_ids['wptests_pt1_p2'] = $this->factory->post->create(
     496                        array(
     497                                'post_type'   => 'wptests_pt1',
     498                                'post_status' => 'publish',
     499                                'post_author' => self::$editor_user_id,
     500                        )
     501                );
     502
     503                $post_ids['wptests_pt2_p1'] = $this->factory->post->create(
     504                        array(
     505                                'post_type'   => 'wptests_pt2',
     506                                'post_status' => 'private',
     507                                'post_author' => self::$editor_user_id,
     508                        )
     509                );
     510
     511                $post_ids['wptests_pt2_p2'] = $this->factory->post->create(
     512                        array(
     513                                'post_type'   => 'wptests_pt2',
     514                                'post_status' => 'publish',
     515                                'post_author' => self::$editor_user_id,
     516                        )
     517                );
     518
     519                wp_set_current_user( 0 );
     520
     521                $q = new WP_Query(
     522                        array(
     523                                'post_type' => 'any',
     524                        )
     525                );
     526
     527                $this->assertSameSets( array( $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     528
     529                wp_set_current_user( self::$subscriber_user_id );
     530                $GLOBALS['current_user']->add_cap( 'read_private_pt1s' );
     531
     532                $q = new WP_Query(
     533                        array(
     534                                'post_type' => 'any',
     535                        )
     536                );
     537
     538                $this->assertSameSets( array( $post_ids['wptests_pt1_p1'], $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     539        }
     540
     541        /**
     542         * @ticket 48556
     543         * @ticket 13509
     544         */
     545        public function test_non_singular_queries_using_multiple_post_type_should_respect_post_type_read_private_posts_cap() {
     546                wp_set_current_user( 0 );
     547
     548                register_post_type(
     549                        'wptests_pt1',
     550                        array(
     551                                'exclude_from_search' => false,
     552                                'capabilities'        => [
     553                                        'read_private_posts' => 'read_private_pt1s',
     554                                ],
     555                        )
     556                );
     557
     558                register_post_type(
     559                        'wptests_pt2',
     560                        array(
     561                                'exclude_from_search' => false,
     562                        )
     563                );
     564
     565                $post_ids = array();
     566
     567                $post_ids['wptests_pt1_p1'] = $this->factory->post->create(
     568                        array(
     569                                'post_type'   => 'wptests_pt1',
     570                                'post_status' => 'private',
     571                                'post_author' => self::$editor_user_id,
     572                        )
     573                );
     574
     575                $post_ids['wptests_pt1_p2'] = $this->factory->post->create(
     576                        array(
     577                                'post_type'   => 'wptests_pt1',
     578                                'post_status' => 'publish',
     579                                'post_author' => self::$editor_user_id,
     580                        )
     581                );
     582
     583                $post_ids['wptests_pt2_p1'] = $this->factory->post->create(
     584                        array(
     585                                'post_type'   => 'wptests_pt2',
     586                                'post_status' => 'private',
     587                                'post_author' => self::$editor_user_id,
     588                        )
     589                );
     590
     591                $post_ids['wptests_pt2_p2'] = $this->factory->post->create(
     592                        array(
     593                                'post_type'   => 'wptests_pt2',
     594                                'post_status' => 'publish',
     595                                'post_author' => self::$editor_user_id,
     596                        )
     597                );
     598
     599                $q = new WP_Query(
     600                        array(
     601                                'post_type' => 'any',
     602                        )
     603                );
     604
     605                $this->assertSameSets( array( $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     606
     607                $u = $this->factory->user->create();
     608
     609                wp_set_current_user( self::$subscriber_user_id );
     610                $GLOBALS['current_user']->add_cap( 'read_private_pt1s' );
     611
     612                $q = new WP_Query(
     613                        array(
     614                                'post_type' => [ 'wptests_pt1', 'wptests_pt2' ],
     615                        )
     616                );
     617
     618                $this->assertSameSets( array( $post_ids['wptests_pt1_p1'], $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
     619        }
    460620}
Note: See TracChangeset for help on using the changeset viewer.