Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.269 diff -u -p -r1.269 bootstrap.inc --- includes/bootstrap.inc 31 Jan 2009 16:50:56 -0000 1.269 +++ includes/bootstrap.inc 3 Feb 2009 12:58:02 -0000 @@ -130,15 +130,15 @@ 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. + * Eigth 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. @@ -1086,14 +1086,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) { @@ -1117,7 +1117,7 @@ function drupal_get_bootstrap_phase() { } function _drupal_bootstrap($phase) { - global $conf, $user; + global $conf, $user, $no_cache; switch ($phase) { @@ -1180,10 +1180,14 @@ 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 ? FALSE : page_get_cache(TRUE); + $cache = $cache_mode == CACHE_DISABLED || $no_cache == TRUE ? FALSE : page_get_cache(TRUE); // If the skipping of the bootstrap hooks is not enforced, call hook_boot. if (!is_object($cache) || $cache_mode != CACHE_AGGRESSIVE) { // Load module handling. @@ -1215,10 +1219,6 @@ function _drupal_bootstrap($phase) { } 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(). Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.861 diff -u -p -r1.861 common.inc --- includes/common.inc 2 Feb 2009 15:42:43 -0000 1.861 +++ includes/common.inc 3 Feb 2009 12:58:04 -0000 @@ -1846,7 +1846,7 @@ function l($text, $path, array $options * react to the closing of the page by calling hook_exit(). */ function drupal_page_footer() { - global $user; + global $user, $no_cache; // Destroy empty anonymous sessions if possible. if (!headers_sent() && drupal_session_is_started() && empty($_SESSION) && !$user->uid) { @@ -1856,7 +1856,7 @@ function drupal_page_footer() { watchdog('session', '$_SESSION is non-empty yet no code has called drupal_session_start().', array(), WATCHDOG_NOTICE); } - if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { + if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && $no_cache != TRUE) { page_set_cache(); } Index: includes/language.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/language.inc,v retrieving revision 1.19 diff -u -p -r1.19 language.inc --- includes/language.inc 1 Feb 2009 16:45:53 -0000 1.19 +++ includes/language.inc 3 Feb 2009 12:58:04 -0000 @@ -10,7 +10,7 @@ * Choose a language for the page, based on language negotiation settings. */ function language_initialize() { - global $user; + global $user, $no_cache; // Configured presentation language mode. $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE); @@ -59,6 +59,11 @@ function language_initialize() { // Browser accept-language parsing. if ($language = language_from_browser()) { + // If the language is set from browser preferences, set $no_cache to TRUE + // to avoid caching the page in this language for subsequent requests. + if ($language != language_default()) { + $no_cache = TRUE; + } return $language; } Index: modules/simpletest/tests/bootstrap.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/bootstrap.test,v retrieving revision 1.12 diff -u -p -r1.12 bootstrap.test --- modules/simpletest/tests/bootstrap.test 31 Jan 2009 16:50:57 -0000 1.12 +++ modules/simpletest/tests/bootstrap.test 3 Feb 2009 12:58:05 -0000 @@ -80,6 +80,83 @@ class BootstrapIPAddressTestCase extends } } +/** + * Test page caching with language negotiation. + */ +class BootstrapPageCacheWithLanguageFallbackTestCase extends DrupalWebTestCase { + + function getInfo() { + return array( + 'name' => t('Page cache test with language negotiation'), + 'description' => t('Enable the page cache and language negotiation, ensure that cached pages are served in the correct language.'), + 'group' => t('Bootstrap') + ); + } + + function setUp() { + parent::setUp('locale'); + $this->web_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + $this->drupalLogin($this->web_user); + + // Enable French language. + $edit = array(); + $edit['langcode'] = 'fr'; + + $this->drupalPost('admin/settings/language/add', $edit, t('Add language')); + $this->drupalLogout($this->web_user); + + // Set language negotiation to "Path prefix with fallback". + variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH); + + // Force inclusion of language.inc. + drupal_init_language(); + // Enable the page cache. + variable_set('cache', CACHE_NORMAL); + } + + /** + * Test page caching with language fallback. + */ + function testPageCacheLanguageFallback() { + + // Visit the root path with browser language preference set to French. + // Since this is in the default site language (English), the page should + // not be cached for this request. + $this->drupalGet('', array(), array('Accept-Language: fr')); + + // The visitor should be served the page in their preferred language. + $this->assertRaw('xml:lang="fr"', t('Page returned in the correct language.')); + + // Since the page has been set by language preferences, we should neither + // serve the page from cache nor cache the rendered page.. + $this->assertFalse(cache_get(url('', array('absolute' => TRUE)), 'cache_page'), t('Page has not been cached.')); + $this->assertFalse($this->drupalGetHeader('Etag'), t('Page not served from cache.')); + + // Visit the page again with no browser language preference, and confirm + // that it is returned in the correct language. + $this->drupalGet(''); + $this->assertRaw('xml:lang="en"', t('Page returned in the correct language.')); + // This request should not have been served from cache since there have + // been no valid requests. + $this->assertFalse($this->drupalGetHeader('Etag'), t('Page not served from cache.')); + // Confirm the cache was populated from this request. + $this->assertTrue(cache_get(url('', array('absolute' => TRUE)), 'cache_page'), t('Page has been cached.')); + + // Ensure that the page served from cache correctly when requested again. + $this->drupalGet(''); + $this->assertTrue($this->drupalGetHeader('Etag'), t('Page served from cache')); + $this->assertRaw('xml:lang="en"', t('Page returned in the correct language.')); + + // Now that the page has been cached, visit again with the browser set to + // accept French, and confirm the page is not served from cache. + $this->drupalGet('', array(), array('Accept-Language: fr')); + + // The visitor should be served the page in their preferred language. + $this->assertRaw('xml:lang="fr"', t('Page returned in the correct language.')); + $this->assertFalse($this->drupalGetHeader('Etag'), t('Page not served from cache.')); + } +} + class BootstrapPageCacheTestCase extends DrupalWebTestCase { function getInfo() {