Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache count_user_posts() #7993

Open
wants to merge 8 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/wp-admin/includes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ function wp_delete_user( $id, $reassign = null ) {

clean_user_cache( $user );

foreach ( get_post_types() as $post_type ) {
clear_user_posts_count_cache( $user->ID, $post_type );
}

/**
* Fires immediately after a user is deleted from the site.
*
Expand Down
4 changes: 4 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@
add_action( $action, 'wp_maybe_update_user_counts', 10, 0 );
}

// User post counts.
add_action( 'attachment_updated', '_clear_user_posts_count_cache_on_author_change', 10, 3 );
add_action( 'post_updated', '_clear_user_posts_count_cache_on_author_change', 10, 3 );
Comment on lines +120 to +121

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider using clean_post_cache. Lots of plugins do manaul sql updates and then call this function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did, but I think it runs too often. No reason to clear the cache unless the post status or author changes, or if the post is deleted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I can add some conditions though

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at this list, there are a number of popular plugins that use that function.

https://wpdirectory.net/search/01JEXQH4XW97YT5CZM38TKDPJB

Including

  • Yoast SEO
  • WooCommerce
  • Jetpack
  • Elementor

and other very popular plugins. Sadly I think we HAVE to use this hook.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still only half of the equation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still only half of the equation.

Can you expand of this, I am not sure I know what you mean?


// Post meta.
add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' );
add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' );
Expand Down
8 changes: 8 additions & 0 deletions src/wp-includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -3819,6 +3819,8 @@ function wp_delete_post( $post_id = 0, $force_delete = false ) {
}
}

clear_user_posts_count_cache( $post->post_author, $post->post_type );

wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) );

/**
Expand Down Expand Up @@ -4983,6 +4985,8 @@ function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true )
do_action( 'add_attachment', $post_id );
}

clear_user_posts_count_cache( $post_author, $post_type );

return $post_id;
}

Expand Down Expand Up @@ -5070,6 +5074,8 @@ function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true )
*/
do_action( 'wp_insert_post', $post_id, $post, $update );

clear_user_posts_count_cache( $post_author, $post_type );

if ( $fire_after_hooks ) {
wp_after_insert_post( $post, $update, $post_before );
}
Expand Down Expand Up @@ -5236,6 +5242,8 @@ function wp_publish_post( $post ) {
/** This action is documented in wp-includes/post.php */
do_action( 'wp_insert_post', $post->ID, $post, true );

clear_user_posts_count_cache( $post->post_author, $post->post_type );

wp_after_insert_post( $post, true, $post_before );
}

Expand Down
92 changes: 73 additions & 19 deletions src/wp-includes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,45 @@ function wp_validate_logged_in_cookie( $user_id ) {
return wp_validate_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' );
}

/**
* Clears the cached posts count for user for given post type.
*
* @since 6.8.0
*
* @param int $user_id User ID.
* @param string $post_type Post type.
*/
function clear_user_posts_count_cache( $user_id, $post_type ) {
$cache_key = "count_user_{$post_type}_{$user_id}";
$cache_groups = array(
'user_posts_count_public',
'user_posts_count',
);

foreach ( $cache_groups as $cache_group ) {
wp_cache_delete( $cache_key, $cache_group );
}
}

/**
* Clears the cached posts count for both users if a post's author changes.
*
* @since 6.8.0
* @access private
*
* @param int $post_id Post ID.
* @param WP_Post $post_after Post object following the update.
* @param WP_Post $post_before Post object before the update.
*/
function _clear_user_posts_count_cache_on_author_change( $post_id, $post_after, $post_before ) {
if ( $post_after->post_author !== $post_before->post_author ) {
$post_type = get_post_type( $post_id );

clear_user_posts_count_cache( $post_after->post_author, $post_type );
clear_user_posts_count_cache( $post_before->post_author, $post_type );
}
}

/**
* Gets the number of posts a user has written.
*
Expand All @@ -576,17 +615,38 @@ function wp_validate_logged_in_cookie( $user_id ) {
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $userid User ID.
* @param array|string $post_type Optional. Single post type or array of post types to count the number of posts for. Default 'post'.
* @param int $user_id User ID.
* @param array|string $post_types Optional. Single post type or array of post types to count the number of posts for. Default 'post'.
* @param bool $public_only Optional. Whether to only return counts for public posts. Default false.
* @return string Number of posts the user has written in this post type.
* @return int Number of posts the user has written in this post type.
*/
function count_user_posts( $userid, $post_type = 'post', $public_only = false ) {
function count_user_posts( $user_id, $post_types = 'post', $public_only = false ) {
global $wpdb;

$where = get_posts_by_author_sql( $post_type, true, $userid, $public_only );
if ( is_string( $post_types ) ) {
$post_types = array( $post_types );
}

$cache_group = $public_only ? 'user_posts_count_public' : 'user_posts_count';

$count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
$count = 0;

foreach ( $post_types as $post_type ) {
$where = get_posts_by_author_sql( $post_type, true, $user_id, $public_only );
$cache_key = "count_user_{$post_type}_{$user_id}";

// Try to get count from cache.
$post_type_count = wp_cache_get( $cache_key, $cache_group );

// If cache is empty, query the database.
if ( false === $post_type_count ) {
$post_type_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" ) );

wp_cache_add( $cache_key, $post_type_count, $cache_group );
}

$count += $post_type_count;
}

/**
* Filters the number of posts a user has written.
Expand All @@ -595,12 +655,12 @@ function count_user_posts( $userid, $post_type = 'post', $public_only = false )
* @since 4.1.0 Added `$post_type` argument.
* @since 4.3.1 Added `$public_only` argument.
*
* @param int $count The user's post count.
* @param int $userid User ID.
* @param string|array $post_type Single post type or array of post types to count the number of posts for.
* @param bool $public_only Whether to limit counted posts to public posts.
* @param int $count The user's post count.
* @param int $user_id User ID.
* @param array $post_type Array of post types to count the number of posts for.
* @param bool $public_only Whether to limit counted posts to public posts.
*/
return apply_filters( 'get_usernumposts', $count, $userid, $post_type, $public_only );
return apply_filters( 'get_usernumposts', $count, $user_id, $post_type, $public_only );
}

/**
Expand All @@ -616,19 +676,13 @@ function count_user_posts( $userid, $post_type = 'post', $public_only = false )
* @return string[] Amount of posts each user has written, as strings, keyed by user ID.
*/
function count_many_users_posts( $users, $post_type = 'post', $public_only = false ) {
global $wpdb;

$count = array();
if ( empty( $users ) || ! is_array( $users ) ) {
return $count;
}

$userlist = implode( ',', array_map( 'absint', $users ) );
$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 );
foreach ( $result as $row ) {
$count[ $row[0] ] = $row[1];
foreach ( $users as $user_id ) {
$count[ $user_id ] = count_user_posts( $user_id, $post_type, $public_only );
}

foreach ( $users as $id ) {
Expand Down
16 changes: 8 additions & 8 deletions tests/phpunit/tests/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -559,21 +559,21 @@ public function test_count_many_users_posts() {

wp_set_current_user( self::$author_id );
$counts = count_many_users_posts( array( self::$author_id, $user_id_b ), 'post', false );
$this->assertSame( '1', $counts[ self::$author_id ] );
$this->assertSame( '1', $counts[ $user_id_b ] );
$this->assertSame( 1, $counts[ self::$author_id ] );
$this->assertSame( 1, $counts[ $user_id_b ] );

$counts = count_many_users_posts( array( self::$author_id, $user_id_b ), 'post', true );
$this->assertSame( '1', $counts[ self::$author_id ] );
$this->assertSame( '1', $counts[ $user_id_b ] );
$this->assertSame( 1, $counts[ self::$author_id ] );
$this->assertSame( 1, $counts[ $user_id_b ] );

wp_set_current_user( $user_id_b );
$counts = count_many_users_posts( array( self::$author_id, $user_id_b ), 'post', false );
$this->assertSame( '1', $counts[ self::$author_id ] );
$this->assertSame( '2', $counts[ $user_id_b ] );
$this->assertSame( 1, $counts[ self::$author_id ] );
$this->assertSame( 2, $counts[ $user_id_b ] );

$counts = count_many_users_posts( array( self::$author_id, $user_id_b ), 'post', true );
$this->assertSame( '1', $counts[ self::$author_id ] );
$this->assertSame( '1', $counts[ $user_id_b ] );
$this->assertSame( 1, $counts[ self::$author_id ] );
$this->assertSame( 1, $counts[ $user_id_b ] );
}

/**
Expand Down
10 changes: 5 additions & 5 deletions tests/phpunit/tests/user/countUserPosts.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,34 +59,34 @@ public function set_up() {
}

public function test_count_user_posts_post_type_should_default_to_post() {
$this->assertSame( '4', count_user_posts( self::$user_id ) );
$this->assertSame( 4, count_user_posts( self::$user_id ) );
}

/**
* @ticket 21364
*/
public function test_count_user_posts_post_type_post() {
$this->assertSame( '4', count_user_posts( self::$user_id, 'post' ) );
$this->assertSame( 4, count_user_posts( self::$user_id, 'post' ) );
}

/**
* @ticket 21364
*/
public function test_count_user_posts_post_type_cpt() {
$this->assertSame( '3', count_user_posts( self::$user_id, 'wptests_pt' ) );
$this->assertSame( 3, count_user_posts( self::$user_id, 'wptests_pt' ) );
}

/**
* @ticket 32243
*/
public function test_count_user_posts_with_multiple_post_types() {
$this->assertSame( '7', count_user_posts( self::$user_id, array( 'wptests_pt', 'post' ) ) );
$this->assertSame( 7, count_user_posts( self::$user_id, array( 'wptests_pt', 'post' ) ) );
}

/**
* @ticket 32243
*/
public function test_count_user_posts_should_ignore_non_existent_post_types() {
$this->assertSame( '4', count_user_posts( self::$user_id, array( 'foo', 'post' ) ) );
$this->assertSame( 4, count_user_posts( self::$user_id, array( 'foo', 'post' ) ) );
}
}
Loading