Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.850 diff -u -p -r1.850 common.inc --- includes/common.inc 19 Jan 2009 21:13:09 -0000 1.850 +++ includes/common.inc 20 Jan 2009 00:24:06 -0000 @@ -2538,8 +2538,12 @@ function drupal_get_js($scope = 'header' $no_preprocess = ''; $files = array(); $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); + if ($preprocess_js) { + // Only check the preprocessor requirements if it's going to be + // preprocessing. If these requirements are not met, preprocessing + // will be disabled. + $preprocess_js = drupal_js_preprocessor()->requirements(); + } // A dummy query-string is added to filenames, to gain control over // browser-caching. The string changes on every update or full cache @@ -2570,7 +2574,7 @@ function drupal_get_js($scope = 'header' break; case 'file': - if (!$item['preprocess'] || !$is_writable || !$preprocess_js) { + if (!$item['preprocess'] || !$preprocess_js) { $no_preprocess .= '\n"; } else { @@ -2581,10 +2585,8 @@ function drupal_get_js($scope = 'header' } // Aggregate any remaining JS files that haven't already been output. - if ($is_writable && $preprocess_js && count($files) > 0) { - $filename = md5(serialize($files) . $query_string) . '.js'; - $preprocess_file = drupal_build_js_cache($files, $filename); - $preprocessed .= '' . "\n"; + if ($preprocess_js && count($files)) { + $preprocessed .= drupal_js_preprocessor()->preprocess($files); } // Keep the order of JS files consistent as some are preprocessed and others are not. @@ -2724,36 +2726,54 @@ function drupal_add_tabledrag($table_id, } /** - * Aggregate JS files, putting them in the files directory. - * - * @param $files - * An array of JS files to aggregate and compress into one file. - * @param $filename - * The name of the aggregate JS file. - * @return - * The name of the JS file. + * Returns a JavaScript preprocessing object that implements JSPreprocessingInterface. */ -function drupal_build_js_cache($files, $filename) { - $contents = ''; +function drupal_js_preprocessor() { + static $instance; - // Create the js/ within the files folder. - $jspath = file_create_path('js'); - file_check_directory($jspath, FILE_CREATE_DIRECTORY); - - if (!file_exists($jspath . '/' . $filename)) { - // Build aggregate JS file. - foreach ($files as $path => $info) { - if ($info['preprocess']) { - // Append a ';' after each JS file to prevent them from running together. - $contents .= file_get_contents($path) . ';'; + if (empty($instance)) { + // Retrieve the preprocess system. + $class = variable_get('preprocess_js_system', 'DrupalPreprocessJS'); + + // Allow for lazy loading of the class. + if (drupal_autoload_class($class)) { + $interfaces = class_implements($class); + if (isset($interfaces['JSPreprocessingInterface'])) { + $instance = new $class; + } + else { + throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'JSPreprocessingInterface'))); } } - - // Create the JS file. - file_unmanaged_save_data($contents, $jspath . '/' . $filename, FILE_EXISTS_REPLACE); + else { + throw new Exception(t('Class %class was not found.', array('%class' => $class))); + } } + return $instance; +} - return $jspath . '/' . $filename; +/** + * The interface for preprocessing JavaScript. + */ +interface JSPreprocessingInterface { + /** + * Check preprocessing requirements. + * + * @return + * TRUE if requirements met to preform preprocessing. + */ + public function requirements(); + + /** + * Preprocess JavaScript files. + * + * @param $files + * An array of JavaScript files. + * + * @return + * HTML for preprocessed JavaScript files. + */ + public function preprocess($files); } /** Index: includes/preprocess.inc =================================================================== RCS file: includes/preprocess.inc diff -N includes/preprocess.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/preprocess.inc 20 Jan 2009 00:24:06 -0000 @@ -0,0 +1,66 @@ +is_writable)) { + $directory = file_directory_path(); + $this->is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + } + return $this->is_writable; + } + + /** + * Aggregate JS files, putting them in the files directory. + * + * @param $files + * An array of JavaScript files to aggregate and compress into one file. + * + * @return + * The script tag containing the path to the cached JavaScript file. + */ + public function preprocess($files) { + $contents = ''; + + $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1); + $filename = md5(serialize($files) . $query_string) . '.js'; + + // Ensure there is a js directory within the files directory that it is writable. + $jspath = file_create_path('js'); + file_check_directory($jspath, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + + if (!file_exists($jspath . '/' . $filename)) { + // Build aggregate JS file. + foreach ($files as $path => $info) { + if ($info['preprocess']) { + // Append a ';' after each JS file to prevent them from running together. + $contents .= file_get_contents($path) . ';'; + } + } + + // Create the JS file. + file_unmanaged_save_data($contents, $jspath . '/' . $filename, FILE_EXISTS_REPLACE); + } + + return '' . "\n"; + } +} Index: modules/simpletest/tests/common.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v retrieving revision 1.21 diff -u -p -r1.21 common.test --- modules/simpletest/tests/common.test 11 Jan 2009 08:39:08 -0000 1.21 +++ modules/simpletest/tests/common.test 20 Jan 2009 00:24:07 -0000 @@ -197,6 +197,25 @@ class CascadingStylesheetsTestCase exten drupal_add_css($css); $this->assertTrue(strpos(drupal_get_css(), $css) > 0, t('Rendered CSS includes the added stylesheet.')); } + + /** + * Tests rendering inline stylesheets with preprocessing on. + */ + function testRenderInlinePreprocess() { + $css = 'body { padding: 0px; }'; + $css_preprocessed = drupal_load_stylesheet_content($css, TRUE); + drupal_add_css($css, 'inline'); + $this->assertTrue(strpos(drupal_get_css(), $css_preprocessed) > 0, t('Rendering preprocessed inline CSS adds it to the page.')); + } + + /** + * Tests rendering the stylesheets with preprocessing off. + */ + function testRenderInlineNoPreprocess() { + $css = 'body { padding: 0px; }'; + drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE)); + $this->assertTrue(strpos(drupal_get_css(), $css) > 0, t('Rendering non-preprocessed inline CSS adds it to the page.')); + } } /** @@ -329,11 +348,12 @@ class DrupalSetContentTestCase extends D /** * Tests for the JavaScript system. */ -class JavaScriptTestCase extends DrupalWebTestCase { +class JavaScriptTestCase extends DrupalWebTestCase implements JSPreprocessingInterface { /** - * Store configured value for JavaScript preprocessing. + * Store configured values for JavaScript preprocessing. */ var $preprocess_js = NULL; + var $js_preprocessor_class = 'DrupalPreprocessJS'; function getInfo() { return array( @@ -345,11 +365,13 @@ class JavaScriptTestCase extends DrupalW function setUp() { // Enable Locale and SimpleTest in the test environment. - parent::setUp('locale', 'simpletest'); + parent::setUp('locale', 'simpletest', 'javascript_test'); - // Disable preprocessing + // Disable preprocessing and use the default preprocessor system. $this->preprocess_js = variable_get('preprocess_js', 0); + $this->js_preprocessor_class = variable_get('preprocess_js_system', 'DrupalPreprocessJS'); variable_set('preprocess_js', 0); + variable_del('preprocess_js_system'); // Reset drupal_add_js() before each test. drupal_add_js(NULL, NULL, TRUE); @@ -358,6 +380,7 @@ class JavaScriptTestCase extends DrupalW function tearDown() { // Restore configured value for JavaScript preprocessing. variable_set('preprocess_js', $this->preprocess_js); + variable_set('preprocess_js_system', $this->js_preprocessor_class); parent::tearDown(); } @@ -470,6 +493,37 @@ class JavaScriptTestCase extends DrupalW $javascript = drupal_get_js(); $this->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'misc/tableselect.js'), t('Altering JavaScript weight through the alter hook.')); } + + // Static variables required to test the pluggable preprocessor system. + static $testFiles = NULL; + static $testRequirements = FALSE; + + /** + * Tests the pluggable JavaScript preprocessor system. + */ + function testPluggablePreprocessor() { + // Switch the preprocessor. + variable_set('preprocess_js', TRUE); + variable_set('preprocess_js_system', 'JavaScriptTestCase'); + + // Add some JavaScript to test the preprocessor system. + drupal_add_js('misc/tableselect.js'); + + // Retrieve the JavaScript, running through the JavaScriptTestCase preprocessor. + $javascript = drupal_get_js(); + $this->assertTrue(JavaScriptTestCase::$testRequirements, t('Pluggable preprocessor system requirements are checked.')); + $this->assertTrue(strpos($javascript, 'has been preprocessed') > 0 && !empty(JavaScriptTestCase::$testRequirements), t('Preprocessor system is pluggable.')); + } + + public function requirements() { + JavaScriptTestCase::$testRequirements = TRUE; + return TRUE; + } + + public function preprocess($files) { + JavaScriptTestCase::$testFiles = $files; + return 'JavaScript has been preprocessed.'; + } } /** Index: modules/simpletest/tests/javascript_test.info =================================================================== RCS file: modules/simpletest/tests/javascript_test.info diff -N modules/simpletest/tests/javascript_test.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/javascript_test.info 20 Jan 2009 00:24:07 -0000 @@ -0,0 +1,5 @@ +; $Id$ +name = "JavaScript tests" +description = "Support module for JavaScript testing." +package = Testing +files[] = common.test Index: modules/simpletest/tests/javascript_test.module =================================================================== RCS file: modules/simpletest/tests/javascript_test.module diff -N modules/simpletest/tests/javascript_test.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/javascript_test.module 20 Jan 2009 00:24:07 -0000 @@ -0,0 +1,8 @@ +