Make WordPress Core

Changeset 49830

Timestamp:
12/17/2020 04:15:38 PM (4 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
    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                $= " 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            $= " 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            $= $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            $= " 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            $= " 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            $= " 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
    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
     465
     466
     467
     468
     469
     470
     471
     472
     473
     474
     475
     476
     477
     478
     479
     480
     481
     482
     483
     484
     485
     486
     487
     488
     489
     490
     491
     492
     493
     494
     495
     496
     497
     498
     499
     500
     501
     502
     503
     504
     505
     506
     507
     508
     509
     510
     511
     512
     513
     514
     515
     516
     517
     518
     519
     520
     521
     522
     523
     524
     525
     526
     527
     528
     529
     530
     531
     532
     533
     534
     535
     536
     537
     538
     539
     540
     541
     542
     543
     544
     545
     546
     547
     548
     549
     550
     551
     552
     553
     554
     555
     556
     557
     558
     559
     560
     561
     562
     563
     564
     565
     566
     567
     568
     569
     570
     571
     572
     573
     574
     575
     576
     577
     578
     579
     580
     581
     582
     583
     584
     585
     586
     587
     588
     589
     590
     591
     592
     593
     594
     595
     596
     597
     598
     599
     600
     601
     602
     603
     604
     605
     606
     607
     608
     609
     610
     611
     612
     613
     614
     615
     616
     617
     618
     619
    460620}
Note: See TracChangeset for help on using the changeset viewer.