diff --git a/includes/common.inc b/includes/common.inc index d7189ab..7743b11 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -86,6 +86,34 @@ define('JS_DEFAULT', 0); define('JS_THEME', 100); /** + * Constants defining aggregation of JavaScript and CSS files. + */ + +/** + * No aggregation. + */ +define('DRUPAL_AGGREGATION_DISABLED', 0); + +/** + * Default aggregation. The filename of the aggregate file is an md5 hash of + * the array of source file information. The last modified timestamp of each + * source file is part of this information, so when a source file is updated, it + * results in a new aggregate file being made. + */ +define('DRUPAL_AGGREGATION_ENABLED', 1); + +/** + * Optimized aggregation. The filename of the aggregate file is an md5 hash of + * the array of source file information. The last modified timestamp of each + * source file is not part of this information, so modification of source files + * does not automatically result in a new aggregate file. This saves on file + * stats, which can be significant for high traffic websites, but requires the + * site administrator to empty the aggregation cache whenever a source file is + * modified. + */ +define('DRUPAL_AGGREGATION_ENABLED_WITHOUT_STAT', 2); + +/** * Error code indicating that the request made by drupal_http_request() exceeded * the specified timeout. */ @@ -3113,8 +3141,11 @@ function drupal_group_css($css) { * @see drupal_pre_render_styles() */ function drupal_aggregate_css(&$css_groups) { - $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); - + $preprocess_css = variable_get('preprocess_css', DRUPAL_AGGREGATION_ENABLED); + // Force aggregation to disabled if we are in maintanance mode. + if (defined('MAINTENANCE_MODE')) { + $preprocess_css = DRUPAL_AGGREGATION_DISABLED; + } // For each group that needs aggregation, aggregate its items. foreach ($css_groups as $key => $group) { switch ($group['type']) { @@ -3122,6 +3153,19 @@ function drupal_aggregate_css(&$css_groups) { // the group's data property to the file path of the aggregate file. case 'file': if ($group['preprocess'] && $preprocess_css) { + // Unless aggregation is enabled without file stats, add each file's + // last modified timestamp to the information from which the aggregate + // filename will be generated. + if ($preprocess_css != DRUPAL_AGGREGATION_ENABLED_WITHOUT_STAT) { + foreach ($group['items'] as &$item) { + // Don't override information that may have been set earlier, such + // as in hook_css_alter(). + if (!isset($item['mtime']) && file_exists($item['data'])) { + $item['mtime'] = filemtime($item['data']); + } + } + unset($item); + } $css_groups[$key]['data'] = drupal_build_css_cache($group['items']); } break; @@ -4095,7 +4139,11 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS $index = 1; $processed = array(); $files = array(); - $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); + $preprocess_js = variable_get('preprocess_js', DRUPAL_AGGREGATION_ENABLED); + // Force aggregation to disabled if we are in maintanance mode. + if (defined('MAINTENANCE_MODE')) { + $preprocess_js = DRUPAL_AGGREGATION_DISABLED; + } // A dummy query-string is added to filenames, to gain control over // browser-caching. The string changes on every update or full cache @@ -4204,6 +4252,19 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS // Aggregate any remaining JS files that haven't already been output. if ($preprocess_js && count($files) > 0) { foreach ($files as $key => $file_set) { + // Unless aggregation is enabled without file stats, add each file's + // last modified timestamp to the information from which the aggregate + // filename will be generated. + if ($preprocess_js != DRUPAL_AGGREGATION_ENABLED_WITHOUT_STAT) { + foreach ($file_set as &$item) { + // Don't override information that may have been set earlier, such + // as in hook_js_alter(). + if (!isset($item['mtime']) && file_exists($item['data'])) { + $item['mtime'] = filemtime($item['data']); + } + } + unset($item); + } $uri = drupal_build_js_cache($file_set); // Only include the file if was written successfully. Errors are logged // using watchdog. diff --git a/modules/color/color.test b/modules/color/color.test index 897bd6c..ae27e2d 100644 --- a/modules/color/color.test +++ b/modules/color/color.test @@ -41,6 +41,7 @@ class ColorTestCase extends DrupalWebTestCase { ), ); theme_enable(array_keys($this->themes)); + variable_set('preprocess_css', DRUPAL_AGGREGATION_DISABLED); // Array filled with valid and not valid color values $this->colorTests = array( diff --git a/modules/profile/profile.test b/modules/profile/profile.test index 83bed25..5207c56 100644 --- a/modules/profile/profile.test +++ b/modules/profile/profile.test @@ -343,7 +343,12 @@ class ProfileTestAutocomplete extends ProfileTestCase { $field_html = ''; // Check that autocompletion html is found on the user's profile edit page. + // Since we're checking for autocomplete.js in the HTML source, we need to + // disable JavaScript aggregation for this request. + $preprocess_js = variable_get('preprocess_js', DRUPAL_AGGREGATION_ENABLED); + variable_set('preprocess_js', DRUPAL_AGGREGATION_DISABLED); $this->drupalGet('user/' . $this->admin_user->uid . '/edit/' . $category); + variable_set('preprocess_js', $preprocess_js); $this->assertRaw($autocomplete_html, t('Autocomplete found.')); $this->assertRaw('misc/autocomplete.js', t('Autocomplete JavaScript found.')); $this->assertRaw('class="form-text form-autocomplete"', t('Autocomplete form element class found.')); diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 177e457..e135d4b 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -558,6 +558,11 @@ class DrupalTagsHandlingTestCase extends DrupalWebTestCase { * Test the Drupal CSS system. */ class CascadingStylesheetsTestCase extends DrupalWebTestCase { + /** + * Store configured value for CSS preprocessing. + */ + protected $preprocess_css = NULL; + public static function getInfo() { return array( 'name' => 'Cascading stylesheets', @@ -568,10 +573,21 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('php', 'locale', 'common_test'); + + // Disable preprocessing. + $this->preprocess_css = variable_get('preprocess_css', DRUPAL_AGGREGATION_ENABLED); + variable_set('preprocess_css', DRUPAL_AGGREGATION_DISABLED); + // Reset drupal_add_css() before each test. drupal_static_reset('drupal_add_css'); } + function tearDown() { + // Restore configured value for CSS preprocessing. + variable_set('preprocess_css', $this->preprocess_css); + parent::tearDown(); + } + /** * Check default stylesheets as empty. */ @@ -1147,8 +1163,8 @@ class JavaScriptTestCase extends DrupalWebTestCase { parent::setUp('locale', 'simpletest', 'common_test'); // Disable preprocessing - $this->preprocess_js = variable_get('preprocess_js', 0); - variable_set('preprocess_js', 0); + $this->preprocess_js = variable_get('preprocess_js', DRUPAL_AGGREGATION_ENABLED); + variable_set('preprocess_js', DRUPAL_AGGREGATION_DISABLED); // Reset drupal_add_js() and drupal_add_library() statics before each test. drupal_static_reset('drupal_add_js'); diff --git a/modules/simpletest/tests/common_test.module b/modules/simpletest/tests/common_test.module index 9b61788..bfbc596 100644 --- a/modules/simpletest/tests/common_test.module +++ b/modules/simpletest/tests/common_test.module @@ -221,7 +221,7 @@ function common_test_library() { function common_test_js_and_css_querystring() { drupal_add_js(drupal_get_path('module', 'node') . '/node.js'); drupal_add_css(drupal_get_path('module', 'node') . '/node.css'); - // A relative URI may have a query string. - drupal_add_css('/' . drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2'); + // A relative URI may have a query string, but shouldn't be preprocessed. + drupal_add_css('/' . drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2', array('preprocess' => FALSE)); return ''; } diff --git a/modules/simpletest/tests/menu.test b/modules/simpletest/tests/menu.test index bb01265..c8be661 100644 --- a/modules/simpletest/tests/menu.test +++ b/modules/simpletest/tests/menu.test @@ -6,6 +6,11 @@ */ class MenuRouterTestCase extends DrupalWebTestCase { + /** + * Store configured value for CSS preprocessing. + */ + protected $preprocess_css = NULL; + public static function getInfo() { return array( 'name' => 'Menu router', @@ -22,6 +27,18 @@ class MenuRouterTestCase extends DrupalWebTestCase { theme_enable(array('bartik')); variable_set('theme_default', 'bartik'); variable_set('admin_theme', 'seven'); + + // Several tests determine which theme got used by asserting the existence + // of a particular CSS file in the HTML source, so we disable CSS + // aggregation for these tests. + $this->preprocess_css = variable_get('preprocess_css', DRUPAL_AGGREGATION_ENABLED); + variable_set('preprocess_css', DRUPAL_AGGREGATION_DISABLED); + } + + function tearDown() { + // Restore configured value for CSS preprocessing. + variable_set('preprocess_css', $this->preprocess_css); + parent::tearDown(); } /** diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test index f1e1bd5..3ab89ec 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -20,6 +20,7 @@ class ThemeUnitTest extends DrupalWebTestCase { function setUp() { parent::setUp('theme_test'); theme_enable(array('test_theme')); + variable_set('preprocess_css', DRUPAL_AGGREGATION_DISABLED); } /** diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 9e7d69d..ca337f4 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1696,15 +1696,25 @@ function system_performance_settings() { '#suffix' => '', ); $form['bandwidth_optimization']['preprocess_css'] = array( - '#type' => 'checkbox', + '#type' => 'radios', '#title' => t('Aggregate and compress CSS files.'), - '#default_value' => intval(variable_get('preprocess_css', 0) && $is_writable), + '#options' => array( + DRUPAL_AGGREGATION_DISABLED => t('Disabled. This can be useful during development, but slows down page download and display.'), + DRUPAL_AGGREGATION_ENABLED => t('Normal (recommended)'), + DRUPAL_AGGREGATION_ENABLED_WITHOUT_STAT => t('Aggressive. This setting skips the normal verification of changes to source CSS files, potentially reducing disk load on high traffic website.'), + ), + '#default_value' => $is_writable ? variable_get('preprocess_css', DRUPAL_AGGREGATION_ENABLED) : 0, '#disabled' => $disabled, ); $form['bandwidth_optimization']['preprocess_js'] = array( - '#type' => 'checkbox', + '#type' => 'radios', '#title' => t('Aggregate JavaScript files.'), - '#default_value' => intval(variable_get('preprocess_js', 0) && $is_writable), + '#options' => array( + DRUPAL_AGGREGATION_DISABLED => t('Disabled. This can be useful during development, but slows down page download and display.'), + DRUPAL_AGGREGATION_ENABLED => t('Normal (recommended)'), + DRUPAL_AGGREGATION_ENABLED_WITHOUT_STAT => t('Aggressive. This setting skips the normal verification of changes to source JavaScript files, potentially reducing disk load on high traffic website.'), + ), + '#default_value' => $is_writable ? variable_get('preprocess_js', DRUPAL_AGGREGATION_ENABLED) : 0, '#disabled' => $disabled, );