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
This commit is contained in:
Rachel Baker 2016-10-25 20:48:29 +00:00
parent 92dfd29aee
commit 2f263fce99
11 changed files with 128 additions and 65 deletions

View File

@ -422,22 +422,30 @@ class WP {
} }
$headers['Content-Type'] = feed_content_type( $type ) . '; charset=' . get_option( 'blog_charset' ); $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 // We're showing a feed, so WP is indeed the only thing that last changed.
if ( !empty($this->query_vars['withcomments']) if ( ! empty( $this->query_vars['withcomments'] )
|| false !== strpos( $this->query_vars['feed'], 'comments-' ) || false !== strpos( $this->query_vars['feed'], 'comments-' )
|| ( empty($this->query_vars['withoutcomments']) || ( empty( $this->query_vars['withoutcomments'] )
&& ( !empty($this->query_vars['p']) && ( ! empty( $this->query_vars['p'] )
|| !empty($this->query_vars['name']) || ! empty( $this->query_vars['name'] )
|| !empty($this->query_vars['page_id']) || ! empty( $this->query_vars['page_id'] )
|| !empty($this->query_vars['pagename']) || ! empty( $this->query_vars['pagename'] )
|| !empty($this->query_vars['attachment']) || ! empty( $this->query_vars['attachment'] )
|| !empty($this->query_vars['attachment_id']) || ! empty( $this->query_vars['attachment_id'] )
) )
) )
) ) {
$wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastcommentmodified('GMT'), 0).' GMT'; $wp_last_modified = mysql2date( 'D, d M Y H:i:s', get_lastcommentmodified( 'GMT' ), false );
else } else {
$wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT'; $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) . '"'; $wp_etag = '"' . md5($wp_last_modified) . '"';
$headers['Last-Modified'] = $wp_last_modified; $headers['Last-Modified'] = $wp_last_modified;
$headers['ETag'] = $wp_etag; $headers['ETag'] = $wp_etag;

View File

@ -293,38 +293,46 @@ function get_default_comment_status( $post_type = 'post', $comment_type = 'comme
* The date the last comment was modified. * The date the last comment was modified.
* *
* @since 1.5.0 * @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. * @global wpdb $wpdb WordPress database abstraction object.
* @staticvar array $cache_lastcommentmodified
* *
* @param string $timezone Which timezone to use in reference to 'gmt', 'blog', * @param string $timezone Which timezone to use in reference to 'gmt', 'blog', or 'server' locations.
* or 'server' locations. * @return string|false Last comment modified date on success, false on failure.
* @return string Last comment modified date.
*/ */
function get_lastcommentmodified($timezone = 'server') { function get_lastcommentmodified( $timezone = 'server' ) {
global $wpdb; global $wpdb;
static $cache_lastcommentmodified = array();
if ( isset($cache_lastcommentmodified[$timezone]) ) $timezone = strtolower( $timezone );
return $cache_lastcommentmodified[$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': 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; break;
case 'blog': 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; break;
case 'server': 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; 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 ); 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. * Get current commenter's name, email, and URL.
* *
@ -1681,6 +1709,10 @@ function wp_insert_comment( $commentdata ) {
if ( $comment_approved == 1 ) { if ( $comment_approved == 1 ) {
wp_update_comment_count( $comment_post_ID ); wp_update_comment_count( $comment_post_ID );
foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
wp_cache_delete( "lastcommentmodified:$timezone", 'timeinfo' );
}
} }
clean_comment_cache( $id ); clean_comment_cache( $id );

View File

@ -214,6 +214,8 @@ add_filter( 'pingback_ping_source_uri', 'pingback_ping_source_uri' );
add_filter( 'xmlrpc_pingback_error', 'xmlrpc_pingback_error' ); add_filter( 'xmlrpc_pingback_error', 'xmlrpc_pingback_error' );
add_filter( 'title_save_pre', 'trim' ); 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 ); add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 );
// REST API filters. // REST API filters.

View File

@ -37,7 +37,10 @@ do_action( 'rss_tag_pre', 'atom-comments' );
?></title> ?></title>
<subtitle type="text"><?php bloginfo_rss('description'); ?></subtitle> <subtitle type="text"><?php bloginfo_rss('description'); ?></subtitle>
<updated><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastcommentmodified('GMT'), false); ?></updated> <updated><?php
$date = get_lastcommentmodified( 'GMT' );
echo $date ? mysql2date( 'Y-m-d\TH:i:s\Z', $date ) : date( 'Y-m-d\TH:i:s\Z' );
?></updated>
<?php if ( is_singular() ) { ?> <?php if ( is_singular() ) { ?>
<link rel="alternate" type="<?php bloginfo_rss('html_type'); ?>" href="<?php comments_link_feed(); ?>" /> <link rel="alternate" type="<?php bloginfo_rss('html_type'); ?>" href="<?php comments_link_feed(); ?>" />

View File

@ -30,7 +30,10 @@ do_action( 'rss_tag_pre', 'atom' );
<title type="text"><?php wp_title_rss(); ?></title> <title type="text"><?php wp_title_rss(); ?></title>
<subtitle type="text"><?php bloginfo_rss("description") ?></subtitle> <subtitle type="text"><?php bloginfo_rss("description") ?></subtitle>
<updated><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastpostmodified('GMT'), false); ?></updated> <updated><?php
$date = get_lastpostmodified( 'GMT' );
echo $date ? mysql2date( 'Y-m-d\TH:i:s\Z', $date ) : date( 'Y-m-d\TH:i:s\Z' );
?></updated>
<link rel="alternate" type="<?php bloginfo_rss('html_type'); ?>" href="<?php bloginfo_rss('url') ?>" /> <link rel="alternate" type="<?php bloginfo_rss('html_type'); ?>" href="<?php bloginfo_rss('url') ?>" />
<id><?php bloginfo('atom_url'); ?></id> <id><?php bloginfo('atom_url'); ?></id>

View File

@ -33,7 +33,10 @@ do_action( 'rss_tag_pre', 'rdf' );
<title><?php wp_title_rss(); ?></title> <title><?php wp_title_rss(); ?></title>
<link><?php bloginfo_rss('url') ?></link> <link><?php bloginfo_rss('url') ?></link>
<description><?php bloginfo_rss('description') ?></description> <description><?php bloginfo_rss('description') ?></description>
<dc:date><?php echo mysql2date('Y-m-d\TH:i:s\Z', get_lastpostmodified('GMT'), false); ?></dc:date> <dc:date><?php
$date = get_lastpostmodified( 'GMT' );
echo $date ? mysql2date( 'Y-m-d\TH:i:s\Z', $date ) : date( 'Y-m-d\TH:i:s\Z' );
?></dc:date>
<sy:updatePeriod><?php <sy:updatePeriod><?php
/** This filter is documented in wp-includes/feed-rss2.php */ /** This filter is documented in wp-includes/feed-rss2.php */
echo apply_filters( 'rss_update_period', 'hourly' ); echo apply_filters( 'rss_update_period', 'hourly' );

View File

@ -14,7 +14,10 @@ echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'; ?>
<title><?php wp_title_rss(); ?></title> <title><?php wp_title_rss(); ?></title>
<link><?php bloginfo_rss('url') ?></link> <link><?php bloginfo_rss('url') ?></link>
<description><?php bloginfo_rss('description') ?></description> <description><?php bloginfo_rss('description') ?></description>
<lastBuildDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_lastpostmodified('GMT'), false); ?></lastBuildDate> <lastBuildDate><?php
$date = get_lastpostmodified( 'GMT' );
echo $date ? mysql2date( 'D, d M Y H:i:s +0000', $date ) : date( 'D, d M Y H:i:s +0000' );
?></lastBuildDate>
<docs>http://backend.userland.com/rss092</docs> <docs>http://backend.userland.com/rss092</docs>
<language><?php bloginfo_rss( 'language' ); ?></language> <language><?php bloginfo_rss( 'language' ); ?></language>

View File

@ -43,7 +43,10 @@ do_action( 'rss_tag_pre', 'rss2-comments' );
<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" /> <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
<link><?php (is_single()) ? the_permalink_rss() : bloginfo_rss("url") ?></link> <link><?php (is_single()) ? the_permalink_rss() : bloginfo_rss("url") ?></link>
<description><?php bloginfo_rss("description") ?></description> <description><?php bloginfo_rss("description") ?></description>
<lastBuildDate><?php echo mysql2date('r', get_lastcommentmodified('GMT')); ?></lastBuildDate> <lastBuildDate><?php
$date = get_lastcommentmodified( 'GMT' );
echo $date ? mysql2date( 'r', $date ) : date( 'r' );
?></lastBuildDate>
<sy:updatePeriod><?php <sy:updatePeriod><?php
/** This filter is documented in wp-includes/feed-rss2.php */ /** This filter is documented in wp-includes/feed-rss2.php */
echo apply_filters( 'rss_update_period', 'hourly' ); echo apply_filters( 'rss_update_period', 'hourly' );

View File

@ -42,7 +42,10 @@ do_action( 'rss_tag_pre', 'rss2' );
<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" /> <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
<link><?php bloginfo_rss('url') ?></link> <link><?php bloginfo_rss('url') ?></link>
<description><?php bloginfo_rss("description") ?></description> <description><?php bloginfo_rss("description") ?></description>
<lastBuildDate><?php echo mysql2date('D, d M Y H:i:s +0000', get_lastpostmodified('GMT'), false); ?></lastBuildDate> <lastBuildDate><?php
$date = get_lastpostmodified( 'GMT' );
echo $date ? mysql2date( 'D, d M Y H:i:s +0000', $date ) : date( 'D, d M Y H:i:s +0000' );
?></lastBuildDate>
<language><?php bloginfo_rss( 'language' ); ?></language> <language><?php bloginfo_rss( 'language' ); ?></language>
<sy:updatePeriod><?php <sy:updatePeriod><?php
$duration = 'hourly'; $duration = 'hourly';

View File

@ -5612,35 +5612,38 @@ function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
} }
$date = wp_cache_get( $key, 'timeinfo' ); $date = wp_cache_get( $key, 'timeinfo' );
if ( false !== $date ) {
if ( ! $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; 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;
} }
/** /**

View File

@ -4,7 +4,7 @@
* *
* @global string $wp_version * @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. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.