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.

git-svn-id: https://develop.svn.wordpress.org/trunk@49830 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Boone Gorges 2020-12-17 16:15:38 +00:00
parent a100f27473
commit f7ced48ad8
2 changed files with 240 additions and 38 deletions

View File

@ -2420,26 +2420,28 @@ class WP_Query {
$where .= $wpdb->prepare( " AND {$wpdb->posts}.ping_status = %s ", $q['ping_status'] );
}
$has_valid_post_types = true;
if ( 'any' === $post_type ) {
$in_search_post_types = get_post_types( array( 'exclude_from_search' => false ) );
if ( empty( $in_search_post_types ) ) {
$where .= ' AND 1=0 ';
$post_type_where = ' AND 1=0 ';
$has_valid_post_types = true;
} else {
$where .= " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')";
$post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')";
}
} elseif ( ! empty( $post_type ) && is_array( $post_type ) ) {
$where .= " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
$post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
} elseif ( ! empty( $post_type ) ) {
$where .= $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type );
$post_type_where = $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type );
$post_type_object = get_post_type_object( $post_type );
} elseif ( $this->is_attachment ) {
$where .= " AND {$wpdb->posts}.post_type = 'attachment'";
$post_type_where = " AND {$wpdb->posts}.post_type = 'attachment'";
$post_type_object = get_post_type_object( 'attachment' );
} elseif ( $this->is_page ) {
$where .= " AND {$wpdb->posts}.post_type = 'page'";
$post_type_where = " AND {$wpdb->posts}.post_type = 'page'";
$post_type_object = get_post_type_object( 'page' );
} else {
$where .= " AND {$wpdb->posts}.post_type = 'post'";
$post_type_where = " AND {$wpdb->posts}.post_type = 'post'";
$post_type_object = get_post_type_object( 'post' );
}
@ -2457,7 +2459,13 @@ class WP_Query {
$user_id = get_current_user_id();
$q_status = array();
if ( ! empty( $q['post_status'] ) ) {
if ( ! $has_valid_post_types ) {
// When there are no public post types, there's no need to assemble the post_status clause.
$where .= $post_type_where;
} elseif ( ! empty( $q['post_status'] ) ) {
$where .= $post_type_where;
$statuswheres = array();
$q_status = $q['post_status'];
if ( ! is_array( $q_status ) ) {
@ -2516,40 +2524,74 @@ class WP_Query {
if ( ! empty( $where_status ) ) {
$where .= " AND ($where_status)";
}
} elseif ( ! $this->is_singular ) {
$where .= " AND ({$wpdb->posts}.post_status = 'publish'";
// Add public states.
$public_states = get_post_stati( array( 'public' => true ) );
foreach ( (array) $public_states as $state ) {
if ( 'publish' === $state ) { // Publish is hard-coded above.
continue;
}
$where .= " OR {$wpdb->posts}.post_status = '$state'";
if ( 'any' === $post_type ) {
$queried_post_types = get_post_types( array( 'exclude_from_search' => false ) );
} elseif ( is_array( $post_type ) ) {
$queried_post_types = $post_type;
} elseif ( ! empty( $post_type ) ) {
$queried_post_types = array( $post_type );
} else {
$queried_post_types = array( 'post' );
}
if ( $this->is_admin ) {
// Add protected states that should show in the admin all list.
$admin_all_states = get_post_stati(
array(
'protected' => true,
'show_in_admin_all_list' => true,
)
);
foreach ( (array) $admin_all_states as $state ) {
$where .= " OR {$wpdb->posts}.post_status = '$state'";
if ( ! empty( $queried_post_types ) ) {
$status_type_clauses = array();
// Assemble a post_status clause for each post type.
foreach ( $queried_post_types as $queried_post_type ) {
$queried_post_type_object = get_post_type_object( $queried_post_type );
if ( ! $queried_post_type_object instanceof \WP_Post_Type ) {
continue;
}
$type_where = '(' . $wpdb->prepare( "{$wpdb->posts}.post_type = %s AND (", $queried_post_type );
// Public statuses.
$public_statuses = get_post_stati( array( 'public' => true ) );
$status_clauses = [];
foreach ( (array) $public_statuses as $public_status ) {
$status_clauses[] = "{$wpdb->posts}.post_status = '$public_status'";
}
$type_where .= implode( ' OR ', $status_clauses );
// Add protected states that should show in the admin all list.
if ( $this->is_admin ) {
$admin_all_statuses = get_post_stati(
array(
'protected' => true,
'show_in_admin_all_list' => true,
)
);
foreach ( (array) $admin_all_statuses as $admin_all_status ) {
$type_where .= " OR {$wpdb->posts}.post_status = '$admin_all_status'";
}
}
// Add private states that are visible to current user.
if ( is_user_logged_in() ) {
$read_private_cap = $queried_post_type_object->cap->read_private_posts;
$private_statuses = get_post_stati( array( 'private' => true ) );
foreach ( (array) $private_statuses as $private_status ) {
$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')";
}
}
$type_where .= '))';
$status_type_clauses[] = $type_where;
}
if ( ! empty( $status_type_clauses ) ) {
$where .= ' AND (' . implode( ' OR ', $status_type_clauses ) . ')';
}
} else {
$where .= ' AND 1=0 ';
}
if ( is_user_logged_in() ) {
// Add private states that are limited to viewing by the author of a post or someone who has caps to read private states.
$private_states = get_post_stati( array( 'private' => true ) );
foreach ( (array) $private_states as $state ) {
$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'";
}
}
$where .= ')';
} else {
$where .= $post_type_where;
}
/*

View File

@ -6,14 +6,16 @@
class Tests_Query_PostStatus extends WP_UnitTestCase {
public static $editor_user_id;
public static $author_user_id;
public static $subscriber_user_id;
public static $editor_private_post;
public static $author_private_post;
public static $editor_privatefoo_post;
public static $author_privatefoo_post;
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$editor_user_id = $factory->user->create( array( 'role' => 'editor' ) );
self::$author_user_id = $factory->user->create( array( 'role' => 'author' ) );
self::$editor_user_id = $factory->user->create( array( 'role' => 'editor' ) );
self::$author_user_id = $factory->user->create( array( 'role' => 'author' ) );
self::$subscriber_user_id = $factory->user->create( array( 'role' => 'subscriber' ) );
self::$editor_private_post = $factory->post->create(
array(
@ -457,4 +459,162 @@ class Tests_Query_PostStatus extends WP_UnitTestCase {
$this->assertContains( $p1, wp_list_pluck( $q->posts, 'ID' ) );
}
/**
* @ticket 48556
* @ticket 13509
*/
public function test_non_singular_queries_using_post_type_any_should_respect_post_type_read_private_posts_cap() {
register_post_type(
'wptests_pt1',
array(
'exclude_from_search' => false,
'capabilities' => [
'read_private_posts' => 'read_private_pt1s',
],
)
);
register_post_type(
'wptests_pt2',
array(
'exclude_from_search' => false,
)
);
$post_ids = array();
$post_ids['wptests_pt1_p1'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt1',
'post_status' => 'private',
'post_author' => self::$editor_user_id,
)
);
$post_ids['wptests_pt1_p2'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt1',
'post_status' => 'publish',
'post_author' => self::$editor_user_id,
)
);
$post_ids['wptests_pt2_p1'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt2',
'post_status' => 'private',
'post_author' => self::$editor_user_id,
)
);
$post_ids['wptests_pt2_p2'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt2',
'post_status' => 'publish',
'post_author' => self::$editor_user_id,
)
);
wp_set_current_user( 0 );
$q = new WP_Query(
array(
'post_type' => 'any',
)
);
$this->assertSameSets( array( $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
wp_set_current_user( self::$subscriber_user_id );
$GLOBALS['current_user']->add_cap( 'read_private_pt1s' );
$q = new WP_Query(
array(
'post_type' => 'any',
)
);
$this->assertSameSets( array( $post_ids['wptests_pt1_p1'], $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
}
/**
* @ticket 48556
* @ticket 13509
*/
public function test_non_singular_queries_using_multiple_post_type_should_respect_post_type_read_private_posts_cap() {
wp_set_current_user( 0 );
register_post_type(
'wptests_pt1',
array(
'exclude_from_search' => false,
'capabilities' => [
'read_private_posts' => 'read_private_pt1s',
],
)
);
register_post_type(
'wptests_pt2',
array(
'exclude_from_search' => false,
)
);
$post_ids = array();
$post_ids['wptests_pt1_p1'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt1',
'post_status' => 'private',
'post_author' => self::$editor_user_id,
)
);
$post_ids['wptests_pt1_p2'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt1',
'post_status' => 'publish',
'post_author' => self::$editor_user_id,
)
);
$post_ids['wptests_pt2_p1'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt2',
'post_status' => 'private',
'post_author' => self::$editor_user_id,
)
);
$post_ids['wptests_pt2_p2'] = $this->factory->post->create(
array(
'post_type' => 'wptests_pt2',
'post_status' => 'publish',
'post_author' => self::$editor_user_id,
)
);
$q = new WP_Query(
array(
'post_type' => 'any',
)
);
$this->assertSameSets( array( $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
$u = $this->factory->user->create();
wp_set_current_user( self::$subscriber_user_id );
$GLOBALS['current_user']->add_cap( 'read_private_pt1s' );
$q = new WP_Query(
array(
'post_type' => [ 'wptests_pt1', 'wptests_pt2' ],
)
);
$this->assertSameSets( array( $post_ids['wptests_pt1_p1'], $post_ids['wptests_pt1_p2'], $post_ids['wptests_pt2_p2'] ), wp_list_pluck( $q->posts, 'ID' ) );
}
}