Commit feb48454 authored by Dries's avatar Dries

- Patch #499156 by Wim Leers: add hook_file_alter() so we can integrate with CDNs.

parent 0597c9e4
......@@ -285,13 +285,23 @@ function file_stream_wrapper_get_instance_by_scheme($scheme) {
}
/**
* Creates the web accessible URL to a stream.
* Creates a web-accessible URL for a stream to an external or local file.
*
* Compatibility: normal paths and stream wrappers.
* @see http://drupal.org/node/515192
*
* There are two kinds of local 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.
*
* @param $uri
* The URI to for which we need an external URL.
* The URI to a file for which we need an external URL, or the path to a
* shipped file.
* @return
* A string containing a URL that may be used to access the file.
* If the provided string already contains a preceding 'http', nothing is done
......@@ -299,11 +309,15 @@ function file_stream_wrapper_get_instance_by_scheme($scheme) {
* found to generate an external URL, then FALSE will be returned.
*/
function file_create_url($uri) {
// Allow the URI to be altered, e.g. to serve a file from a CDN or static
// file server.
drupal_alter('file_url', $uri);
$scheme = file_uri_scheme($uri);
if (!$scheme) {
// If this is not a properly formatted stream return the URI with the base
// url prepended.
// If this is not a properly formatted stream, then it is a shipped file.
// Therefor, return the URI with the base URL prepended.
return $GLOBALS['base_url'] . '/' . $uri;
}
elseif ($scheme == 'http' || $scheme == 'https') {
......@@ -320,9 +334,6 @@ function file_create_url($uri) {
return FALSE;
}
}
// @todo Implement CDN integration hook stuff in this function.
// @see http://drupal.org/node/499156
}
/**
......
......@@ -1878,6 +1878,26 @@ class FileDownloadTest extends FileTestCase {
parent::setUp('file_test');
}
/**
* Test the public file transfer system.
*/
function testPublicFileTransfer() {
// Test generating an URL to a created file.
$file = $this->createFile();
$url = file_create_url($file->uri);
$this->assertEqual($GLOBALS['base_url'] . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created file.'));
$this->drupalHead($url);
$this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the 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).
$filepath = 'misc/jquery.js';
$url = file_create_url($filepath);
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
$this->drupalHead($url);
$this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the shipped file.'));
}
/**
* Test the private file transfer system.
*/
......@@ -1907,6 +1927,48 @@ class FileDownloadTest extends FileTestCase {
}
}
/**
* Tests for file URL rewriting.
*/
class FileURLRewritingTest extends FileTestCase {
public static function getInfo() {
return array(
'name' => t('File URL rewriting'),
'description' => t('Tests for file URL rewriting.'),
'group' => t('File'),
);
}
function setUp() {
parent::setUp('file_test');
variable_set('file_test_hook_file_url_alter', TRUE);
}
/**
* Test the generating of rewritten shipped file URLs.
*/
function testShippedFileURL() {
// 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).
$filepath = 'misc/jquery.js';
$url = file_create_url($filepath);
$this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
$filepath = 'misc/favicon.ico';
$url = file_create_url($filepath);
$this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
}
/**
* Test the generating of rewritten public created file URLs.
*/
function testPublicCreatedFileURL() {
// Test generating an URL to a created file.
$file = $this->createFile();
$url = file_create_url($file->uri);
$this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . file_directory_path() . '/' . $file->filename, $url, t('Correctly generated a URL for a created file.'));
}
}
/**
* Tests for file_munge_filename() and file_unmunge_filename().
*/
......
......@@ -9,6 +9,11 @@
* calling file_test_get_calls() or file_test_set_return().
*/
define('FILE_URL_TEST_CDN_1', 'http://cdn1.example.com');
define('FILE_URL_TEST_CDN_2', 'http://cdn2.example.com');
/**
* Implement hook_menu().
*/
......@@ -264,6 +269,51 @@ function file_test_file_delete($file) {
_file_test_log_call('delete', array($file));
}
/**
* Implement hook_file_url_alter().
*/
function file_test_file_url_alter(&$uri) {
// Only run this hook when this variable is set. Otherwise, we'd have to add
// another hidden test module just for this hook.
if (!variable_get('file_test_hook_file_url_alter', FALSE)) {
return;
}
$cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png');
// Most CDNs don't support private file transfers without a lot of hassle,
// so don't support this in the common case.
$schemes = array('public');
$scheme = file_uri_scheme($uri);
// Only serve shipped files and public created files from the CDN.
if (!$scheme || in_array($scheme, $schemes)) {
// Shipped files.
if (!$scheme) {
$path = $uri;
}
// Public created files.
else {
$wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
}
// Clean up Windows paths.
$path = str_replace('\\', '/', $path);
// Serve 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)) {
$uri = FILE_URL_TEST_CDN_1 . '/' . $path;
}
else {
$uri = FILE_URL_TEST_CDN_2 . '/' . $path;
}
}
}
/**
* Helper class for testing the stream wrapper registry.
*
......
......@@ -1449,6 +1449,67 @@ function hook_file_download($filepath) {
}
/**
* Alter the URL to a file.
*
* This hook is called from file_create_url(), and is called fairly
* frequently (10+ times per page), depending on how many files there are in a
* given page.
* If CSS and JS aggregation are disabled, this can become very frequently
* (50+ times per page) so performance is critical.
*
* This function should alter the URI, if it wants to rewrite the file URL.
* If it does so, no other hook_file_url_alter() implementation will be
* allowed to further alter the path.
*
* @param $uri
* The URI to a file for which we need an external URL, or the path to a
* shipped file.
*/
function hook_file_url_alter(&$uri) {
global $user;
// User 1 will always see the local file in this example.
if ($user->uid == 1) {
return;
}
$cdn1 = 'http://cdn1.example.com';
$cdn2 = 'http://cdn2.example.com';
$cdn_extensions = array('css', 'js', 'gif', 'jpg', 'jpeg', 'png');
// Most CDNs don't support private file transfers without a lot of hassle,
// so don't support this in the common case.
$schemes = array('public');
$scheme = file_uri_scheme($uri);
// Only serve shipped files and public created files from the CDN.
if (!$scheme || in_array($scheme, $schemes)) {
// Shipped files.
if (!$scheme) {
$path = $uri;
}
// Public created files.
else {
$wrapper = file_stream_wrapper_get_instance_by_scheme($scheme);
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
}
// Clean up Windows paths.
$path = str_replace('\\', '/', $path);
// Serve 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)) {
$uri = $cdn1 . '/' . $path;
}
else {
$uri = $cdn2 . '/' . $path;
}
}
}
/**
* Check installation requirements and do status reporting.
*
* This hook has two closely related uses, determined by the $phase argument:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment