? 54238-page-cache.patch
? sites/default/files
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.263
diff -u -p -r1.263 bootstrap.inc
--- includes/bootstrap.inc	4 Jan 2009 16:15:54 -0000	1.263
+++ includes/bootstrap.inc	11 Jan 2009 17:00:25 -0000
@@ -656,23 +656,26 @@ function variable_del($name) {
   unset($conf[$name]);
 }
 
-
 /**
  * Retrieve the current page from the cache.
  *
- * Note: we do not serve cached pages when status messages are waiting (from
- * a redirected form submission which was completed).
+ * @return
+ *   A string of the saved page from the cache, or NULL if the page has not
+ *   been cached or is not allowed to be cached.
+ *
+ * @see page_cache_allowed()
  */
 function page_get_cache() {
-  global $user, $base_root;
+  global $base_root;
 
   $cache = NULL;
 
-  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_set_message()) == 0) {
+  if (page_cache_allowed()) {
     $cache = cache_get($base_root . request_uri(), 'cache_page');
 
     if (empty($cache)) {
       ob_start();
+      page_cache_allowed(TRUE);
     }
   }
 
@@ -680,6 +683,36 @@ function page_get_cache() {
 }
 
 /**
+ * Retrieves and optionally sets the current page caching status.
+ *
+ * This function is useful if page caching has been started but needs to be
+ * disabled for the current page if something unique is being displayed on the
+ * page, like a message with drupal_set_message().
+ *
+ * @param $cache_status
+ *   (optional) A boolean that sets the current page caching status.
+ * @return
+ *   TRUE if the current page should be cached, FALSE if otherwise.
+ */
+function page_cache_allowed($cache_status = NULL) {
+  global $user;
+  static $status = NULL;
+
+  if (!isset($status)) {
+    // Do not allow caching when status messages are waiting (from a redirected
+    // form submission which was completed).
+    $status = count(drupal_set_message()) == 0;
+  }
+
+  if (isset($cache_status)) {
+    $status = $cache_status;
+  }
+
+  // Always check the request method and that the user is logged out.
+  return $status && !$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD');
+}
+
+/**
  * Includes a file with the provided type and name. This prevents
  * including a theme, engine, module, etc., more than once.
  *
@@ -954,6 +987,8 @@ function drupal_set_message($message = N
 
     if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
       $_SESSION['messages'][$type][] = $message;
+      // Disable the current request from being cached.
+      page_cache_allowed(FALSE);
     }
   }
 
@@ -1147,7 +1182,7 @@ function _drupal_bootstrap($phase) {
         module_invoke_all('boot');
       }
       // If there is a cached page, display it.
-      if ($cache) {
+      if ($cache && page_cache_allowed()) {
         drupal_page_cache_header($cache);
         // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
         if ($cache_mode != CACHE_AGGRESSIVE) {
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.845
diff -u -p -r1.845 common.inc
--- includes/common.inc	11 Jan 2009 08:39:07 -0000	1.845
+++ includes/common.inc	11 Jan 2009 17:00:30 -0000
@@ -1840,7 +1840,6 @@ function l($text, $path, array $options 
  * react to the closing of the page by calling hook_exit().
  */
 function drupal_page_footer() {
-
   if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
     page_set_cache();
   }
@@ -2975,31 +2974,27 @@ function _drupal_bootstrap_full() {
  * We thus only deal with the gzip variant and unzip the cache in case
  * the browser does not accept gzip encoding.
  *
- * @see drupal_page_header
+ * @see drupal_page_header()
+ * @see page_cache_allowed()
  */
 function page_set_cache() {
-  global $user, $base_root;
+  global $base_root;
 
-  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_get_messages(NULL, FALSE)) == 0) {
-    // This will fail in some cases, see page_get_cache() for the explanation.
-    if ($data = ob_get_contents()) {
-      $cache = TRUE;
-      if (variable_get('page_compression', TRUE) && function_exists('gzencode')) {
-        // We do not store the data in case the zlib mode is deflate.
-        // This should be rarely happening.
-        if (zlib_get_coding_type() == 'deflate') {
-          $cache = FALSE;
-        }
-        elseif (zlib_get_coding_type() == FALSE) {
-          $data = gzencode($data, 9, FORCE_GZIP);
-        }
-        // The remaining case is 'gzip' which means the data is
-        // already compressed and nothing left to do but to store it.
+  if (ob_get_level() && ($data = ob_get_contents())) {
+    if (variable_get('page_compression', TRUE) && function_exists('gzencode')) {
+      // We do not store the data in case the zlib mode is deflate.
+      // This should be rarely happening.
+      if (zlib_get_coding_type() == 'deflate') {
+        page_cache_allowed(FALSE);
       }
-      ob_end_flush();
-      if ($cache && $data) {
-        cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
+      elseif (zlib_get_coding_type() == FALSE) {
+        $data = gzencode($data, 9, FORCE_GZIP);
       }
+      // The remaining case is 'gzip' which means the data is
+      // already compressed and nothing left to do but to store it.
+    }
+    if (page_cache_allowed() && $data) {
+      cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
     }
   }
 }
Index: modules/simpletest/tests/cache.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/cache.test,v
retrieving revision 1.3
diff -u -p -r1.3 cache.test
--- modules/simpletest/tests/cache.test	8 Dec 2008 21:41:54 -0000	1.3
+++ modules/simpletest/tests/cache.test	11 Jan 2009 17:00:35 -0000
@@ -29,7 +29,7 @@ class CacheTestCase extends DrupalWebTes
   }
 
   /**
-   * Assert or a cache entry exists.
+   * Assert that a cache entry exists.
    *
    * @param $message
    *   Message to display.
@@ -55,7 +55,7 @@ class CacheTestCase extends DrupalWebTes
   }
 
   /**
-   * Assert or a cache entry has been removed.
+   * Assert that a cache entry has been removed.
    *
    * @param $message
    *   Message to display.
@@ -77,6 +77,33 @@ class CacheTestCase extends DrupalWebTes
   }
 
   /**
+   * Assert that the page cache has been disabled.
+   *
+   * @param $message
+   *   Message to display.
+   * @param $cid
+   *   The cache id.
+   * @param $table
+   *   The table the cache item was stored in.
+   * @param $allowed
+   *   The cache status to set.
+   */
+  function assertCacheAllowed($message, $cid = NULL, $table = NULL, $allowed = TRUE) {
+    if ($table == NULL) {
+      $table = $this->default_table;
+    }
+    if ($cid == NULL) {
+      $cid = $this->default_cid;
+    }
+
+    $cache = cache_get($cid, $table);
+    if (!page_cache_allowed($allowed)) {
+      $cache = NULL;
+    }
+    $this->assertFalse($cache, $message);
+  }
+
+  /**
    * Perform the general wipe.
    * @param $table
    *   The table to perform the wipe on.
@@ -224,4 +251,108 @@ class CacheClearCase extends CacheTestCa
                       || $this->checkCacheExists('test_cid_clear2', $this->default_value),
                       t('Two caches removed after clearing cid substring with wildcard true.'));
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Test page caching.
+ */
+class CachePageCase extends CacheTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Cache page test'),
+      'description' => t('Check caching of pages and conditions for not caching pages.'),
+      'group' => t('Cache'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('system_test');
+
+    // Enable caching and clear the page cache.
+    variable_set('cache', CACHE_NORMAL);
+    cache_clear_all('*', 'cache_page', TRUE);
+  }
+
+  /**
+   * Test that pages with messages are not cached.
+   */
+  function testNoCacheMessages() {
+    // View the front page with a message on it and test it was not cached.
+    variable_set('front_page_output', 1);
+    $this->drupalGet('');
+    $this->assertText(t('On front page.'), t('Message found.'));
+    $this->assertPageNotCached();
+
+    // View the front page without a message on it and test it was cached.
+    variable_set('front_page_output', 0);
+    $this->drupalGet('');
+    $this->assertNoText(t('On front page.'), t('Message not found.'));
+    $this->assertPageCached();
+  }
+
+  /**
+   * Test that pages viewed by logged in users are not cached.
+   */
+  function testNoCacheLoggedIn() {
+    // Create a user, log in, and test that the request was not cached.
+    $cache_user = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($cache_user);
+    $this->drupalGet('');
+    $this->assertPageNotCached();
+
+    // Logout and check that the next request is cached.
+    $this->drupalLogout();
+    $this->drupalGet('');
+    $this->assertPageCached();
+  }
+
+  /**
+   * Test that GET and HEAD requests are cached, while POST requests are not.
+   */
+  function testCacheMethods() {
+    $this->drupalGet('');
+    $this->assertPageCached();
+
+    // Clear the page from the cache for the POST test.
+    cache_clear_all('*', 'cache_page', TRUE);
+
+    $this->drupalPost(NULL, array(), t('Log in'));
+    $this->assertPageNotCached();
+
+    $this->drupalHead('');
+    $this->assertPageCached();
+    $this->assertPageCacheDisabled();
+    $this->assertPageCacheEnabled();
+  }
+
+  /**
+   * Test that a page has been cached.
+   */
+  private function assertPageCached($url = '') {
+    $this->assertCacheExists(t('Page has been cached.'), TRUE, url($url, array('absolute' => TRUE)), 'cache_page');
+  }
+
+  /**
+   * Test that a page has not been cached.
+   */
+  private function assertPageNotCached($url = '') {
+    $this->assertCacheRemoved(t('Page has not been cached.'), url($url, array('absolute' => TRUE)), 'cache_page');
+  }
+
+  /**
+   * Test that the page cache can be invalidated.
+   */
+  private function assertPageCacheDisabled($url = '') {
+    $this->assertCacheAllowed(t('Page caching disabled.'), url($url, array('absolute' => TRUE)), 'cache_page', FALSE);
+  }
+
+  /**
+   * Test that the page cache can be validated.
+   */
+  private function assertPageCacheEnabled($url = '') {
+    $this->assertCacheAllowed(t('Page caching enabled.'), url($url, array('absolute' => TRUE)), 'cache_page', TRUE);
+  }
+
+
+}
