From df151c2ef80bc5b653e839d78c5489645e6c04a9 Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Sat, 28 Jun 2025 12:34:42 +0800 Subject: [PATCH 1/8] Performance: Add better caching on count_many_users_posts() --- src/wp-includes/user.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 60da3d283186f..4aedd94659137 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -682,14 +682,21 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal return $pre; } - $userlist = implode( ',', array_map( 'absint', $users ) ); - $where = get_posts_by_author_sql( $post_type, true, null, $public_only ); + $userlist = implode( ',', array_map( 'absint', $users ) ); + $cache_key = "count_many_users_posts_{$post_type}_{ str_replace( ',', '_', $userlist ) }"; + $cache_group = $public_only ? 'count_many_users_posts_public' : 'count_many_users_posts'; + $count = wp_cache_get( $cache_key, $cache_group ); - $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N ); + if ( false === $count ) { + $where = get_posts_by_author_sql( $post_type, true, null, $public_only ); + $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N ); + + $count = array_fill_keys( $users, 0 ); + foreach ( $result as $row ) { + $count[ $row[0] ] = $row[1]; + } - $count = array_fill_keys( $users, 0 ); - foreach ( $result as $row ) { - $count[ $row[0] ] = $row[1]; + wp_cache_add( $cache_key, $count, $cache_group ); } return $count; From ecc31d6568b1af4b4559d2dc1f2fc5cf19b0c44b Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Sat, 28 Jun 2025 13:04:35 +0800 Subject: [PATCH 2/8] Fix: Better setting up cache key and add 1 hour expiration on the OC --- src/wp-includes/user.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 4aedd94659137..7d16075861b95 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -683,7 +683,7 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal } $userlist = implode( ',', array_map( 'absint', $users ) ); - $cache_key = "count_many_users_posts_{$post_type}_{ str_replace( ',', '_', $userlist ) }"; + $cache_key = "count_many_users_posts_{$post_type}_" . str_replace( ',', '_', $userlist ); $cache_group = $public_only ? 'count_many_users_posts_public' : 'count_many_users_posts'; $count = wp_cache_get( $cache_key, $cache_group ); @@ -696,7 +696,7 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal $count[ $row[0] ] = $row[1]; } - wp_cache_add( $cache_key, $count, $cache_group ); + wp_cache_add( $cache_key, $count, $cache_group, HOUR_IN_SECONDS ); } return $count; From 3ad234d1391db74e833a2d5c0713e73cf8e91fcc Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Sat, 28 Jun 2025 16:18:52 +0800 Subject: [PATCH 3/8] Include and current user ID in count_many_users_posts cache key --- src/wp-includes/user.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 7d16075861b95..c429fb2aeb56e 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -683,7 +683,7 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal } $userlist = implode( ',', array_map( 'absint', $users ) ); - $cache_key = "count_many_users_posts_{$post_type}_" . str_replace( ',', '_', $userlist ); + $cache_key = "count_many_users_posts_{$post_type}_{$public_only}_" . str_replace( ',', '_', $userlist ) . '_' . get_current_user_id(); $cache_group = $public_only ? 'count_many_users_posts_public' : 'count_many_users_posts'; $count = wp_cache_get( $cache_key, $cache_group ); From ed4536ab6c70b096023b05e744dac5325c398199 Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Thu, 18 Sep 2025 10:08:51 +0800 Subject: [PATCH 4/8] Feedback: Make users array clean before building cache key --- src/wp-includes/user.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index c429fb2aeb56e..d9380e84ba9a9 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -682,8 +682,12 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal return $pre; } - $userlist = implode( ',', array_map( 'absint', $users ) ); - $cache_key = "count_many_users_posts_{$post_type}_{$public_only}_" . str_replace( ',', '_', $userlist ) . '_' . get_current_user_id(); + // Cleanup the users array. Remove duplicates and sort for consistent ordering. + $users = array_unique( array_filter( array_map( 'absint', (array) $users ) ) ); + sort( $users ); + + $userlist = implode( '_', $users ); + $cache_key = "count_many_users_posts_{$post_type}_{$public_only}_{$userlist}_" . get_current_user_id(); $cache_group = $public_only ? 'count_many_users_posts_public' : 'count_many_users_posts'; $count = wp_cache_get( $cache_key, $cache_group ); From 6bc7289f6f21e5710c400b493c21a216bda7f0ea Mon Sep 17 00:00:00 2001 From: "Rolly G. Bueno Jr." <16076280+rollybueno@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:32:58 +0800 Subject: [PATCH 5/8] $userlist is used in the SQL query's IN clause so needs to be comma separated. Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- src/wp-includes/user.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index d9380e84ba9a9..cd991a5272a3c 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -686,7 +686,7 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal $users = array_unique( array_filter( array_map( 'absint', (array) $users ) ) ); sort( $users ); - $userlist = implode( '_', $users ); + $userlist = implode( ',', $users ); $cache_key = "count_many_users_posts_{$post_type}_{$public_only}_{$userlist}_" . get_current_user_id(); $cache_group = $public_only ? 'count_many_users_posts_public' : 'count_many_users_posts'; $count = wp_cache_get( $cache_key, $cache_group ); From 23f1853be8b2c627ef19d5d4a4db8779df0ba968 Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Sat, 20 Sep 2025 18:16:28 +0800 Subject: [PATCH 6/8] Fix: Use salted cache functions for count_many_users_posts() --- src/wp-includes/user.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index ace3309267e06..e000e1d7f7393 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -687,9 +687,9 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal sort( $users ); $userlist = implode( ',', $users ); - $cache_key = "count_many_users_posts_{$post_type}_{$public_only}_{$userlist}_" . get_current_user_id(); - $cache_group = $public_only ? 'count_many_users_posts_public' : 'count_many_users_posts'; - $count = wp_cache_get( $cache_key, $cache_group ); + $cache_key = 'count_many_users_posts:' . md5( implode( ':', array( $post_type, $public_only ? 'public' : 'public|private', $userlist ) ) ); + $cache_salts = array( wp_cache_get_last_changed( 'posts' ), wp_cache_get_last_changed( 'users' ) ); + $count = wp_cache_get_salted( $cache_key, 'user-queries', $cache_salts ); if ( false === $count ) { $where = get_posts_by_author_sql( $post_type, true, null, $public_only ); @@ -700,7 +700,7 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal $count[ $row[0] ] = $row[1]; } - wp_cache_add( $cache_key, $count, $cache_group, HOUR_IN_SECONDS ); + wp_cache_set_salted( $cache_key, $count, 'user-queries', $cache_salts, HOUR_IN_SECONDS ); } return $count; From 2d1d98df2d0054f0bdaece9ea77bdc376ab1f47b Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Sat, 20 Sep 2025 19:00:23 +0800 Subject: [PATCH 7/8] Test Unit: Add comprehensive test unit --- tests/phpunit/tests/user.php | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/phpunit/tests/user.php b/tests/phpunit/tests/user.php index 535004ee560a8..95d053b915f30 100644 --- a/tests/phpunit/tests/user.php +++ b/tests/phpunit/tests/user.php @@ -604,6 +604,45 @@ public function test_count_many_users_posts() { $this->assertSame( '1', $counts[ $user_id_b ] ); } + /** + * Test salted cache functionality for count_many_users_posts(). + * + * @ticket 63405 + */ + public function test_count_many_users_posts_salted_cache() { + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + $editor_id = self::factory()->user->create( array( 'role' => 'editor' ) ); + + // Create posts for both users + self::factory()->post->create( array( 'post_author' => $author_id ) ); + self::factory()->post->create( array( 'post_author' => $editor_id ) ); + + // Test cache hit + $counts1 = count_many_users_posts( array( $author_id, $editor_id ), 'post', false ); + $counts2 = count_many_users_posts( array( $author_id, $editor_id ), 'post', false ); + $this->assertSame( $counts1, $counts2 ); + $this->assertSame( '1', $counts1[ $author_id ] ); + $this->assertSame( '1', $counts1[ $editor_id ] ); + + // Test cache invalidation - create new post for author only + self::factory()->post->create( array( 'post_author' => $author_id ) ); + $counts3 = count_many_users_posts( array( $author_id, $editor_id ), 'post', false ); + $this->assertSame( '2', $counts3[ $author_id ] ); + $this->assertSame( '1', $counts3[ $editor_id ] ); + + // Test different post types use different cache keys + self::factory()->post->create( + array( + 'post_author' => $author_id, + 'post_type' => 'page', + ) + ); + $counts_posts = count_many_users_posts( array( $author_id ), 'post', false ); + $counts_pages = count_many_users_posts( array( $author_id ), 'page', false ); + $this->assertSame( '2', $counts_posts[ $author_id ] ); + $this->assertSame( '1', $counts_pages[ $author_id ] ); + } + /** * @ticket 22858 */ From d63231fb2d5e9084ae562578ff780f581cf0862d Mon Sep 17 00:00:00 2001 From: Rolly Bueno Date: Sat, 20 Sep 2025 19:20:45 +0800 Subject: [PATCH 8/8] Add current user context to count_many_users_posts() cache key --- src/wp-includes/user.php | 2 +- tests/phpunit/tests/user.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index e000e1d7f7393..c81c6aae7d96c 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -687,7 +687,7 @@ function count_many_users_posts( $users, $post_type = 'post', $public_only = fal sort( $users ); $userlist = implode( ',', $users ); - $cache_key = 'count_many_users_posts:' . md5( implode( ':', array( $post_type, $public_only ? 'public' : 'public|private', $userlist ) ) ); + $cache_key = 'count_many_users_posts:' . md5( implode( ':', array( $post_type, $public_only ? 'public' : 'public|private', $userlist, get_current_user_id() ) ) ); $cache_salts = array( wp_cache_get_last_changed( 'posts' ), wp_cache_get_last_changed( 'users' ) ); $count = wp_cache_get_salted( $cache_key, 'user-queries', $cache_salts ); diff --git a/tests/phpunit/tests/user.php b/tests/phpunit/tests/user.php index 95d053b915f30..ea25853796a3f 100644 --- a/tests/phpunit/tests/user.php +++ b/tests/phpunit/tests/user.php @@ -610,6 +610,9 @@ public function test_count_many_users_posts() { * @ticket 63405 */ public function test_count_many_users_posts_salted_cache() { + // Clear any existing cache + wp_cache_flush(); + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); $editor_id = self::factory()->user->create( array( 'role' => 'editor' ) );