Commit 5468b47b authored by Dries's avatar Dries

- Patch #227232 by dopry, c960657, jmstacey, pwolanin, aaron, drewish: added...

- Patch #227232 by dopry, c960657, jmstacey, pwolanin, aaron, drewish: added initial support for PHP file wrappers.
parent 1aec2983
......@@ -103,6 +103,9 @@ Drupal 7.0, xxxx-xx-xx (development version)
uploading a site logo--that don't require the overhead of databases and
hooks, the current unmanaged copy, move and delete operations have been
preserved but renamed to file_unmanaged_*().
* Rewrote file handling to use PHP stream wrappers to enable support for
both public and private files and to support pluggable storage mechanisms
and access to remote resources (e.g. S3 storage or Flickr photos).
- Image handling:
* Improved image handling, including better support for add-on image
libraries.
......
......@@ -3451,7 +3451,8 @@ function _drupal_bootstrap_full() {
fix_gpc_magic();
// Load all enabled modules
module_load_all();
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
// Let all modules take action before menu system handles the request
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
......
......@@ -6,6 +6,13 @@
* API for handling file uploads and server file management.
*/
/**
* Stream wrapper code is included here because there are cases where
* File API is needed before a bootstrap, or in an alternate order (e.g.
* maintenance theme).
*/
require_once DRUPAL_ROOT . '/includes/stream_wrappers.inc';
/**
* @defgroup file File interface
* @{
......@@ -76,6 +83,221 @@
*/
define('FILE_STATUS_PERMANENT', 1);
/**
* Methods to manage a registry of stream wrappers.
*/
/**
* Drupal stream wrapper registry.
*
* A stream wrapper is an abstraction of a file system that allows Drupal to
* use the same set of methods to access both local files and remote resources.
*
* Provide a facility for managing and querying user-defined stream wrappers
* in PHP. PHP's internal stream_get_wrappers() doesn't return the class
* registered to handle a stream, which we need to be able to find the handler
* for class instantiation.
*
* If a module registers a scheme that is already registered with PHP, the
* existing scheme will be unregistered and replaced with the specified class.
*
* A stream is referenced as "scheme://target".
*
* @return
* Returns the entire Drupal stream wrapper registry.
* @see hook_stream_wrappers()
* @see hook_stream_wrappers_alter()
*/
function file_get_stream_wrappers() {
$wrappers = &drupal_static(__FUNCTION__);
if (!isset($wrappers)) {
$wrappers = module_invoke_all('stream_wrappers');
drupal_alter('stream_wrappers', $wrappers);
$existing = stream_get_wrappers();
foreach ($wrappers as $scheme => $info) {
// We only register classes that implement our interface.
if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) {
// Record whether we are overriding an existing scheme.
if (in_array($scheme, $existing, TRUE)) {
$wrappers[$scheme]['override'] = TRUE;
stream_wrapper_unregister($scheme);
}
else {
$wrappers[$scheme]['override'] = FALSE;
}
stream_wrapper_register($scheme, $info['class']);
}
}
}
return $wrappers;
}
/**
* Returns the stream wrapper class name for a given scheme.
*
* @param $scheme
* Stream scheme.
* @return
* Return string if a scheme has a registered handler, or FALSE.
*/
function file_stream_wrapper_get_class($scheme) {
$wrappers = file_get_stream_wrappers();
return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class'];
}
/**
* Returns the scheme of a URI (e.g. a stream).
*
* @param $uri
* A stream, referenced as "scheme://target".
* @return
* A string containing the name of the scheme, or FALSE if none. For example,
* the URI "public://example.txt" would return "public".
*/
function file_uri_scheme($uri) {
$data = explode('://', $uri, 2);
return count($data) == 2 ? $data[0] : FALSE;
}
/**
* Check that the scheme of a stream URI is valid.
*
* Confirms that there is a registered stream handler for the provided scheme
* and that it is callable. This is useful if you want to confirm a valid
* scheme without creating a new instance of the registered handler.
*
* @param $scheme
* A URI scheme, a stream is referenced as "scheme://target".
* @return
* Returns TRUE if the string is the name of a validated stream,
* or FALSE if the scheme does not have a registered handler.
*/
function file_stream_wrapper_valid_scheme($scheme) {
// Does the scheme have a registered handler that is callable?
$class = file_stream_wrapper_get_class($scheme);
if (class_exists($class)) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Returns the target of a URI (e.g. a stream).
*
* @param $uri
* A stream, referenced as "scheme://target".
* @return
* A string containing the target (path), or FALSE if none.
* For example, the URI "public://sample/test.txt" would return
* "sample/test.txt".
*/
function file_uri_target($uri) {
$data = explode('://', $uri, 2);
if (count($data) != 2) {
return FALSE;
}
// Remove erroneous beginning forward slash.
$data[1] = ltrim($data[1], '\/');
return $data[1];
}
/**
* Normalizes a URI by making it syntactically correct.
*
* A stream is referenced as "scheme://target".
*
* The following actions are taken:
* - Remove all occurrences of the wrapper's directory path
* - Remove trailing slashes from target
* - Trim erroneous leading slashes from target. e.g. ":///" becomes "://".
*
* @param $uri
* String reference containing the URI to normalize.
*/
function file_stream_wrapper_uri_normalize($uri) {
$scheme = file_uri_scheme($uri);
if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
$target = file_uri_target($uri);
// Remove all occurrences of the wrapper's directory path.
$directory_path = file_stream_wrapper_get_instance_by_scheme($scheme)->getDirectoryPath();
$target = str_replace($directory_path, '', $target);
// Trim trailing slashes from target.
$target = rtrim($target, '/');
// Trim erroneous leading slashes from target.
$uri = $scheme . '://' . ltrim($target, '/');
}
return $uri;
}
/**
* Returns a reference to the stream wrapper class responsible for a given URI (stream).
*
* The scheme determines the stream wrapper class that should be
* used by consulting the stream wrapper registry.
*
* @param $uri
* A stream, referenced as "scheme://target".
* @return
* Returns a new stream wrapper object appropriate for the given URI or FALSE
* if no registered handler could be found. For example, a URI of
* "private://example.txt" would return a new private stream wrapper object
* (DrupalPrivateStreamWrapper).
*/
function file_stream_wrapper_get_instance_by_uri($uri) {
$scheme = file_uri_scheme($uri);
$class = file_stream_wrapper_get_class($scheme);
if (class_exists($class)) {
$instance = new $class;
$instance->setUri($uri);
return $instance;
}
else {
return FALSE;
}
}
/**
* Returns a reference to the stream wrapper class responsible for a given scheme.
*
* This helper method returns a stream instance using a scheme. That is, the
* passed string does not contain a "://". For example, "public" is a scheme
* but "public://" is a URI (stream). This is because the later contains both
* a scheme and target despite target being empty.
*
* Note: the instance URI will be initialized to "scheme://" so that you can
* make the customary method calls as if you had retrieved an instance by URI.
*
* @param $scheme
* If the stream was "public://target", "public" would be the scheme.
* @return
* Returns a new stream wrapper object appropriate for the given $scheme.
* For example, for the public scheme a stream wrapper object
* (DrupalPublicStreamWrapper).
* FALSE is returned if no registered handler could be found.
*/
function file_stream_wrapper_get_instance_by_scheme($scheme) {
$class = file_stream_wrapper_get_class($scheme);
if (class_exists($class)) {
$instance = new $class;
$instance->setUri($scheme . '://');
return $instance;
}
else {
return FALSE;
}
}
/**
* Create the download path to a file.
*
......
This diff is collapsed.
......@@ -2077,3 +2077,80 @@ class FileMimeTypeTest extends DrupalWebTestCase {
}
}
}
/**
* Tests stream wrapper registry.
*/
class StreamWrapperRegistryTest extends DrupalWebTestCase {
protected $scheme = 'dummy';
protected $classname = 'DrupalDummyStreamWrapper';
public static function getInfo() {
return array(
'name' => 'Stream Wrapper Registry',
'description' => 'Tests stream wrapper registry.',
'group' => 'File',
);
}
function setUp() {
drupal_static_reset('file_get_stream_wrappers');
parent::setUp('file_test');
}
function tearDown() {
parent::tearDown();
stream_wrapper_unregister($this->scheme);
}
/**
* Test the getClassName() function.
*/
function testGetClassName() {
// Check the dummy scheme.
$this->assertEqual($this->classname, file_stream_wrapper_get_class($this->scheme), t('Got correct class name for dummy scheme.'));
// Check core's scheme.
$this->assertEqual('DrupalPublicStreamWrapper', file_stream_wrapper_get_class('public'), t('Got correct class name for public scheme.'));
}
/**
* Test the file_stream_wrapper_get_instance_by_scheme() function.
*/
function testGetInstanceByScheme() {
$instance = file_stream_wrapper_get_instance_by_scheme($this->scheme);
$this->assertEqual($this->classname, get_class($instance), t('Got correct class type for dummy scheme.'));
$instance = file_stream_wrapper_get_instance_by_scheme('public');
$this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public scheme.'));
}
/**
* Test the URI and target functions.
*/
function testGetInstanceByUri() {
$instance = file_stream_wrapper_get_instance_by_uri($this->scheme . '://foo');
$this->assertEqual($this->classname, get_class($instance), t('Got correct class type for dummy URI.'));
$instance = file_stream_wrapper_get_instance_by_uri('public://foo');
$this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public URI.'));
// Test file_stream_wrapper_uri_normalize.
$uri = 'public:///' . $this->originalFileDirectory . '/foo/bar/';
$uri = file_stream_wrapper_uri_normalize($uri);
$this->assertEqual('public://foo/bar', $uri, t('Got a properly normalized URI'));
// Test file_uri_taget().
$this->assertEqual('foo/bar.txt', file_uri_target('public://foo/bar.txt'), t('Got a valid stream target from public://foo/bar.txt'));
$this->assertFalse(file_uri_target('foo/bar.txt'), t('foo/bar.txt is not a valid stream.'));
}
/**
* Test the scheme functions.
*/
function testGetValidStreamScheme() {
$this->assertEqual('foo', file_uri_scheme('foo://pork//chops'), t('Got the correct scheme from foo://asdf'));
$this->assertTrue(file_stream_wrapper_valid_scheme(file_uri_scheme('public://asdf')), t('Got a valid stream scheme from public://asdf'));
$this->assertFalse(file_stream_wrapper_valid_scheme(file_uri_scheme('foo://asdf')), t('Did not get a valid stream scheme from foo://asdf'));
}
}
......@@ -23,6 +23,19 @@ function file_test_menu() {
return $items;
}
/**
* Implement hook_stream_wrappers().
*/
function file_test_stream_wrappers() {
return array(
'dummy' => array(
'name' => t('Dummy files'),
'class' => 'DrupalDummyStreamWrapper',
'description' => t('Dummy wrapper for simpletest.'),
),
);
}
/**
* Form to test file uploads.
*/
......@@ -238,3 +251,33 @@ function file_test_file_move($file, $source) {
function file_test_file_delete($file) {
_file_test_log_call('delete', array($file));
}
/**
* Helper class for testing the stream wrapper registry.
*
* Dummy stream wrapper implementation (dummy://).
*/
class DrupalDummyStreamWrapper extends DrupalLocalStreamWrapper {
function getDirectoryPath() {
return variable_get('stream_public_path', 'sites/default/files');
}
/**
* Override getInternalUri().
*
* Return a dummy path for testing.
*/
function getInternalUri() {
return '/dummy/example.txt';
}
/**
* Override getExternalUrl().
*
* Return the HTML URI of a public file.
*/
function getExternalUrl() {
return '/dummy/example.txt';
}
}
......@@ -1159,6 +1159,60 @@ function custom_url_rewrite_inbound(&$result, $path, $path_language) {
}
}
/**
* Registers PHP stream wrapper implementations associated with a module.
*
* Provide a facility for managing and querying user-defined stream wrappers
* in PHP. PHP's internal stream_get_wrappers() doesn't return the class
* registered to handle a stream, which we need to be able to find the handler
* for class instantiation.
*
* If a module registers a scheme that is already registered with PHP, it will
* be unregistered and replaced with the specified class.
*
* @return
* A nested array, keyed first by scheme name ("public" for "public://"),
* then keyed by the following values:
* - 'name' A short string to name the wrapper.
* - 'class' A string specifying the PHP class that implements the
* DrupalStreamWrapperInterface interface.
* - 'description' A string with a short description of what the wrapper does.
*
* @see file_get_stream_wrappers()
* @see hook_stream_wrappers_alter()
* @see system_stream_wrappers()
*/
function hook_stream_wrappers() {
return array(
'public' => array(
'name' => t('Public files'),
'class' => 'DrupalPublicStreamWrapper',
'description' => t('Public local files served by the webserver.'),
),
'private' => array(
'name' => t('Private files'),
'class' => 'DrupalPrivateStreamWrapper',
'description' => t('Private local files served by Drupal.'),
),
'temp' => array(
'name' => t('Temporary files'),
'class' => 'DrupalTempStreamWrapper',
'description' => t('Temporary local files for upload and previews.'),
)
);
}
/**
* Alters the list of PHP stream wrapper implementations.
*
* @see file_get_stream_wrappers()
* @see hook_stream_wrappers()
*/
function hook_stream_wrappers_alter(&$wrappers) {
// Change the name of private files to reflect the performance.
$wrappers['private']['name'] = t('Slow files');
}
/**
* Load additional information into file objects.
*
......
......@@ -1157,6 +1157,29 @@ function system_library() {
return $libraries;
}
/**
* Implement hook_stream_wrappers().
*/
function system_stream_wrappers() {
return array(
'public' => array(
'name' => t('Public files'),
'class' => 'DrupalPublicStreamWrapper',
'description' => t('Public local files served by the webserver.'),
),
'private' => array(
'name' => t('Private files'),
'class' => 'DrupalPrivateStreamWrapper',
'description' => t('Private local files served by Drupal.'),
),
'temporary' => array(
'name' => t('Temporary files'),
'class' => 'DrupalTemporaryStreamWrapper',
'description' => t('Temporary local files for upload and previews.'),
)
);
}
/**
* Retrieve a blocked IP address from the database.
*
......
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