diff --git a/includes/common.inc b/includes/common.inc index 48f27bd29b7d6297067b16c94e731a2486bbaf67..fbd88e9938134752e3fbb5db3dcf4632ad698de0 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -294,7 +294,6 @@ function drupal_get_destination() { * @see drupal_get_destination() */ function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { - global $update_mode; if (isset($_REQUEST['destination'])) { extract(parse_url(urldecode($_REQUEST['destination']))); @@ -309,7 +308,7 @@ function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response // Allow modules to react to the end of the page request before redirecting. // We do not want this while running update.php. - if (empty($update_mode)) { + if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('exit', $url); } @@ -1707,10 +1706,16 @@ function drupal_get_css($css = NULL) { $no_module_preprocess = ''; $no_theme_preprocess = ''; - $preprocess_css = variable_get('preprocess_css', FALSE); + $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); $directory = file_directory_path(); $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. + $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1); + foreach ($css as $media => $types) { // If CSS preprocessing is off, we still need to output the styles. // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones. @@ -1720,22 +1725,22 @@ function drupal_get_css($css = NULL) { // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*, // regardless of whether preprocessing is on or off. if (!$preprocess && $type == 'module') { - $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file .'" />'."\n"; + $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n"; } // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*, // regardless of whether preprocessing is on or off. else if (!$preprocess && $type == 'theme') { - $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file .'" />'."\n"; + $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n"; } else { - $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file .'" />'."\n"; + $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n"; } } } } if ($is_writable && $preprocess_css) { - $filename = md5(serialize($types)) .'.css'; + $filename = md5(serialize($types) . $query_string) .'.css'; $preprocess_file = drupal_build_css_cache($types, $filename); $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $preprocess_file .'" />'."\n"; } @@ -2020,8 +2025,7 @@ function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer * All JavaScript code segments and includes for the scope as HTML tags. */ function drupal_get_js($scope = 'header', $javascript = NULL) { - global $update_mode; - if (empty($update_mode) && function_exists('locale_update_js_files')) { + if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) { locale_update_js_files(); } @@ -2037,10 +2041,18 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { $preprocessed = ''; $no_preprocess = array('core' => '', 'module' => '', 'theme' => ''); $files = array(); - $preprocess_js = (variable_get('preprocess_js', FALSE) && empty($update_mode)); + $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); $directory = file_directory_path(); $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + // A dummy query-string is added to filenames, to gain control over + // browser-caching. The string changes on every update or full cache + // flush, forcing browsers to load a new copy of the files, as the + // URL changed. Files that should not be cached (see drupal_add_js()) + // get time() as query-string instead, to enforce reload on every + // page request. + $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1); + foreach ($javascript as $type => $data) { if (!$data) continue; @@ -2059,7 +2071,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. foreach ($data as $path => $info) { if (!$info['preprocess'] || !$is_writable || !$preprocess_js) { - $no_preprocess[$type] .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . ($info['cache'] ? '' : '?'. time()) ."\"></script>\n"; + $no_preprocess[$type] .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . ($info['cache'] ? $query_string : '?'. time()) ."\"></script>\n"; } else { $files[$path] = $info; @@ -2070,7 +2082,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { // Aggregate any remaining JS files that haven't already been output. if ($is_writable && $preprocess_js && count($files) > 0) { - $filename = md5(serialize($files)) .'.js'; + $filename = md5(serialize($files) . $query_string) .'.js'; $preprocess_file = drupal_build_js_cache($files, $filename); $preprocessed .= '<script type="text/javascript" src="'. base_path() . $preprocess_file .'"></script>'."\n"; } @@ -2412,7 +2424,6 @@ function xmlrpc($url) { function _drupal_bootstrap_full() { static $called; - global $update_mode; if ($called) { return; @@ -2440,7 +2451,7 @@ function _drupal_bootstrap_full() { module_load_all(); // Let all modules take action before menu system handles the request // We do not want this while running update.php. - if (empty($update_mode)) { + if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('init'); } } @@ -3506,6 +3517,9 @@ function drupal_implode_tags($tags) { * exposes a hook for other modules to clear their own cache data as well. */ function drupal_flush_all_caches() { + // Change query-strings on css/js files to enforce reload for all users. + _drupal_flush_css_js(); + drupal_clear_css_cache(); drupal_clear_js_cache(); drupal_rebuild_theme_registry(); @@ -3519,3 +3533,22 @@ function drupal_flush_all_caches() { cache_clear_all('*', $table, TRUE); } } + +/** + * Helper function to change query-strings on css/js files. + * + * Changes the character added to all css/js files as dummy query-string, + * so that all browsers are forced to reload fresh files. We keep + * 20 characters history (FIFO) to avoid repeats, but only the first + * (newest) character is actually used on urls, to keep them short. + * This is also called from update.php. + */ +function _drupal_flush_css_js() { + $string_history = variable_get('css_js_query_string', '00000000000000000000'); + $new_character = $string_history[0]; + $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + while (strpos($string_history, $new_character) !== FALSE) { + $new_character = $characters[mt_rand(0, strlen($characters) - 1)]; + } + variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19)); +} diff --git a/install.php b/install.php index 4264931505b3bf67a0dca4dbb3fd3bc4c0211176..2b83a3c52825f86e6359d43bf88deedb47b79884 100644 --- a/install.php +++ b/install.php @@ -792,6 +792,10 @@ function install_tasks($profile, $task) { // Register actions declared by any modules. actions_synchronize(); + // Randomize query-strings on css/js files, to hide the fact that + // this is a new install, not upgraded yet. + _drupal_flush_css_js(); + variable_set('install_profile', $profile); } diff --git a/update.php b/update.php index 235eed624578e3f54b7b1ccd7b4847cce1650daa..3ddf1043500202cf0aec0ea62abb357af134fe73 100644 --- a/update.php +++ b/update.php @@ -13,6 +13,12 @@ * be sure to open settings.php again, and change it back to its original state! */ +/** + * Global flag to identify update.php run, and so avoid various unwanted + * operations, such as hook_init() and hook_exit() invokes, css/js preprocessing + * and translation, and solve some theming issues. This flag is checked on several + * places in Drupal code (not just update.php). + */ define('MAINTENANCE_MODE', 'update'); /** @@ -264,11 +270,7 @@ function update_batch() { function update_finished($success, $results, $operations) { // clear the caches in case the data has been updated. - cache_clear_all('*', 'cache', TRUE); - cache_clear_all('*', 'cache_page', TRUE); - cache_clear_all('*', 'cache_filter', TRUE); - drupal_clear_css_cache(); - drupal_clear_js_cache(); + drupal_flush_all_caches(); $_SESSION['update_results'] = $results; $_SESSION['update_success'] = $success; @@ -340,6 +342,9 @@ function update_results_page() { } function update_info_page() { + // Change query-strings on css/js files to enforce reload for all users. + _drupal_flush_css_js(); + update_task_list('info'); drupal_set_title('Drupal database update'); $output = '<p>Use this utility to update your database whenever a new release of Drupal or a module is installed.</p><p>For more detailed information, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.</p>'; @@ -516,11 +521,6 @@ function update_task_list($active = NULL) { include_once './includes/bootstrap.inc'; -// Bootstrap Drupal in a safe way, without calling hook_init() and hook_exit(), -// to avoid possible warnings. We need to set the global variable after -// DRUPAL_BOOTSTRAP_CONFIGURATION, which unsets globals, but before the rest. -drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); -$update_mode = TRUE; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); drupal_maintenance_theme();