Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.257
diff -u -p -r1.257 bootstrap.inc
--- includes/bootstrap.inc	3 Dec 2008 14:51:53 -0000	1.257
+++ includes/bootstrap.inc	10 Dec 2008 15:19:10 -0000
@@ -130,15 +130,14 @@ define('DRUPAL_BOOTSTRAP_SESSION', 4);
 define('DRUPAL_BOOTSTRAP_VARIABLES', 5);
 
 /**
- * Seventh bootstrap phase: load bootstrap.inc and module.inc, start
- * the variable system and try to serve a page from the cache.
+ * Seventh bootstrap phase: find out language of the page.
  */
-define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 6);
-
+define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);
 /**
- * Eighth bootstrap phase: find out language of the page.
+ * Eighth bootstrap phase: load bootstrap.inc and module.inc, start
+ * the variable system and try to serve a page from the cache.
  */
-define('DRUPAL_BOOTSTRAP_LANGUAGE', 7);
+define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 7);
 
 /**
  * Nineth bootstrap phase: set $_GET['q'] to Drupal path of request.
@@ -658,12 +657,12 @@ function variable_del($name) {
  * a redirected form submission which was completed).
  */
 function page_get_cache() {
-  global $user, $base_root;
+  global $user;
 
   $cache = NULL;
 
   if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_set_message()) == 0) {
-    $cache = cache_get($base_root . request_uri(), 'cache_page');
+    $cache = cache_get(drupal_get_page_cache_key(), 'cache_page');
 
     if (empty($cache)) {
       ob_start();
@@ -674,6 +673,44 @@ function page_get_cache() {
 }
 
 /**
+ * Retrieve a cache key for the current page.
+ */
+function drupal_get_page_cache_key() {
+  global $base_root;
+
+  $key = $base_root . request_uri();
+  $keys = drupal_set_page_cache_key();
+  // Append keys with spaces.
+  if (!empty($keys)) {
+    $key .= ' ' . drupal_query_string_encode($keys);
+  }
+  return $key;
+}
+
+/**
+ * Alter the cache key for the current page request.
+ *
+ * This function should be called only at or before the hook_boot() stage.
+ * Otherwise, the key tested for will be different from the key used when
+ * the page cache is set.
+ *
+ * @param $key
+ *   The key to be set. E.g. 'language'.
+ * @param $value
+ *   The value to be set. E.g. 'fr'.
+ *
+ * @return
+ *   An array of any keys that have been set.
+ */
+function drupal_set_page_cache_key($key = NULL, $value = NULL) {
+  static $keys = array();
+  if ($key && $value) {
+    $keys[$key] = $value;
+  }
+  return $keys;
+}
+
+/**
  * Includes a file with the provided type and name. This prevents
  * including a theme, engine, module, etc., more than once.
  *
@@ -1044,14 +1081,14 @@ function drupal_anonymous_user($session 
  *     DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts.
  *     DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
  *     DRUPAL_BOOTSTRAP_VARIABLES: initialize variable handling.
+ *     DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page.
  *     DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start
  *       the variable system and try to serve a page from the cache.
- *     DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page.
  *     DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request.
  *     DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
  */
 function drupal_bootstrap($phase = NULL) {
-  static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_VARIABLES, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $completed_phase = -1;
+  static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_VARIABLES, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $completed_phase = -1;
 
   if (isset($phase)) {
     while ($phases && $phase > $completed_phase) {
@@ -1129,16 +1166,20 @@ function _drupal_bootstrap($phase) {
       $conf = variable_init(isset($conf) ? $conf : array());
       break;
 
+    case DRUPAL_BOOTSTRAP_LANGUAGE:
+      drupal_init_language();
+      break;
+
     case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
       $cache_mode = variable_get('cache', CACHE_DISABLED);
-      // Get the page from the cache.
-      $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();
       // If the skipping of the bootstrap hooks is not enforced, call hook_boot.
-      if (!$cache || $cache_mode != CACHE_AGGRESSIVE) {
+      if ($cache_mode != CACHE_AGGRESSIVE) {
         // Load module handling.
         require_once DRUPAL_ROOT . '/includes/module.inc';
         module_invoke_all('boot');
       }
+      // Get the page from the cache.
+      $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();
       // If there is a cached page, display it.
       if ($cache) {
         drupal_page_cache_header($cache);
@@ -1149,14 +1190,15 @@ function _drupal_bootstrap($phase) {
         // We are done.
         exit;
       }
+      // If not skipped above, call hook_boot now.
+      if ($cache_mode == CACHE_AGGRESSIVE) {
+        require_once DRUPAL_ROOT . '/includes/module.inc';
+        module_invoke_all('boot');
+      }
       // Prepare for non-cached page workflow.
       drupal_page_header();
       break;
 
-    case DRUPAL_BOOTSTRAP_LANGUAGE:
-      drupal_init_language();
-      break;
-
     case DRUPAL_BOOTSTRAP_PATH:
       require_once DRUPAL_ROOT . '/includes/path.inc';
       // Initialize $_GET['q'] prior to loading modules and invoking hook_init().
@@ -1210,6 +1252,7 @@ function drupal_init_language() {
     include_once DRUPAL_ROOT . '/includes/language.inc';
     $language = language_initialize();
   }
+  drupal_set_page_cache_key('language', $language->language);
 }
 
 /**
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.836
diff -u -p -r1.836 common.inc
--- includes/common.inc	9 Dec 2008 11:09:26 -0000	1.836
+++ includes/common.inc	10 Dec 2008 15:19:12 -0000
@@ -2912,7 +2912,7 @@ function _drupal_bootstrap_full() {
  * @see drupal_page_header
  */
 function page_set_cache() {
-  global $user, $base_root;
+  global $user;
 
   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.
@@ -2932,7 +2932,7 @@ function page_set_cache() {
       }
       ob_end_flush();
       if ($cache && $data) {
-        cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
+        cache_set(drupal_get_page_cache_key(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
       }
     }
   }
Index: modules/simpletest/tests/bootstrap.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/bootstrap.test,v
retrieving revision 1.9
diff -u -p -r1.9 bootstrap.test
--- modules/simpletest/tests/bootstrap.test	3 Dec 2008 14:51:53 -0000	1.9
+++ modules/simpletest/tests/bootstrap.test	10 Dec 2008 15:19:18 -0000
@@ -208,8 +208,9 @@ class HookBootExitTestCase extends Drupa
     // Test with aggressive cache. Boot and exit should not fire since the
     // page is cached.
     variable_set('cache', CACHE_AGGRESSIVE);
-    $this->assertTrue(cache_get(url('', array('absolute' => TRUE)), 'cache_page'), t('Page has been cached.'));
     $this->drupalGet('');
+    $this->drupalHead('');
+    $this->assertText('ETag: ', t('Verify presence of ETag header indicating that page caching is enabled.'));
     $this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'system_test' AND message = 'hook_boot'")->fetchField(), $calls, t('hook_boot not called with agressive cache and a cached page.'));
     $this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'system_test' AND message = 'hook_exit'")->fetchField(), $calls, t('hook_exit not called with agressive cache and a cached page.'));
 
