Skip to content

Commit 06ef70d

Browse files
committed
Posts, Post Types: Improve wp_count_posts() query performance for users who cannot read_private_posts.
The query is refactored to use two subqueries which can leverage DB indexes. Props rcorrales, snehapatil02, sirlouen, sajjad67, pbearne, johnbillion, westonruter. Fixes #61097. git-svn-id: https://develop.svn.wordpress.org/trunk@60788 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 4b88704 commit 06ef70d

File tree

2 files changed

+117
-17
lines changed

2 files changed

+117
-17
lines changed

src/wp-includes/post.php

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3400,21 +3400,42 @@ function wp_count_posts( $type = 'post', $perm = '' ) {
34003400
return apply_filters( 'wp_count_posts', $counts, $type, $perm );
34013401
}
34023402

3403-
$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
3404-
3405-
if ( 'readable' === $perm && is_user_logged_in() ) {
3406-
$post_type_object = get_post_type_object( $type );
3407-
if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
3408-
$query .= $wpdb->prepare(
3409-
" AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
3410-
get_current_user_id()
3411-
);
3412-
}
3403+
if (
3404+
'readable' === $perm &&
3405+
is_user_logged_in() &&
3406+
! current_user_can( get_post_type_object( $type )->cap->read_private_posts )
3407+
) {
3408+
// Optimized query uses subqueries which can leverage DB indexes for better performance. See #61097.
3409+
$query = $wpdb->prepare(
3410+
"
3411+
SELECT post_status, COUNT(*) AS num_posts
3412+
FROM (
3413+
SELECT post_status
3414+
FROM {$wpdb->posts}
3415+
WHERE post_type = %s AND post_status != 'private'
3416+
UNION ALL
3417+
SELECT post_status
3418+
FROM {$wpdb->posts}
3419+
WHERE post_type = %s AND post_status = 'private' AND post_author = %d
3420+
) AS filtered_posts
3421+
",
3422+
$type,
3423+
$type,
3424+
get_current_user_id()
3425+
);
3426+
} else {
3427+
$query = $wpdb->prepare(
3428+
"
3429+
SELECT post_status, COUNT(*) AS num_posts
3430+
FROM {$wpdb->posts}
3431+
WHERE post_type = %s
3432+
",
3433+
$type
3434+
);
34133435
}
34143436

3415-
$query .= ' GROUP BY post_status';
3416-
3417-
$results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
3437+
$query .= ' GROUP BY post_status';
3438+
$results = (array) $wpdb->get_results( $query, ARRAY_A );
34183439
$counts = array_fill_keys( get_post_stati(), 0 );
34193440

34203441
foreach ( $results as $row ) {

tests/phpunit/tests/post.php

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,41 @@
66
* @group post
77
*/
88
class Tests_Post extends WP_UnitTestCase {
9-
protected static $editor_id;
109
protected static $grammarian_id;
1110

11+
protected static $user_ids = array(
12+
'administrator' => null,
13+
'editor' => null,
14+
'author' => null,
15+
'contributor' => null,
16+
);
17+
1218
private $post_ids = array();
1319

1420
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
15-
self::$editor_id = $factory->user->create( array( 'role' => 'editor' ) );
21+
22+
self::$user_ids = array(
23+
'administrator' => $factory->user->create(
24+
array(
25+
'role' => 'administrator',
26+
)
27+
),
28+
'editor' => $factory->user->create(
29+
array(
30+
'role' => 'editor',
31+
)
32+
),
33+
'author' => $factory->user->create(
34+
array(
35+
'role' => 'author',
36+
)
37+
),
38+
'contributor' => $factory->user->create(
39+
array(
40+
'role' => 'contributor',
41+
)
42+
),
43+
);
1644

1745
add_role(
1846
'grammarian',
@@ -142,6 +170,7 @@ public function test_parse_post_content_starting_with_nextpage_multi() {
142170

143171
/**
144172
* @ticket 24803
173+
* @covers ::wp_count_posts
145174
*/
146175
public function test_wp_count_posts() {
147176
$post_type = rand_str( 20 );
@@ -161,6 +190,49 @@ public function test_wp_count_posts() {
161190
$this->assertEquals( new stdClass(), $count );
162191
}
163192

193+
/**
194+
* Ensure `wp_count_posts()` in 'readable' context excludes private posts
195+
* authored by other users when the current user lacks the capability to
196+
* read private posts.
197+
*
198+
* @ticket 61097
199+
* @covers ::wp_count_posts
200+
*/
201+
public function test_wp_count_posts_readable_excludes_unreadable_private_posts() {
202+
$post_type = rand_str( 20 );
203+
register_post_type( $post_type );
204+
205+
$admin_user_id = self::$user_ids['administrator'];
206+
207+
self::factory()->post->create_many(
208+
5,
209+
array(
210+
'post_type' => $post_type,
211+
'post_status' => 'publish',
212+
'post_author' => $admin_user_id,
213+
)
214+
);
215+
216+
self::factory()->post->create_many(
217+
3,
218+
array(
219+
'post_type' => $post_type,
220+
'post_status' => 'private',
221+
'post_author' => $admin_user_id,
222+
)
223+
);
224+
225+
$current_user_id = self::$user_ids['author'];
226+
wp_set_current_user( $current_user_id );
227+
228+
$count = wp_count_posts( $post_type, 'readable' );
229+
$this->assertEquals( 5, $count->publish );
230+
_unregister_post_type( $post_type );
231+
}
232+
233+
/**
234+
* @covers ::wp_count_posts
235+
*/
164236
public function test_wp_count_posts_filtered() {
165237
$post_type = rand_str( 20 );
166238
register_post_type( $post_type );
@@ -186,6 +258,9 @@ public function filter_wp_count_posts( $counts ) {
186258
return $counts;
187259
}
188260

261+
/**
262+
* @covers ::wp_count_posts
263+
*/
189264
public function test_wp_count_posts_insert_invalidation() {
190265
$post_ids = self::factory()->post->create_many( 3 );
191266
$initial_counts = wp_count_posts();
@@ -206,6 +281,9 @@ public function test_wp_count_posts_insert_invalidation() {
206281
$this->assertNotEquals( $initial_counts->publish, $after_draft_counts->publish );
207282
}
208283

284+
/**
285+
* @covers ::wp_count_posts
286+
*/
209287
public function test_wp_count_posts_trash_invalidation() {
210288
$post_ids = self::factory()->post->create_many( 3 );
211289
$initial_counts = wp_count_posts();
@@ -226,6 +304,7 @@ public function test_wp_count_posts_trash_invalidation() {
226304

227305
/**
228306
* @ticket 49685
307+
* @covers ::wp_count_posts
229308
*/
230309
public function test_wp_count_posts_status_changes_visible() {
231310
self::factory()->post->create_many( 3 );
@@ -252,7 +331,7 @@ public function test_wp_tag_cloud_link_with_post_type() {
252331
$post = self::factory()->post->create( array( 'post_type' => $post_type ) );
253332
wp_set_object_terms( $post, 'foo', $tax );
254333

255-
wp_set_current_user( self::$editor_id );
334+
wp_set_current_user( self::$user_ids['editor'] );
256335

257336
$wp_tag_cloud = wp_tag_cloud(
258337
array(
@@ -305,7 +384,7 @@ public function test_utf8mb3_post_saves_with_emoji() {
305384
'post_excerpt' => 'foo😐bat',
306385
);
307386

308-
wp_set_current_user( self::$editor_id );
387+
wp_set_current_user( self::$user_ids['editor'] );
309388

310389
edit_post( $data );
311390

0 commit comments

Comments
 (0)