diff --git a/src/wp-admin/includes/user.php b/src/wp-admin/includes/user.php index abed2d20157c5..8198bd464bb3d 100644 --- a/src/wp-admin/includes/user.php +++ b/src/wp-admin/includes/user.php @@ -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. * diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index ae654605e8f4b..bce9637c6191b 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -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 ); + // Post meta. add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' ); add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' ); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index eb90e762f5663..51fa08361f04e 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -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 ) ); /** @@ -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; } @@ -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 ); } @@ -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 ); } diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index f60dbe5d3fae2..1b1d8ef7f6987 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -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. * @@ -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. @@ -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 ); } /** @@ -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 ) { diff --git a/tests/phpunit/tests/user.php b/tests/phpunit/tests/user.php index 884200530f1d9..ab86690acfd19 100644 --- a/tests/phpunit/tests/user.php +++ b/tests/phpunit/tests/user.php @@ -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 ] ); } /** diff --git a/tests/phpunit/tests/user/countUserPosts.php b/tests/phpunit/tests/user/countUserPosts.php index dbc94f418e7e6..ebf9722ab2dcb 100644 --- a/tests/phpunit/tests/user/countUserPosts.php +++ b/tests/phpunit/tests/user/countUserPosts.php @@ -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' ) ) ); } }