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
This commit is contained in:
Scott Taylor 2014-02-03 19:41:40 +00:00
parent 9fd8c54ef9
commit 28bab15d16
2 changed files with 66 additions and 6 deletions

View File

@ -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 ) );
}

View File

@ -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 );
}
}