<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][7477] trunk/bp-activity/bp-activity-classes.php: Set better HTTP headers for activity RSS feeds.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://buddypress.trac.wordpress.org/changeset/7477">7477</a></dd>
<dt>Author</dt> <dd>r-a-y</dd>
<dt>Date</dt> <dd>2013-10-24 20:51:38 +0000 (Thu, 24 Oct 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Set better HTTP headers for activity RSS feeds.

Previously, BP's RSS feeds would use WP's native HTTP headers set in the
WP::send_headers() method.  This would cause BuddyPress to inherit WP
feed's "ETag" and HTTP status headers.

When trying to refresh BP's RSS feeds, this would not work correctly due
to BP relying on WP's post last modified date instead of BP's activity last
updated date.  This would lead to a HTTP 304 Not Modified status header
being sent by WP, meaning the client (browser, RSS reader) would not fetch
the latest items from the activity stream.

This commit sets the appropriate HTTP headers for BP's activity RSS feeds
and uses a new, internal class method - http_headers() - to set them.

Fixes <a href="http://buddypress.trac.wordpress.org/ticket/5213">#5213</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpactivitybpactivityclassesphp">trunk/bp-activity/bp-activity-classes.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbpactivitybpactivityclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-activity/bp-activity-classes.php (7476 => 7477)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-activity/bp-activity-classes.php        2013-10-24 01:46:03 UTC (rev 7476)
+++ trunk/bp-activity/bp-activity-classes.php   2013-10-24 20:51:38 UTC (rev 7477)
</span><span class="lines">@@ -1270,14 +1270,16 @@
</span><span class="cx">          }
</span><span class="cx">  }
</span><span class="cx"> 
</span><del>-       /** OUTPUT ***************************************************************/
-
</del><span class="cx">   /**
</span><del>-        * Output the RSS feed.
</del><ins>+         * Sets various HTTP headers related to Content-Type and browser caching.
</ins><span class="cx">    *
</span><ins>+        * Most of this class method is derived from {@link WP::send_headers()}.
+        *
+        * @since BuddyPress (1.9.0)
+        *
</ins><span class="cx">    * @access protected
</span><span class="cx">   */
</span><del>-       protected function output() {
</del><ins>+        protected function http_headers() {
</ins><span class="cx">           // set up some additional headers if not on a directory page
</span><span class="cx">          // this is done b/c BP uses pseudo-pages
</span><span class="cx">          if ( ! bp_is_directory() ) {
</span><span class="lines">@@ -1287,7 +1289,72 @@
</span><span class="cx">                  status_header( 200 );
</span><span class="cx">          }
</span><span class="cx"> 
</span><del>-               header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
</del><ins>+                // Set content-type
+               @header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
+
+               // Cache-related variables
+               $last_modified      = mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false );
+               $modified_timestamp = strtotime( $last_modified );
+               $etag               = md5( $last_modified );
+       
+               // Set cache-related headers
+               @header( 'Last-Modified: ' . $last_modified );
+               @header( 'Pragma: no-cache' );
+               @header( 'ETag: ' . '"' . $etag . '"' );
+       
+               // First commit of BuddyPress! (Easter egg)
+               @header( 'Expires: Tue, 25 Mar 2008 17:13:55 GMT');
+       
+               // Get ETag from supported user agents
+               if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) {
+                       $client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] );
+       
+                       // Remove quotes from ETag
+                       $client_etag = trim( $client_etag, '"' );
+       
+                       // Strip suffixes from ETag if they exist (eg. "-gzip")
+                       if ( $etag_suffix_pos = strpos( $client_etag, '-' ) ) {
+                               $client_etag = substr( $client_etag, 0, $etag_suffix_pos );
+                       }
+
+               // No ETag found
+               } else {
+                       $client_etag = false;
+               }
+       
+               // Get client last modified timestamp from supported user agents
+               $client_last_modified      = empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ? '' : trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
+               $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
+       
+               // Set 304 status if feed hasn't been updated since last fetch
+               if ( ( $client_last_modified && $client_etag ) ?
+                                ( ( $client_modified_timestamp >= $modified_timestamp ) && ( $client_etag == $etag ) ) :
+                                ( ( $client_modified_timestamp >= $modified_timestamp ) || ( $client_etag == $etag ) ) ) {
+                       $status = 304;
+               } else {
+                       $status = false;
+               }
+       
+               // If feed hasn't changed as reported by the user agent, set 304 status header
+               if ( ! empty( $status ) ) {
+                       status_header( $status );
+       
+                       // cached response, so stop now!
+                       if ( $status == 304 ) {
+                               exit();
+                       }
+               }
+       }
+
+       /** OUTPUT ***************************************************************/
+
+       /**
+        * Output the RSS feed.
+        *
+        * @access protected
+        */
+       protected function output() {
+               $this->http_headers();
</ins><span class="cx">           echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?'.'>';
</span><span class="cx">  ?>
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>