Skip to content

Commit 66b98b9

Browse files
committed
Users: Add caching to count_user_posts function
Introduced caching for the `count_user_posts` function to reduce redundant database queries. This ensures better performance by storing and reusing query results when possible. Additionally, sanitized and sorted the `$post_type` array to avoid invalid queries. Props spacedmonkey, peterwilsoncc, mamaduka, flixos90, johnjamesjacoby, swissspidy, dilip2615, johnregan3, wpgurudev, desrosj, milindmore22, Krstarica, dilipom13. Fixes #39242. git-svn-id: https://develop.svn.wordpress.org/trunk@59817 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 47217cb commit 66b98b9

File tree

2 files changed

+149
-1
lines changed

2 files changed

+149
-1
lines changed

src/wp-includes/user.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,9 +604,19 @@ function wp_validate_logged_in_cookie( $user_id ) {
604604
function count_user_posts( $userid, $post_type = 'post', $public_only = false ) {
605605
global $wpdb;
606606

607+
$post_type = array_unique( (array) $post_type );
608+
sort( $post_type );
609+
607610
$where = get_posts_by_author_sql( $post_type, true, $userid, $public_only );
611+
$query = "SELECT COUNT(*) FROM $wpdb->posts $where";
608612

609-
$count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
613+
$last_changed = wp_cache_get_last_changed( 'posts' );
614+
$cache_key = 'count_user_posts:' . md5( $query ) . ':' . $last_changed;
615+
$count = wp_cache_get( $cache_key, 'post-queries' );
616+
if ( false === $count ) {
617+
$count = $wpdb->get_var( $query );
618+
wp_cache_set( $cache_key, $count, 'post-queries' );
619+
}
610620

611621
/**
612622
* Filters the number of posts a user has written.

tests/phpunit/tests/user/countUserPosts.php

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,142 @@ public function test_count_user_posts_with_multiple_post_types() {
8989
public function test_count_user_posts_should_ignore_non_existent_post_types() {
9090
$this->assertSame( '4', count_user_posts( self::$user_id, array( 'foo', 'post' ) ) );
9191
}
92+
93+
/**
94+
* Post count be correct after reassigning posts to another user.
95+
*
96+
* @ticket 39242
97+
*/
98+
public function test_reassigning_users_posts_modifies_count() {
99+
// Create new user.
100+
$new_user_id = self::factory()->user->create(
101+
array(
102+
'role' => 'author',
103+
)
104+
);
105+
106+
// Prior to reassigning posts.
107+
$this->assertSame( '4', count_user_posts( self::$user_id ), 'Original user is expected to have a count of four posts prior to reassignment.' );
108+
$this->assertSame( '0', count_user_posts( $new_user_id ), 'New user is expected to have a count of zero posts prior to reassignment.' );
109+
110+
// Delete the original user, reassigning their posts to the new user.
111+
wp_delete_user( self::$user_id, $new_user_id );
112+
113+
// After reassigning posts.
114+
$this->assertSame( '0', count_user_posts( self::$user_id ), 'Original user is expected to have a count of zero posts following reassignment.' );
115+
$this->assertSame( '4', count_user_posts( $new_user_id ), 'New user is expected to have a count of four posts following reassignment.' );
116+
}
117+
118+
/**
119+
* Post count be correct after deleting user without reassigning posts.
120+
*
121+
* @ticket 39242
122+
*/
123+
public function test_post_count_retained_after_deleting_user_without_reassigning_posts() {
124+
$this->assertSame( '4', count_user_posts( self::$user_id ), 'User is expected to have a count of four posts prior to deletion.' );
125+
126+
// Delete the original user without reassigning their posts.
127+
wp_delete_user( self::$user_id );
128+
129+
$this->assertSame( '0', count_user_posts( self::$user_id ), 'User is expected to have a count of zero posts following deletion.' );
130+
}
131+
132+
/**
133+
* Post count should work for users that don't exist but have posts assigned.
134+
*
135+
* @ticket 39242
136+
*/
137+
public function test_count_user_posts_for_non_existent_user() {
138+
$next_user_id = self::$user_id + 1;
139+
140+
// Assign post to next user.
141+
self::factory()->post->create(
142+
array(
143+
'post_author' => $next_user_id,
144+
'post_type' => 'post',
145+
)
146+
);
147+
148+
$next_user_post_count = count_user_posts( $next_user_id );
149+
$this->assertSame( '1', $next_user_post_count, 'Non-existent user is expected to have count of one post.' );
150+
}
151+
152+
/**
153+
* Cached user count value should be accurate after user is created.
154+
*
155+
* @ticket 39242
156+
*/
157+
public function test_count_user_posts_for_user_created_after_being_assigned_posts() {
158+
global $wpdb;
159+
$next_user_id = (int) $wpdb->get_var( "SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$wpdb->users'" );
160+
161+
// Assign post to next user.
162+
self::factory()->post->create(
163+
array(
164+
'post_author' => $next_user_id,
165+
'post_type' => 'post',
166+
)
167+
);
168+
169+
// Cache the user count.
170+
count_user_posts( $next_user_id );
171+
172+
// Create user.
173+
$real_next_user_id = self::factory()->user->create(
174+
array(
175+
'role' => 'author',
176+
)
177+
);
178+
179+
$this->assertSame( $next_user_id, $real_next_user_id, 'User ID should match calculated value' );
180+
$this->assertSame( '1', count_user_posts( $next_user_id ), 'User is expected to have count of one post.' );
181+
}
182+
183+
/**
184+
* User count cache should be hit regardless of post type order.
185+
*
186+
* @ticket 39242
187+
*/
188+
public function test_cache_should_be_hit_regardless_of_post_type_order() {
189+
// Prime Cache.
190+
count_user_posts( self::$user_id, array( 'wptests_pt', 'post' ) );
191+
192+
$query_num_start = get_num_queries();
193+
count_user_posts( self::$user_id, array( 'post', 'wptests_pt' ) );
194+
$total_queries = get_num_queries() - $query_num_start;
195+
196+
$this->assertSame( 0, $total_queries, 'Cache should be hit regardless of post type order.' );
197+
}
198+
199+
/**
200+
* User count cache should be hit for string and array of post types.
201+
*
202+
* @ticket 39242
203+
*/
204+
public function test_cache_should_be_hit_for_string_and_array_equivalent_queries() {
205+
// Prime Cache.
206+
count_user_posts( self::$user_id, 'post' );
207+
208+
$query_num_start = get_num_queries();
209+
count_user_posts( self::$user_id, array( 'post' ) );
210+
$total_queries = get_num_queries() - $query_num_start;
211+
212+
$this->assertSame( 0, $total_queries, 'Cache should be hit for string and array equivalent post types.' );
213+
}
214+
215+
/**
216+
* User count cache should be hit for array duplicates and equivalent queries.
217+
*
218+
* @ticket 39242
219+
*/
220+
public function test_cache_should_be_hit_for_and_array_duplicates_equivalent_queries() {
221+
// Prime Cache
222+
count_user_posts( self::$user_id, array( 'post', 'post', 'post' ) );
223+
224+
$query_num_start = get_num_queries();
225+
count_user_posts( self::$user_id, array( 'post' ) );
226+
$total_queries = get_num_queries() - $query_num_start;
227+
228+
$this->assertSame( 0, $total_queries, 'Cache is expected to be hit for equivalent queries with duplicate post types' );
229+
}
92230
}

0 commit comments

Comments
 (0)