From 2f263fce9961031d6dfd9546bec596893bd592b3 Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Tue, 25 Oct 2016 20:48:29 +0000 Subject: [PATCH] Feeds: Always return a valid timestamp for the Last-Modified header of comment or post feeds. Fixes bug where an invalid Last-Modified value would be returned in feed requests for sites that had 0 items to return. Comment or post feeds will now return the current timestamp as the Last-Modified header value. Example: a request for the comments feed for a site without any comments. Replaced use of the local static variable `$cache_lastcommentmodified` to store the modified date in `get_lastcommentmodified()` with the Object Cache API. The `get_lastcommentmodified()` function returns early if there is a cached value and returns `false` if there where no comments found. Introduced `_clear_modified_cache_on_transition_comment_status()` to flush the `lastcommentmodified` cache key when a comment enters or leaves approval status. In `get_lastpostmodified()` return early if there is a cached value and return `false` if there are no posts found. Props swissspidy, rachelbaker, dllh, leobaiano. Fixes #38027. Built from https://develop.svn.wordpress.org/trunk@38925 git-svn-id: http://core.svn.wordpress.org/trunk@38868 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp.php | 40 +++++++++++-------- wp-includes/comment.php | 62 ++++++++++++++++++++++-------- wp-includes/default-filters.php | 2 + wp-includes/feed-atom-comments.php | 5 ++- wp-includes/feed-atom.php | 5 ++- wp-includes/feed-rdf.php | 5 ++- wp-includes/feed-rss.php | 5 ++- wp-includes/feed-rss2-comments.php | 5 ++- wp-includes/feed-rss2.php | 5 ++- wp-includes/post.php | 57 ++++++++++++++------------- wp-includes/version.php | 2 +- 11 files changed, 128 insertions(+), 65 deletions(-) diff --git a/wp-includes/class-wp.php b/wp-includes/class-wp.php index cac538d6cd..c62d79082b 100644 --- a/wp-includes/class-wp.php +++ b/wp-includes/class-wp.php @@ -422,22 +422,30 @@ class WP { } $headers['Content-Type'] = feed_content_type( $type ) . '; charset=' . get_option( 'blog_charset' ); - // We're showing a feed, so WP is indeed the only thing that last changed - if ( !empty($this->query_vars['withcomments']) - || false !== strpos( $this->query_vars['feed'], 'comments-' ) - || ( empty($this->query_vars['withoutcomments']) - && ( !empty($this->query_vars['p']) - || !empty($this->query_vars['name']) - || !empty($this->query_vars['page_id']) - || !empty($this->query_vars['pagename']) - || !empty($this->query_vars['attachment']) - || !empty($this->query_vars['attachment_id']) - ) - ) - ) - $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastcommentmodified('GMT'), 0).' GMT'; - else - $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT'; + // We're showing a feed, so WP is indeed the only thing that last changed. + if ( ! empty( $this->query_vars['withcomments'] ) + || false !== strpos( $this->query_vars['feed'], 'comments-' ) + || ( empty( $this->query_vars['withoutcomments'] ) + && ( ! empty( $this->query_vars['p'] ) + || ! empty( $this->query_vars['name'] ) + || ! empty( $this->query_vars['page_id'] ) + || ! empty( $this->query_vars['pagename'] ) + || ! empty( $this->query_vars['attachment'] ) + || ! empty( $this->query_vars['attachment_id'] ) + ) + ) + ) { + $wp_last_modified = mysql2date( 'D, d M Y H:i:s', get_lastcommentmodified( 'GMT' ), false ); + } else { + $wp_last_modified = mysql2date( 'D, d M Y H:i:s', get_lastpostmodified( 'GMT' ), false ); + } + + if ( ! $wp_last_modified ) { + $wp_last_modified = date( 'D, d M Y H:i:s' ); + } + + $wp_last_modified .= ' GMT'; + $wp_etag = '"' . md5($wp_last_modified) . '"'; $headers['Last-Modified'] = $wp_last_modified; $headers['ETag'] = $wp_etag; diff --git a/wp-includes/comment.php b/wp-includes/comment.php index 49ddda8b6d..7da516266f 100644 --- a/wp-includes/comment.php +++ b/wp-includes/comment.php @@ -293,38 +293,46 @@ function get_default_comment_status( $post_type = 'post', $comment_type = 'comme * The date the last comment was modified. * * @since 1.5.0 + * @since 4.7.0 Replaced caching the modified date in a local static variable + * with the Object Cache API. * * @global wpdb $wpdb WordPress database abstraction object. - * @staticvar array $cache_lastcommentmodified * - * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', - * or 'server' locations. - * @return string Last comment modified date. + * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', or 'server' locations. + * @return string|false Last comment modified date on success, false on failure. */ -function get_lastcommentmodified($timezone = 'server') { +function get_lastcommentmodified( $timezone = 'server' ) { global $wpdb; - static $cache_lastcommentmodified = array(); - if ( isset($cache_lastcommentmodified[$timezone]) ) - return $cache_lastcommentmodified[$timezone]; + $timezone = strtolower( $timezone ); + $key = "lastcommentmodified:$timezone"; - $add_seconds_server = date('Z'); + $comment_modified_date = wp_cache_get( $key, 'timeinfo' ); + if ( false !== $comment_modified_date ) { + return $comment_modified_date; + } - switch ( strtolower($timezone)) { + switch ( $timezone ) { case 'gmt': - $lastcommentmodified = $wpdb->get_var("SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1"); + $comment_modified_date = $wpdb->get_var( "SELECT comment_date_gmt FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" ); break; case 'blog': - $lastcommentmodified = $wpdb->get_var("SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1"); + $comment_modified_date = $wpdb->get_var( "SELECT comment_date FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1" ); break; case 'server': - $lastcommentmodified = $wpdb->get_var($wpdb->prepare("SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server)); + $add_seconds_server = date( 'Z' ); + + $comment_modified_date = $wpdb->get_var( $wpdb->prepare( "SELECT DATE_ADD(comment_date_gmt, INTERVAL %s SECOND) FROM $wpdb->comments WHERE comment_approved = '1' ORDER BY comment_date_gmt DESC LIMIT 1", $add_seconds_server ) ); break; } - $cache_lastcommentmodified[$timezone] = $lastcommentmodified; + if ( $comment_modified_date ) { + wp_cache_set( $key, $comment_modified_date, 'timeinfo' ); - return $lastcommentmodified; + return $comment_modified_date; + } + + return false; } /** @@ -1572,6 +1580,26 @@ function wp_transition_comment_status($new_status, $old_status, $comment) { do_action( "comment_{$new_status}_{$comment->comment_type}", $comment->comment_ID, $comment ); } +/** + * Clear the lastcommentmodified cached value when a comment status is changed. + * + * Deletes the lastcommentmodified cache key when a comment enters or leaves + * 'approved' status. + * + * @since 4.7.0 + * @access private + * + * @param string $new_status The new comment status. + * @param string $old_status The old comment status. + */ +function _clear_modified_cache_on_transition_comment_status( $new_status, $old_status ) { + if ( 'approved' === $new_status || 'approved' === $old_status ) { + foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) { + wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' ); + } + } +} + /** * Get current commenter's name, email, and URL. * @@ -1681,6 +1709,10 @@ function wp_insert_comment( $commentdata ) { if ( $comment_approved == 1 ) { wp_update_comment_count( $comment_post_ID ); + + foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) { + wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' ); + } } clean_comment_cache( $id ); diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index 71255c3497..323e55e840 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -214,6 +214,8 @@ add_filter( 'pingback_ping_source_uri', 'pingback_ping_source_uri' ); add_filter( 'xmlrpc_pingback_error', 'xmlrpc_pingback_error' ); add_filter( 'title_save_pre', 'trim' ); +add_action( 'transition_comment_status', '_clear_modified_cache_on_transition_comment_status', 10, 2 ); + add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 ); // REST API filters. diff --git a/wp-includes/feed-atom-comments.php b/wp-includes/feed-atom-comments.php index e5c7089164..5b494a2992 100644 --- a/wp-includes/feed-atom-comments.php +++ b/wp-includes/feed-atom-comments.php @@ -37,7 +37,10 @@ do_action( 'rss_tag_pre', 'atom-comments' ); ?> - + diff --git a/wp-includes/feed-atom.php b/wp-includes/feed-atom.php index a4a62f74b9..09cb7c016e 100644 --- a/wp-includes/feed-atom.php +++ b/wp-includes/feed-atom.php @@ -30,7 +30,10 @@ do_action( 'rss_tag_pre', 'atom' ); <?php wp_title_rss(); ?> - + diff --git a/wp-includes/feed-rdf.php b/wp-includes/feed-rdf.php index 8302881500..b748400032 100644 --- a/wp-includes/feed-rdf.php +++ b/wp-includes/feed-rdf.php @@ -33,7 +33,10 @@ do_action( 'rss_tag_pre', 'rdf' ); <?php wp_title_rss(); ?> - + '; ?> <?php wp_title_rss(); ?> - + http://backend.userland.com/rss092 diff --git a/wp-includes/feed-rss2-comments.php b/wp-includes/feed-rss2-comments.php index f05bb8f71a..af329c5b63 100644 --- a/wp-includes/feed-rss2-comments.php +++ b/wp-includes/feed-rss2-comments.php @@ -43,7 +43,10 @@ do_action( 'rss_tag_pre', 'rss2-comments' ); - + " rel="self" type="application/rss+xml" /> - + true ) ); - array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) ); - $post_types = "'" . implode( "', '", $post_types ) . "'"; - } else { - $post_types = "'" . sanitize_key( $post_type ) . "'"; - } - - switch ( $timezone ) { - case 'gmt': - $date = $wpdb->get_var("SELECT post_{$field}_gmt FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1"); - break; - case 'blog': - $date = $wpdb->get_var("SELECT post_{$field} FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1"); - break; - case 'server': - $add_seconds_server = date( 'Z' ); - $date = $wpdb->get_var("SELECT DATE_ADD(post_{$field}_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1"); - break; - } - - if ( $date ) { - wp_cache_set( $key, $date, 'timeinfo' ); - } + if ( false !== $date ) { + return $date; } - return $date; + if ( 'any' === $post_type ) { + $post_types = get_post_types( array( 'public' => true ) ); + array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) ); + $post_types = "'" . implode( "', '", $post_types ) . "'"; + } else { + $post_types = "'" . sanitize_key( $post_type ) . "'"; + } + + switch ( $timezone ) { + case 'gmt': + $date = $wpdb->get_var("SELECT post_{$field}_gmt FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1"); + break; + case 'blog': + $date = $wpdb->get_var("SELECT post_{$field} FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1"); + break; + case 'server': + $add_seconds_server = date( 'Z' ); + $date = $wpdb->get_var("SELECT DATE_ADD(post_{$field}_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1"); + break; + } + + if ( $date ) { + wp_cache_set( $key, $date, 'timeinfo' ); + + return $date; + } + + return false; } /** diff --git a/wp-includes/version.php b/wp-includes/version.php index 7031ffbeb2..d6bd2f4fee 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.7-alpha-38924'; +$wp_version = '4.7-alpha-38925'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.