From 28bab15d16b891539642821a392abbc0781d5a17 Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Mon, 3 Feb 2014 19:41:40 +0000 Subject: [PATCH] Properly invalidate the cache for `wp_count_posts()` on insert, trash, or when transitioning `post_status` inside of `_transition_post_status()`. Introduces `_count_posts_cache_key()`. Adds unit tests. Props mark8barnes, for bringing this to our attention in an initial patch. Fixes #21879. git-svn-id: https://develop.svn.wordpress.org/trunk@27081 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/post.php | 36 ++++++++++++++++++++++++++++++------ tests/phpunit/tests/post.php | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 7c537f9eab..02cc7d5a5e 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2076,6 +2076,26 @@ function unstick_post($post_id) { update_option('sticky_posts', $stickies); } +/** + * Return the cache key for wp_count_posts() based on the passed arguments + * + * @since 3.9.0 + * + * @param string $type Optional. Post type to retrieve count + * @param string $perm Optional. 'readable' or empty. + * @return string The cache key. + */ +function _count_posts_cache_key( $type = 'post', $perm = '' ) { + $cache_key = 'posts-' . $type; + if ( 'readable' == $perm && is_user_logged_in() ) { + $post_type_object = get_post_type_object( $type ); + if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) { + $cache_key .= '_' . $perm . '_' . get_current_user_id(); + } + } + return $cache_key; +} + /** * Count number of posts of a post type and if user has permissions to view. * @@ -2101,16 +2121,15 @@ function wp_count_posts( $type = 'post', $perm = '' ) { if ( ! post_type_exists( $type ) ) return new stdClass; - $user = wp_get_current_user(); - - $cache_key = 'posts-' . $type; + $cache_key = _count_posts_cache_key( $type, $perm ); $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s"; if ( 'readable' == $perm && is_user_logged_in() ) { $post_type_object = get_post_type_object($type); - if ( !current_user_can( $post_type_object->cap->read_private_posts ) ) { - $cache_key .= '_' . $perm . '_' . $user->ID; - $query .= " AND (post_status != 'private' OR ( post_author = '$user->ID' AND post_status = 'private' ))"; + if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) { + $query .= $wpdb->prepare( " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))", + get_current_user_id() + ); } } $query .= ' GROUP BY post_status'; @@ -4885,6 +4904,11 @@ function _transition_post_status($new_status, $old_status, $post) { } } + if ( $new_status !== $old_status ) { + wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' ); + wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' ); + } + // Always clears the hook in case the post status bounced from future to draft. wp_clear_scheduled_hook('publish_future_post', array( $post->ID ) ); } diff --git a/tests/phpunit/tests/post.php b/tests/phpunit/tests/post.php index 6e6e4094d6..e8074e9388 100644 --- a/tests/phpunit/tests/post.php +++ b/tests/phpunit/tests/post.php @@ -829,4 +829,40 @@ class Tests_Post extends WP_UnitTestCase { $counts->publish = 7; return $counts; } + + function test_wp_count_posts_insert_invalidation() { + $post_ids = $this->factory->post->create_many( 10 ); + $initial_counts = wp_count_posts(); + + $key = array_rand( $post_ids ); + $_post = get_post( $post_ids[$key], ARRAY_A ); + $_post['post_status'] = 'draft'; + wp_insert_post( $_post ); + $post = get_post( $post_ids[$key] ); + $this->assertEquals( 'draft', $post->post_status ); + $this->assertNotEquals( 'publish', $post->post_status ); + + $after_draft_counts = wp_count_posts(); + $this->assertEquals( 1, $after_draft_counts->draft ); + $this->assertEquals( 9, $after_draft_counts->publish ); + $this->assertNotEquals( $initial_counts->publish, $after_draft_counts->publish ); + } + + function test_wp_count_posts_trash_invalidation() { + $post_ids = $this->factory->post->create_many( 10 ); + $initial_counts = wp_count_posts(); + + $key = array_rand( $post_ids ); + + wp_trash_post( $post_ids[$key] ); + + $post = get_post( $post_ids[$key] ); + $this->assertEquals( 'trash', $post->post_status ); + $this->assertNotEquals( 'publish', $post->post_status ); + + $after_trash_counts = wp_count_posts(); + $this->assertEquals( 1, $after_trash_counts->trash ); + $this->assertEquals( 9, $after_trash_counts->publish ); + $this->assertNotEquals( $initial_counts->publish, $after_trash_counts->publish ); + } }