Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.945 diff -u -F^f -r1.945 common.inc --- includes/common.inc 29 Jul 2009 06:39:33 -0000 1.945 +++ includes/common.inc 29 Jul 2009 18:04:47 -0000 @@ -2496,15 +2496,15 @@ 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 .= '' . "\n"; + $no_module_preprocess .= '' . "\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. elseif (!$preprocess && $type == 'theme') { - $no_theme_preprocess .= '' . "\n"; + $no_theme_preprocess .= '' . "\n"; } else { - $output .= '' . "\n"; + $output .= '' . "\n"; } } } @@ -2516,7 +2516,7 @@ function drupal_get_css($css = NULL) { // starting with "ad*". $filename = 'css_' . md5(serialize($types) . $query_string) . '.css'; $preprocess_file = drupal_build_css_cache($types, $filename); - $output .= '' . "\n"; + $output .= '' . "\n"; } } if (!empty($no_inline_preprocess)) { @@ -2962,7 +2962,7 @@ function drupal_get_js($scope = 'header' case 'file': if (!$item['preprocess'] || !$is_writable || !$preprocess_js) { - $no_preprocess .= '\n"; + $no_preprocess .= '\n"; } else { $files[$item['data']] = $item; @@ -2982,7 +2982,7 @@ function drupal_get_js($scope = 'header' // starting with "ad*". $filename = 'js_' . md5(serialize($files) . $query_string) . '.js'; $preprocess_file = drupal_build_js_cache($files, $filename); - $preprocessed .= '' . "\n"; + $preprocessed .= '' . "\n"; } // Keep the order of JS files consistent as some are preprocessed and others are not. Index: includes/file.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/file.inc,v retrieving revision 1.178 diff -u -F^f -r1.178 file.inc --- includes/file.inc 27 Jul 2009 19:53:17 -0000 1.178 +++ includes/file.inc 29 Jul 2009 18:04:48 -0000 @@ -301,18 +301,60 @@ function file_stream_wrapper_get_instanc /** * Create the download path to a file. * - * @param $path A string containing the path of the file to generate URL for. - * @return A string containing a URL that can be used to download the file. + * There are two kinds of files: + * - "created files", i.e. those in the files directory (which is stored in + * the file_directory_path variable and can be retrieved using + * file_directory_path()). These are files that have either been uploaded by + * users or were generated automatically (for example through CSS + * aggregation). + * - "shipped files", i.e. those outside of the files directory, which ship as + * part of Drupal core or contributed modules or themes. + * When a hook_file_url_alter() function is defined and overrides the path, + * then that rewritten path is used instead of creating a URL for the file at + * the given path. + * + * @param $path + * A string containing the Drupal path (i.e. path relative to the Drupal + * root directory) of the file to generate the URL for. + * @return + * A string containing a URL that can be used to download the file. */ function file_create_url($path) { - // Strip file_directory_path from $path. We only include relative paths in - // URLs. - $path = file_directory_strip($path); - switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) { - case FILE_DOWNLOADS_PUBLIC: - return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path); - case FILE_DOWNLOADS_PRIVATE: - return url('system/files/' . $path, array('absolute' => TRUE)); + // Clean up Windows paths. + $old_path = $path = str_replace('\\', '/', $path); + + drupal_alter('file_url', $path); + + // If any module has altered the path, then return the alteration. + if ($path != $old_path) { + return $path; + } + + // Otherwise serve the file from Drupal's web server. This point will only + // be reached when either no custom_file_url_rewrite() function has been + // defined, or when that function returned FALSE, thereby indicating that it + // cannot (or doesn't wish to) rewrite the URL. This is typically because + // the file doesn't match some conditions to be served from a CDN or static + // file server, or because the file has not yet been synced to the CDN or + // static file server. + + // Shipped files. + if (strpos($path, file_directory_path() . '/') !== 0) { + return base_path() . $path; + } + // Created files. + else { + switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) { + case FILE_DOWNLOADS_PUBLIC: + return $GLOBALS['base_url'] . '/' . $path; + case FILE_DOWNLOADS_PRIVATE: + // Strip file_directory_path from $path. Private downloads' URLs are + // rewritten to be served relatively to system/files (which is a menu + // callback that streams the file) instead of relatively to the file + // directory path. + $path = file_directory_strip($path); + return url('system/files/' . $path, array('absolute' => TRUE)); + } } } Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.354 diff -u -F^f -r1.354 form.inc --- includes/form.inc 28 Jul 2009 12:13:46 -0000 1.354 +++ includes/form.inc 29 Jul 2009 18:04:50 -0000 @@ -2452,7 +2452,7 @@ function theme_image_button($element) { (!empty($element['#value']) ? ('value="' . check_plain($element['#value']) . '" ') : '') . 'id="' . $element['#id'] . '" ' . drupal_attributes($element['#attributes']) . - ' src="' . base_path() . $element['#src'] . '" ' . + ' src="' . file_create_url($element['#src']) . '" ' . (!empty($element['#title']) ? 'alt="' . check_plain($element['#title']) . '" title="' . check_plain($element['#title']) . '" ' : '' ) . "/>\n"; } Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.500 diff -u -F^f -r1.500 theme.inc --- includes/theme.inc 27 Jul 2009 18:38:35 -0000 1.500 +++ includes/theme.inc 29 Jul 2009 18:04:52 -0000 @@ -1097,24 +1097,24 @@ function theme_get_setting($setting_name if ($settings['toggle_logo']) { if ($settings['default_logo']) { - $settings['logo'] = base_path() . dirname($theme_object->filename) . '/logo.png'; + $settings['logo'] = file_create_url(dirname($theme_object->filename) . '/logo.png'); } elseif ($settings['logo_path']) { - $settings['logo'] = base_path() . $settings['logo_path']; + $settings['logo'] = file_create_url($settings['logo_path']); } } if ($settings['toggle_favicon']) { if ($settings['default_favicon']) { if (file_exists($favicon = dirname($theme_object->filename) . '/favicon.ico')) { - $settings['favicon'] = base_path() . $favicon; + $settings['favicon'] = file_create_url($favicon); } else { - $settings['favicon'] = base_path() . 'misc/favicon.ico'; + $settings['favicon'] = file_create_url('misc/favicon.ico'); } } elseif ($settings['favicon_path']) { - $settings['favicon'] = base_path() . $settings['favicon_path']; + $settings['favicon'] = file_create_url($settings['favicon_path']); } else { $settings['toggle_favicon'] = FALSE; @@ -1338,7 +1338,7 @@ function theme_links($links, $attributes function theme_image($path, $alt = '', $title = '', $attributes = array(), $getsize = TRUE) { if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) { $attributes = drupal_attributes($attributes); - $url = (url($path) == $path) ? $path : (base_path() . $path); + $url = (url($path) == $path) ? $path : file_create_url($path); return '' . check_plain($alt) . ''; } } Index: modules/simpletest/tests/file.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v retrieving revision 1.37 diff -u -F^f -r1.37 file.test --- modules/simpletest/tests/file.test 27 Jul 2009 19:53:18 -0000 1.37 +++ modules/simpletest/tests/file.test 29 Jul 2009 18:04:54 -0000 @@ -1913,6 +1913,25 @@ function file_test_file_scan_callback_re } /** + * Test the generating of public file URLs. + */ + function testPublicFileURL() { + // Set file downloads to public. + variable_set('file_downloads', FILE_DOWNLOADS_PUBLIC); + + // Test generating an URL to a created file. + $file = $this->createFile(); + $url = file_create_url($file->filepath); + $this->assertEqual($GLOBALS['base_url'] . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created file.')); + + // Test generating an URL to a shipped file (i.e. a file that is part of + // Drupal core, a module or a theme, for example a JavaScript file). + $file = 'misc/jquery.js'; + $url = file_create_url($file); + $this->assertEqual(base_path() . $file, $url, t('Correctly generated a URL for a shipped file.')); + } + + /** * Test the private file transfer system. */ function testPrivateFileTransfer() { @@ -1921,7 +1940,7 @@ function file_test_file_scan_callback_re // Create a file. $file = $this->createFile(); - $url = file_create_url($file->filename); + $url = file_create_url($file->filepath); // Set file_test access header to allow the download. file_test_set_return('download', array('x-foo' => 'Bar')); @@ -1943,6 +1962,45 @@ function file_test_file_scan_callback_re } /** + * Tests for file URL rewriting. + */ +class FileURLRewritingTest extends FileDownloadTest { + public static function getInfo() { + return array( + 'name' => t('File URL rewriting'), + 'description' => t('Tests for file URL rewriting.'), + 'group' => t('File'), + ); + } + + function setUp() { + DrupalWebTestCase::setUp('file_test', 'file_url_test'); + } + + /** + * Test the generating of rewritten public file URLs. + */ + function testPublicFileURL() { + // Set file downloads to public. + variable_set('file_downloads', FILE_DOWNLOADS_PUBLIC); + + // Test generating an URL to a created file. + $file = $this->createFile(); + $url = file_create_url($file->filepath); + $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $file->filepath, $url, t('Correctly generated a URL for a created file.')); + + // Test generating an URL to a shipped file (i.e. a file that is part of + // Drupal core, a module or a theme, for example a JavaScript file). + $file = 'misc/jquery.js'; + $url = file_create_url($file); + $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $file, $url, t('Correctly generated a URL for a shipped file.')); + $file = 'misc/favicon.ico'; + $url = file_create_url($file); + $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $file, $url, t('Correctly generated a URL for a shipped file.')); + } +} + +/** * Tests for file_munge_filename() and file_unmunge_filename(). */ class FileNameMungingTest extends FileTestCase { Index: modules/simpletest/tests/file_url_test.info =================================================================== RCS file: modules/simpletest/tests/file_url_test.info diff -N modules/simpletest/tests/file_url_test.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/file_url_test.info 29 Jul 2009 18:04:54 -0000 @@ -0,0 +1,8 @@ +; $Id$ +name = "File URL test" +description = "Support module for file URL rewrite tests." +package = Testing +version = VERSION +core = 7.x +files[] = file_url_test.module +hidden = TRUE Index: modules/simpletest/tests/file_url_test.module =================================================================== RCS file: modules/simpletest/tests/file_url_test.module diff -N modules/simpletest/tests/file_url_test.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/file_url_test.module 29 Jul 2009 18:04:55 -0000 @@ -0,0 +1,37 @@ +uid == 1) { + return; + } + + $cdn1 = 'http://cdn1.example.com'; + $cdn2 = 'http://cdn2.example.com'; + $cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png'); + + // Most CDN's don't support private file transfers without a lot of hassle, + // so don't support this in the common case. + if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) { + return; + } + + // Serve files without extension and files with one of the CDN extensions + // from CDN 1, all others from CDN 2. + $pathinfo = pathinfo($path); + if (!array_key_exists('extension', $pathinfo) || in_array($pathinfo['extension'], $cdn_extensions)) { + $path = $cdn1 . '/' . $path; + } + else { + $path = $cdn2 . '/' . $path; + } +} + +/** * Load additional information into file objects. * * file_load_multiple() calls this hook to allow modules to load Index: themes/garland/template.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/template.php,v retrieving revision 1.23 diff -u -F^f -r1.23 template.php --- themes/garland/template.php 28 Jul 2009 10:09:25 -0000 1.23 +++ themes/garland/template.php 29 Jul 2009 18:04:56 -0000 @@ -79,9 +79,9 @@ function garland_node_submitted($node) { function garland_get_ie_styles() { global $language; - $ie_styles = '' . "\n"; + $ie_styles = '' . "\n"; if ($language->direction == LANGUAGE_RTL) { - $ie_styles .= ' ' . "\n"; + $ie_styles .= ' ' . "\n"; } return $ie_styles;