diff --git includes/file.inc includes/file.inc index 6a28a0c..df2a8d1 100644 --- includes/file.inc +++ includes/file.inc @@ -6,6 +6,17 @@ * API for handling file uploads and server file management. */ +// Stream wrapper interface and base class implementation +require_once DRUPAL_ROOT . '/includes/stream_wrappers.inc'; + +// Stream wrapper registry +require_once DRUPAL_ROOT . '/includes/stream_wrapper_registry.inc'; + +// Register core wrappers +DrupalStreamWrapperRegistry::register('public', 'DrupalPublicStreamWrapper'); +DrupalStreamWrapperRegistry::register('private', 'DrupalPrivateStreamWrapper'); +DrupalStreamWrapperRegistry::register('temp', 'DrupalTempStreamWrapper'); + /** * @defgroup file File interface * @{ @@ -79,10 +90,10 @@ define('FILE_STATUS_PERMANENT', 1); /** * Create the download path to a file. * - * @param $path A string containing the path of the file to generate URL for. + * @param $file A file object. * @return A string containing a URL that can be used to download the file. */ -function file_create_url($path) { +function file_create_url($file) { // Strip file_directory_path from $path. We only include relative paths in // URLs. if (strpos($path, file_directory_path() . '/') === 0) { @@ -103,12 +114,17 @@ function file_create_url($path) { * @param $destination * A string containing the path to verify. If this value is omitted, Drupal's * 'files' directory will be used. + * @param mixed $scheme + * An optional string specifiying the name of a stream wrapper. + * TODO: Link to stream wrappers. * @return * A string containing the path to file, with file system directory appended * if necessary, or FALSE if the path is invalid (i.e. outside the configured * 'files' or temp directories). */ -function file_create_path($destination = NULL) { +function file_create_path($destination = NULL, $scheme = NULL) { + // Get the wrapper for this scheme. + // Ask the scheme for the directory. $file_path = file_directory_path(); if (is_null($destination)) { return $file_path; @@ -242,15 +258,16 @@ function file_check_path(&$path) { * path of the source. */ function file_check_location($source, $directory = '') { - $check = realpath($source); + $check = drupal_realpath($source); + if ($check) { $source = $check; } else { // This file does not yet exist. - $source = realpath(dirname($source)) . '/' . basename($source); + $source = drupal_realpath(dirname($source)) . '/' . basename($source); } - $directory = realpath($directory); + $directory = drupal_realpath($directory); if ($directory && strpos($source, $directory) !== 0) { return FALSE; } @@ -445,7 +462,7 @@ function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) * @see file_copy() */ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - $source = realpath($source); + $source = drupal_realpath($source); if (!file_exists($source)) { drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); return FALSE; @@ -473,7 +490,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST // Make sure source and destination filenames are not the same, makes no // sense to copy it if they are. In fact copying the file will most likely // result in a 0 byte file. Which is bad. Real bad. - if ($source == realpath($destination)) { + if ($source == drupal_realpath($destination)) { drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error'); return FALSE; } @@ -864,8 +881,8 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { * Saves a file upload to a new location. * * The file will be added to the files table as a temporary file. Temporary - * files are periodically cleaned. To make the file a permanent file call - * assign the status and use file_save() to save it. + * files are periodically cleaned. To make the file a permanent file, move it with + * file_move() from the temp hanlder to a one with a permanent store, eg public:// or private://. * * @param $source * A string specifying the name of the upload field to save. @@ -873,8 +890,10 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { * An optional, associative array of callback functions used to validate the * file. See file_validate() for a full discussion of the array format. * @param $destination - * A string containing the directory $source should be copied to. If this is - * not provided or is not writable, the temporary directory will be used. + * A string containing the location $source should be copied to within the temp directory. + * $destination can be a directory or directory and filename within the temp directory. + * If not provided, the temporary directory will be used, and the $replace flag will + * will be honored. * @param $replace * A boolean indicating whether an existing file of the same name in the * destination directory should overwritten. A false value will generate a @@ -1013,7 +1032,6 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, return FALSE; } - /** * Check that a file meets the criteria specified by the validators. * @@ -1490,10 +1508,13 @@ function file_directory_temp() { /** * Determine the default 'files' directory. * + * TODO: this should be deprecated. + * * @return * A string containing the path to Drupal's 'files' directory. */ function file_directory_path() { + return variable_get('file_directory_path', conf_path() . '/files'); } @@ -1561,7 +1582,7 @@ function file_get_mimetype($filename, $mapping = NULL) { } /** - * Set the permissions on a file or directory. + * Set the permissions on a URI (stream). * * This function will use the 'file_chmod_directory' and 'file_chmod_file' * variables for the default modes for directories and uploaded/generated files. @@ -1570,17 +1591,24 @@ function file_get_mimetype($filename, $mapping = NULL) { * these files, and give group write permissions so webserver group members * (e.g. a vhost account) can alter files uploaded and owned by the webserver. * - * @param $path + * PHP's chmod does not support stream wrappers so we use our wrapper implementation + * which interfaces with chmod() by default. Contrib wrappers can override this + * bahavior in their implementations as needed. + * + * @see http://php.net/manual/en/function.chmod.php + * + * @param $uri * String containing the path to a file or directory. * @param $mode * Integer value for the permissions. Consult PHP chmod() documentation for * more information. * @return - * TRUE for success, FALSE in the event of an error. + * TRUE for success, FALSE in the event of an error. Errors are logged by watchdog. */ -function drupal_chmod($path, $mode = NULL) { +function drupal_chmod($uri, $mode = NULL) { + if (!isset($mode)) { - if (is_dir($path)) { + if (is_dir($uri)) { $mode = variable_get('file_chmod_directory', 0775); } else { @@ -1588,14 +1616,82 @@ function drupal_chmod($path, $mode = NULL) { } } - if (@chmod($path, $mode)) { - return TRUE; + // If this URI is a stream, pass it off to the appropriate stream wrapper. + // Otherwise, attempt PHP's chmod. This allows use of drupal_chmod even + // for unmanaged files outside of the stream wrapper interface. + if (DrupalStreamWrapperRegistry::getStreamScheme($uri)) { + if (DrupalStreamWrapperRegistry::getInstance($uri)->stream_chmod($uri, $mode)) { + return TRUE; + } + } + else { + if (@chmod($uri, $mode)) { + return TRUE; + } } - watchdog('file', 'The file permissions could not be set on %path.', array('%path' => $path), WATCHDOG_ERROR); + watchdog('file', 'The file permissions could not be set on %uri.', array('%uri' => $uri), WATCHDOG_ERROR); return FALSE; } /** + * Get canonicalized absolute path of a file or directory + * + * PHP's realpath() does not properly support streams, so this function + * fills that gap. If a stream wrapped URI is provided, it will be passed + * to the registered wrapper for handling. If the URI does not contain a + * scheme or the wrapper implementation does not implement realpath, then + * FALSE will be returned. + * + * @see http://php.net/manual/en/function.realpath.php + * + * @param string $uri + * A string containing a path to a stream (file, directory, etc.) + * @return mixed + * A string containing the absolute pathname, or FALSE on failure. + */ +function drupal_realpath($uri) { + + // If this URI is a stream, pass it off to the appropriate stream wrapper. + // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even + // for unmanaged files outside of the stream wrapper interface. + if (DrupalStreamWrapperRegistry::getStreamScheme($uri)) { + return DrupalStreamWrapperRegistry::getInstance($uri)->stream_realpath($uri); + } + else { + return realpath($uri); + } +} + +/** + * Create file with unique file name. + * + * Stream wrapper friendly PHP tempnam() replacement. Creates a file with a unique + * filename, with access permission set to 0600, in the predeclared temp directory. + * Drupal will perform garbage collection on these files during cron runs based on + * DRUPAL_MAXIMUM_TEMP_FILE_AGE. + * + * Note the absense of param $dir, unlike PHP's tempnam. $dir is determined by the + * temp stream wrapper. It first tries the temp directory specified in + * admin/settings/file-system and if that directory does not exist it will fall back + * to a system default tmp directory. + * + * @see http://php.net/manual/en/function.tempnam.php + * + * @param string $prefix + * The prefix of the generated temporary filename. Note: Windows uses only + * the first three characters of prefix. + * @return mixed + * Returns the new temporary filename, or FALSE on failure. + */ +function drupal_tempnam($prefix) { + + /* + TODO This implementation will change once we get the rest of the API sorted. + */ + return tempnam(DrupalStreamWrapperRegistry::getInstance('temp://')->interpolate_uri(''), $prefix); +} + +/** * @} End of "defgroup file". */ diff --git includes/stream_wrapper_registry.inc includes/stream_wrapper_registry.inc new file mode 100644 index 0000000..edee76f --- /dev/null +++ includes/stream_wrapper_registry.inc @@ -0,0 +1,144 @@ +directoryPath(). + */ + function interpolate_uri($uri) { + $basepath = realpath($this->directoryPath()); + return drupal_realpath($basepath) . str_replace('/..','', parse_url($uri, PHP_URL_PATH)); + } + + function html_uri($uri) { + return $uri; + } + + function mime($uri) { + return file_get_mimetype(basename($uri)); + } + + function stream_chmod($uri, $mode) { + return @chmod($uri, $mode); + } + + function stream_realpath($uri) { + return realpath($this->interpolate_uri($uri)); + } + + + /** + * Support for fopen(), file_get_contents(), file_put_contents() etc. + * + * @see http://php.net/manual/en/streamwrapper.stream-open.php + * + * @param $path + * A string containing the path to the file to open. + * @param $mode + * The file mode ("r", "wb" etc.). + * @param $options + * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS. + * @param &$opened_path + * A string containing the path actually opened. + * @return bool + * TRUE if file was opened successfully. + */ + public function stream_open($uri, $mode, $options, &$opened_url) { + $uri = $this->interpolate_uri($uri); + $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($uri, $mode) : @fopen($uri, $mode); + + if ((bool)$this->handle && $options & STREAM_USE_PATH) { + $opened_url = $uri; + } + + return (bool)$this->handle; + } + + /** + * Support for flock(). + * + * @see http://php.net/manual/en/streamwrapper.stream-lock.php + * + * @param int $operation + * @return bool + * Always returns TRUE. + */ + function stream_lock($operation) { + if (in_array($operation, array(LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB))) { + return flock($this->handle, $operation); + } + + return TRUE; + } + + /** + * Support for fread(), file_get_contents() etc. + * + * @see http://php.net/manual/en/streamwrapper.stream-read.php + * + * @param $count + * Maximum number of bytes to be read. + * @return + * The string that was read, or FALSE in case of an error. + */ + public function stream_read($count) { + return fread($this->handle, $count); + } + + /** + * Support for fwrite(), file_put_contents() etc. + * + * @see http://php.net/manual/en/streamwrapper.stream-write.php + * + * @param $data + * The string to be written. + * @return int + * The number of bytes written. + */ + public function stream_write($data) { + return fwrite($this->handle, $data); + } + + /** + * Support for feof(). + * + * @see http://php.net/manual/en/streamwrapper.stream-eof.php + * + * @return bool + * TRUE if end-of-file has been reached. + */ + public function stream_eof() { + return feof($this->handle); + } + + /** + * Support for fseek(). + * + * @see http://php.net/manual/en/streamwrapper.stream-seek.php + * + * @param $offset + * The byte offset to got to. + * @param $whence + * SEEK_SET, SEEK_CUR, or SEEK_END. + * @return + * TRUE on success + */ + public function stream_seek($offset, $whence) { + return fseek($this->handle, $offset, $whence); + } + + /** + * Support for fflush(). + * + * @see http://php.net/manual/en/streamwrapper.stream-flush.php + * + * @return + * TRUE if data was successfully stored (or there was no data to store). + */ + public function stream_flush() { + return fflush($this->handle); + } + + /** + * Support for ftell(). + * + * @see http://php.net/manual/en/streamwrapper.stream-tell.php + * + * @return + * The current offset in bytes from the beginning of file. + */ + public function stream_tell() { + return ftell($this->handle); + } + + /** + * Support for fstat(). + * + * @see http://php.net/manual/en/streamwrapper.stream-stat.php + * + * @return + * An array with file status, or FALSE in case of an error - see fstat() + * for a description of this array. + */ + public function stream_stat() { + return fstat($this->handle); + } + + /** + * Support for fclose(). + * + * @see http://php.net/manual/en/streamwrapper.stream-close.php + * + * @return + * TRUE if stream was successfully closed. + */ + public function stream_close() { + return fclose($this->handle); + } + + /** + * Support for unlink(). + * + * @see http://php.net/manual/en/streamwrapper.unlink.php + * + * @param $uri + * A string containing the uri to the resource to delete. + * @return + * TRUE if resource was successfully deleted. + */ + public function unlink($uri) { + return unlink($this->interpolate_uri($uri)); + } + + /** + * Support for rename(). + * + * @see http://php.net/manual/en/streamwrapper.rename.php + * + * @param $from_uri, + * The uri to the file to rename. + * @param $to_uri + * The new uri for file. + * @return + * TRUE if file was successfully renamed. + */ + public function rename($from_uri, $to_uri) { + return rename($this->interpolate_uri($from_uri), $this->interpolate_uri($to_uri)); + } + + /** + * Support for mkdir(). + * + * @see http://php.net/manual/en/streamwrapper.mkdir.php + * + * @param $uri + * A string containing the url to the directory to create. + * @param $mode + * Permission flags - see mkdir(). + * @param $options + * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE. + * @return + * TRUE if directory was successfully created. + */ + public function mkdir($uri, $mode, $options) { + $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE); + if ($options & STREAM_REPORT_ERRORS) { + return mkdir($this->interpolate_uri($uri), $mode, $recursive); + } + else { + return @mkdir($this->interpolate_uri($uri), $mode, $recursive); + } + } + + /** + * Support for rmdir(). + * + * @see http://php.net/manual/en/streamwrapper.rmdir.php + * + * @param $uri + * A string containing the url to the directory to delete. + * @param $options + * A bit mask of STREAM_REPORT_ERRORS. + * @return + * TRUE if directory was successfully removed. + */ + public function rmdir($uri, $options) { + if ($options & STREAM_REPORT_ERRORS) { + return rmdir($this->interpolate_uri($uri)); + } + else { + return @rmdir($this->interpolate_uri($uri)); + } + } + + /** + * Support for stat(). + * + * @see http://php.net/manual/en/streamwrapper.url-stat.php + * + * @param $uri + * A string containing the url to get information about. + * @param $flags + * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET. + * @return + * An array with file status, or FALSE in case of an error - see fstat() + * for a description of this array. + */ + public function url_stat($uri, $flags) { + if ($flags & STREAM_URL_STAT_QUIET) { + return @stat($this->interpolate_uri($uri)); + } + else { + return stat($this->interpolate_uri($uri)); + } + } + + /** + * Support for opendir(). + * + * @see http://php.net/manual/en/streamwrapper.dir-opendir.php + * + * @param $uri + * A string containing the url to the directory to open. + * @param $options + * Unknown (parameter is not documented in PHP Manual). + * @return + * TRUE on success. + */ + public function dir_opendir($uri, $options) { + $this->handle = opendir($this->interpolate_uri($uri)); + + return (bool)$this->handle; + } + + /** + * Support for readdir(). + * + * @see http://php.net/manual/en/streamwrapper.dir-readdir.php + * + * @return + * The next filename, or FALSE if there are no more files in the directory. + */ + public function dir_readdir() { + return readdir($this->handle); + } + + /** + * Support for rewinddir(). + * + * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php + * + * @return + * TRUE on success. + */ + public function dir_rewinddir() { + return rewinddir($this->handle); + } + + /** + * Support for closedir(). + * + * @see http://php.net/manual/en/streamwrapper.dir-closedir.php + * + * @return + * TRUE on success. + */ + public function dir_closedir() { + return closedir($this->handle); + } +} + + +/** + * Drupal public (public://) stream wrapper class. + * + * Provides support for storing publicly accessible + * files with the Drupal file interface. + */ +class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper { + + static protected function directoryPath() { + return variable_get('stream_public_path', 'sites/default/files'); + } + + /** + * Override html_uri(). + * + * Return the HTML URI of a public file. + */ + function html_uri($uri) { + $basepath = $this->directoryPath(); + $path = parse_url($uri, PHP_URL_PATH); + + return $GLOBALS['base_url'] . '/' . $basepath . '/' . str_replace('\\', '/', $path); + } +} + + +/** + * Drupal private (private://) stream wrapper class. + * + * Provides support for storing privately accessible + * files with the Drupal file interface. + * + * Extends DrupalPublicStreamWrapper. + */ +class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper { + + static protected function directoryPath() { + return variable_get('stream_private_path', 'sites/default/files-private'); + } + + /** + * Override html_uri. + * + * Return the HTML URI of a private file. + */ + function html_uri($uri) { + return url('system/files/' . parse_url($uri, PHP_URL_PATH), array('absolute' => TRUE)); + } +} + + +/** + * Drupal temp (temp://) stream wrapper class. + * + * Provides support for storing temporarily accessible + * files with the Drupal file interface. + * + * Extends DrupalPublicStreamWrapper. + */ +class DrupalTempStreamWrapper extends DrupalLocalStreamWrapper { + + static protected function directoryPath() { + return variable_get('file_directory_temp', 'sites/default/files/tmp'); + } + + /** + * Override html_uri. + * + * Return the HTML URI of a private file. + */ + function html_uri($uri) { + return url('system/files/' . parse_url($uri, PHP_URL_PATH), array('absolute' => TRUE)); + } +} diff --git modules/simpletest/tests/file.test modules/simpletest/tests/file.test index 18bc651..d1a1af0 100644 --- modules/simpletest/tests/file.test +++ modules/simpletest/tests/file.test @@ -41,6 +41,37 @@ function file_test_file_scan_callback($filepath, $reset = FALSE) { } /** + * Helper class for testing the stream wrapper registry. + * + * Dummy stream wrapper implementation (dummy://). + */ +class DrupalDummyStreamWrapper extends DrupalLocalStreamWrapper { + // TODO figure out what this should really return. + static protected function directoryPath() { + return variable_get('stream_public_path', 'sites/default/files'); + } + + /** + * Override interpolate_uri(). + * + * Return a dummy path for testing. + */ + function interpolate_uri($uri) { + return '/dummy/example.txt'; + } + + /** + * Override html_uri(). + * + * Return the HTML URI of a public file. + */ + function html_uri($uri) { + return '/dummy/example.txt'; + } + +} + +/** * Base class for file tests that adds some additional file specific * assertions and helper functions. */ @@ -401,7 +432,7 @@ class FileValidatorTest extends DrupalWebTestCase { if (image_get_toolkit()) { // Copy the image so that the original doesn't get resized. $temp_dir = file_directory_temp(); - copy(realpath('misc/druplicon.png'), realpath($temp_dir) . '/druplicon.png'); + copy(drupal_realpath('misc/druplicon.png'), drupal_realpath($temp_dir) . '/druplicon.png'); $this->image->filepath = $temp_dir . '/druplicon.png'; $errors = file_validate_image_resolution($this->image, '10x5'); @@ -411,7 +442,7 @@ class FileValidatorTest extends DrupalWebTestCase { $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File'); $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File'); - unlink(realpath($temp_dir . '/druplicon.png')); + unlink(drupal_realpath($temp_dir . '/druplicon.png')); } else { // TODO: should check that the error is returned if no toolkit is available. @@ -505,14 +536,14 @@ class FileUnmanagedSaveDataTest extends FileTestCase { $filepath = file_unmanaged_save_data($contents); $this->assertTrue($filepath, t('Unnamed file saved correctly.')); $this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory.")); - $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.')); + $this->assertEqual($contents, file_get_contents(drupal_realpath($filepath)), t('Contents of the file are correct.')); // Provide a filename. $filepath = file_unmanaged_save_data($contents, 'asdf.txt', FILE_EXISTS_REPLACE); $this->assertTrue($filepath, t('Unnamed file saved correctly.')); $this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory.")); $this->assertEqual('asdf.txt', basename($filepath), t('File was named correctly.')); - $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.')); + $this->assertEqual($contents, file_get_contents(drupal_realpath($filepath)), t('Contents of the file are correct.')); $this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664)); } } @@ -552,7 +583,7 @@ class FileSaveUploadTest extends FileHookTestCase { // Upload with replace to gurantee there's something there. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => realpath($this->image->filepath) + 'files[file_test_upload]' => drupal_realpath($this->image->filepath) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -579,7 +610,7 @@ class FileSaveUploadTest extends FileHookTestCase { // Upload a second file. $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField(); $image2 = current($this->drupalGetTestFiles('image')); - $edit = array('files[file_test_upload]' => realpath($image2->filepath)); + $edit = array('files[file_test_upload]' => drupal_realpath($image2->filepath)); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!')); @@ -604,7 +635,7 @@ class FileSaveUploadTest extends FileHookTestCase { function testExistingRename() { $edit = array( 'file_test_replace' => FILE_EXISTS_RENAME, - 'files[file_test_upload]' => realpath($this->image->filepath) + 'files[file_test_upload]' => drupal_realpath($this->image->filepath) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -620,7 +651,7 @@ class FileSaveUploadTest extends FileHookTestCase { function testExistingReplace() { $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => realpath($this->image->filepath) + 'files[file_test_upload]' => drupal_realpath($this->image->filepath) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -636,7 +667,7 @@ class FileSaveUploadTest extends FileHookTestCase { function testExistingError() { $edit = array( 'file_test_replace' => FILE_EXISTS_ERROR, - 'files[file_test_upload]' => realpath($this->image->filepath) + 'files[file_test_upload]' => drupal_realpath($this->image->filepath) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -2031,3 +2062,49 @@ class FileMimeTypeTest extends DrupalWebTestCase { } } } + +/** + * Tests stream wrapper registry. + */ +class StreamWrapperRegistryUnitTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => t('Stream Wrapper Registry'), + 'description' => t('Tests stream wrapper registry.'), + 'group' => t('File'), + ); + } + + /** + * Test stream wrapper registry. + */ + function testStreamWrapperRegistry() { + $scheme = 'dummy'; + $filename = 'example.txt'; + $class = 'DrupalDummyStreamWrapper'; + $uri = $scheme .'://'. $filename; + + DrupalStreamWrapperRegistry::register($scheme, $class); + $this->assertTrue(DrupalStreamWrapperRegistry::getInstance($uri), 'Dummy stream wrapper registered'); + + // $this->assertEqual($this->scheme, stream_scheme($this->uri), 'stream_scheme() with valid stream wrapped URI.'); + // $this->assertFalse(stream_scheme('/foo/bar'), 'stream_scheme() with faulty URI.'); + //$this->assertEqual('/dummy/example.txt', stream_wrapper($dummy_uri)->interpolate_uri($dummy_uri), 'Stream wrapper URI interpolation.'); + + // Unregister dummy wrapper + DrupalStreamWrapperRegistry::unregister($scheme); + $this->assertFalse(DrupalStreamWrapperRegistry::getInstance($uri), 'Dummy stream wrapper unregistered.'); + } + + /** + * Confirm core stream wrappers are available. + */ + function testCoreStreamWrapperAvailability() { + + $filename = 'example.txt'; + $this->assertTrue(DrupalStreamWrapperRegistry::getInstance('public://'. $filename), 'Public stream wrapper registered and available.'); + $this->assertTrue(DrupalStreamWrapperRegistry::getInstance('private://'. $filename), 'Private stream wrapper registered and available.'); + $this->assertTrue(DrupalStreamWrapperRegistry::getInstance('temp://'. $filename), 'Temp stream wrapper registered and available.'); + } +}