diff --git a/core/includes/common.inc b/core/includes/common.inc index 1581de6..c5e1d9d 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -868,14 +868,18 @@ function drupal_set_time_limit($time_limit) { /** * Returns the path to a system item (module, theme, etc.). * - * @param $type + * This function should only be used when including a file containing PHP code; + * the 'module://', 'profile://' and 'theme://' stream wrappers should be used + * for other use cases. + * + * @param string $type * The type of the item; one of 'core', 'profile', 'module', 'theme', or * 'theme_engine'. - * @param $name + * @param string $name * The name of the item for which the path is requested. Ignored for * $type 'core'. * - * @return + * @return string * The path to the requested item or an empty string if the item is not found. */ function drupal_get_path($type, $name) { diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php index 937953f..01c3186 100644 --- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php @@ -50,7 +50,7 @@ * @return string * String specifying the path. */ - abstract function getDirectoryPath(); + protected abstract function getDirectoryPath(); /** * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri(). @@ -78,19 +78,25 @@ function getUri() { * @param string $uri * Optional URI. * - * @return string|bool - * Returns a string representing a location suitable for writing of a file, - * or FALSE if unable to write to the file such as with read-only streams. + * @return string + * Returns a string representing a location suitable for writing of a file. + * + * @throws \InvalidArgumentException + * If a malformed $uri parameter is passed in. */ protected function getTarget($uri = NULL) { if (!isset($uri)) { $uri = $this->uri; } - list(, $target) = explode('://', $uri, 2); + $uri_parts = explode('://', $uri, 2); + if (count($uri_parts) === 1) { + // The delimiter ('://') was not found in $uri, malformed $uri passed. + throw new \InvalidArgumentException(sprintf('Malformed uri parameter passed: %s', $uri)); + } - // Remove erroneous leading or trailing, forward-slashes and backslashes. - return trim($target, '\/'); + // Remove erroneous leading or trailing forward-slashes and backslashes. + return trim($uri_parts[1], '\/'); } /** @@ -119,11 +125,11 @@ protected function getLocalPath($uri = NULL) { if (!isset($uri)) { $uri = $this->uri; } - $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri); + $path = $this->getDirectoryPath() . DIRECTORY_SEPARATOR . $this->getTarget($uri); $realpath = realpath($path); if (!$realpath) { // This file does not yet exist. - $realpath = realpath(dirname($path)) . '/' . drupal_basename($path); + $realpath = realpath(dirname($path)) . DIRECTORY_SEPARATOR . drupal_basename($path); } $directory = realpath($this->getDirectoryPath()); if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) { @@ -308,7 +314,7 @@ public function stream_close() { * @see http://php.net/manual/streamwrapper.stream-cast.php */ public function stream_cast($cast_as) { - return false; + return FALSE; } /** @@ -420,7 +426,7 @@ public function mkdir($uri, $mode, $options) { if ($recursive) { // $this->getLocalPath() fails if $uri has multiple levels of directories // that do not yet exist. - $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri); + $localpath = $this->getDirectoryPath() . DIRECTORY_SEPARATOR . $this->getTarget($uri); } else { $localpath = $this->getLocalPath($uri); diff --git a/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php new file mode 100644 index 0000000..36c7e54 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php @@ -0,0 +1,44 @@ +moduleHandler()->moduleExists($name)) { + return $name; + } + else { + // The module does not exist or is not installed. + throw new \InvalidArgumentException(sprintf('Module %s does not exist or is not installed', $name)); + } + } + + /** + * {@inheritdoc} + */ + protected function getDirectoryPath() { + return drupal_get_path('module', $this->getOwnerName()); + } + + /** + * Wraps the ModuleHandler service. + * + * @return \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected function moduleHandler() { + return \Drupal::moduleHandler(); + } +} diff --git a/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php new file mode 100644 index 0000000..e36ede2 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php @@ -0,0 +1,39 @@ +getOwnerName()); + } +} diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php index 3f9f5d4..8fe1d1f 100644 --- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php @@ -86,7 +86,7 @@ public function realpath(); * An optional URI. * * @return string - * A string containing the directory name, or FALSE if not applicable. + * A string containing the directory name. * * @see drupal_dirname() */ diff --git a/core/lib/Drupal/Core/StreamWrapper/SystemStream.php b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php new file mode 100644 index 0000000..dadbd40 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php @@ -0,0 +1,90 @@ +uri, 2); + if (count($uri_parts) === 1) { + // The delimiter ('://') was not found in $uri, malformed $uri passed. + throw new \InvalidArgumentException(sprintf('Malformed uri parameter passed: %s', $this->uri)); + } + + // Remove the trailing filename from the path. + $length = strpos($uri_parts[1], '/'); + return ($length === FALSE) ? $uri_parts[1] : substr($uri_parts[1], 0, $length); + } + + /** + * {@inheritdoc} + */ + protected function getTarget($uri = NULL) { + return rtrim(strstr(parent::getTarget($uri), '/') ?: '', '/'); + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException + */ + public function getExternalUrl() { + $dir = $this->getDirectoryPath(); + if (empty($dir)) { + throw new \InvalidArgumentException(sprintf('Extension directory for %s does not exist.', $this->uri)); + } + return $this->request()->getUriForPath(base_path() . $dir . $this->getTarget()); + } + + /** + * {@inheritdoc} + */ + public function dirname($uri = NULL) { + if (!isset($uri)) { + $uri = $this->uri; + } + list($scheme) = explode('://', $uri, 2); + $target = $this->getTarget($uri); + $dirname = dirname($target); + + if ($dirname === '.' || $dirname === '\\') { + $dirname = ''; + } + + return rtrim($scheme . '://' . $this->getOwnerName() . $dirname, '/'); + } + + /** + * Wraps the drupal request. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + protected function request() { + return \Drupal::request(); + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php new file mode 100644 index 0000000..f928c8c --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php @@ -0,0 +1,75 @@ +getActiveTheme(); + break; + case 'default': + $name = $this->themeHandler()->getDefault(); + break; + case 'admin': + $name = $this->config('system.theme')->get('admin'); + break; + } + // Return name only for installed themes. + if ($this->themeHandler()->themeExists($name)) { + return $name; + } + else { + // The theme does not exist or is not installed. + throw new \InvalidArgumentException(sprintf('Theme %s does not exist or is not installed', $name)); + } + } + + /** + * {@inheritdoc} + */ + protected function getDirectoryPath() { + return drupal_get_path('theme', $this->getOwnerName()); + } + + /** + * Gets the currently active theme. + * + * @return mixed + */ + protected function getActiveTheme() { + return \Drupal::service('theme.negotiator')->determineActiveTheme(\Drupal::service('current_route_match')); + } + + /** + * Wraps the theme handler service. + * + * @return \Drupal\Core\Extension\ThemeHandler + */ + protected function themeHandler() { + return \Drupal::service('theme_handler'); + } + + /** + * Wraps the drupal configuration service. + * + * @param $config_name + * @return \Drupal\Core\Config\Config + */ + protected function config($config_name) { + return \Drupal::config($config_name); + } +} diff --git a/core/modules/color/tests/modules/color_test/color_test.module b/core/modules/color/tests/modules/color_test/color_test.module index 603906e..b89e5e4 100644 --- a/core/modules/color/tests/modules/color_test/color_test.module +++ b/core/modules/color/tests/modules/color_test/color_test.module @@ -9,6 +9,6 @@ * Implements hook_system_theme_info(). */ function color_test_system_theme_info() { - $themes['color_test_theme'] = drupal_get_path('module', 'color_test') . '/themes/color_test_theme/color_test_theme.info.yml'; + $themes['color_test_theme'] = 'module://color_test/themes/color_test_theme/color_test_theme.info.yml'; return $themes; } diff --git a/core/modules/file/tests/file_test/file_test.dummy.inc b/core/modules/file/tests/file_test/file_test.dummy.inc new file mode 100644 index 0000000..a09fd2e --- /dev/null +++ b/core/modules/file/tests/file_test/file_test.dummy.inc @@ -0,0 +1 @@ +Dummy file used by SystemStreamTest.php. diff --git a/core/modules/search/src/Tests/SearchSimplifyTest.php b/core/modules/search/src/Tests/SearchSimplifyTest.php index a850750..0a55965 100644 --- a/core/modules/search/src/Tests/SearchSimplifyTest.php +++ b/core/modules/search/src/Tests/SearchSimplifyTest.php @@ -24,7 +24,7 @@ function testSearchSimplifyUnicode() { // their own lines). So the even-numbered lines should simplify to nothing, // and the odd-numbered lines we need to split into shorter chunks and // verify that simplification doesn't lose any characters. - $input = file_get_contents(DRUPAL_ROOT . '/core/modules/search/tests/UnicodeTest.txt'); + $input = file_get_contents('module://search/tests/UnicodeTest.txt'); $basestrings = explode(chr(10), $input); $strings = array(); foreach ($basestrings as $key => $string) { diff --git a/core/modules/system/src/Tests/File/SystemStreamTest.php b/core/modules/system/src/Tests/File/SystemStreamTest.php new file mode 100644 index 0000000..02b75ab --- /dev/null +++ b/core/modules/system/src/Tests/File/SystemStreamTest.php @@ -0,0 +1,369 @@ + 'System Stream wrappers', + 'description' => 'Unit tests system stream wrapper functions.', + 'group' => 'File API', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + drupal_static_reset('file_get_stream_wrappers'); + + // Add default profile for the purposes of this test. + new Settings(Settings::getAll() + array( + 'install_profile' => 'standard' + )); + } + + /** + * Test Invalid stream uri. + */ + public function testInvalidStreamUriException() { + $bad_uris = array( + 'invalid/uri', + 'invalid_uri', + 'module/invalid/uri', + 'module/invalid_uri', + 'module:invalid_uri', + 'module::/invalid/uri', + 'module::/invalid_uri', + 'module//:invalid/uri', + 'module//invalid_uri', + 'module//invalid/uri', + ); + + /** @var \Drupal\Core\StreamWrapper\ModuleStream $instance */ + $instance = file_stream_wrapper_get_instance_by_scheme('module'); + + foreach ($bad_uris as $bad_uri) { + try { + $instance->dirname($bad_uri); + $this->fail(String::format('Invalid uri %uri not detected.', array('%uri' => $bad_uri))); + } + catch (\InvalidArgumentException $e) { + if ($e->getMessage() == 'Malformed uri parameter passed: ' . $bad_uri) { + $this->pass(String::format('Throw exception on invalid uri %uri supplied.', array('%uri' => $bad_uri))); + } + else { + throw new \InvalidArgumentException($e); + } + } + } + } + + /** + * Test the Module stream wrapper functions. + */ + public function testModuleStream() { + // Generate a module stream wrapper instance. + /** @var \Drupal\Core\StreamWrapper\ModuleStream $instance */ + $instance = file_stream_wrapper_get_instance_by_scheme('module'); + + // dirname() + $data = $this->providerModuleStream('dirname'); + $this->runTestOnInstanceMethod($instance, 'dirname', $data); + + // realpath() + $data = $this->providerModuleStream('realpath'); + $this->runTestOnInstanceMethod($instance, 'realpath', $data); + + // getExternalUrl() + $data = $this->providerModuleStream('getExternalUrl'); + $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data); + } + + /** + * Provides test data for testModuleStream() + */ + protected function providerModuleStream($method) { + $uris = array( + 'module://system', + 'module://system/css/system.admin.css', + 'module://file_test/file_test.dummy.inc', + 'module://file_test/src/file_test.dummy.inc', + 'module://ckeditor/ckeditor.info.yml', + 'module://foo_bar/foo.bar.js', + ); + switch ($method) { + case 'dirname': + return array_combine($uris, array( + array('module://system', 'Get the dirname of an installed module.'), + array('module://system/css', 'Get the dirname of a resource located in a subdirectory of an installed module.'), + array('module://file_test', 'Get the dirname of a resource in a module located in a subdirectory.'), + array('module://file_test/src', 'Get the dirname of a non-existent resource in a subdirectory of a module.'), + array(new \InvalidArgumentException(), 'Fail returning an uninstalled module\'s dirname.'), + array(new \InvalidArgumentException(), 'Fail returning a non-existent module\'s dirname.'), + )); + + case 'realpath': + $base_path = DRUPAL_ROOT; + return array_combine($uris, array( + array($base_path . '/core/modules/system', 'Get the realpath of an installed module.'), + array($base_path . '/core/modules/system/css/system.admin.css', 'Get the realpath of a resource located in a subdirectory of an installed module.'), + array($base_path . '/core/modules/file/tests/file_test/file_test.dummy.inc', 'Get the realpath of a resource in a module located in a subdirectory.'), + array($base_path . '/core/modules/file/tests/file_test/src/file_test.dummy.inc', 'Get the realpath of a non-existent resource in a subdirectory of a module.'), + array(new \InvalidArgumentException(), 'Fail returning an uninstalled module\'s basepath.'), + array(new \InvalidArgumentException(), 'Fail returning a non-existent module\'s basepath.'), + )); + + case 'getExternalUrl': + $base_url = \Drupal::request()->getUriForPath(base_path()); + return array_combine($uris, array( + array($base_url . 'core/modules/system', "Return the external url for the module directory path."), + array($base_url . 'core/modules/system/css/system.admin.css', "Return the external url of a resource located in a subdirectory."), + array($base_url . 'core/modules/file/tests/file_test/file_test.dummy.inc', "Return the external url of an include file located in a subdirectory."), + array($base_url . 'core/modules/file/tests/file_test/src/file_test.dummy.inc', "Return the external url even for a non-existent resource, as long as the module exists."), + array(new \InvalidArgumentException(), "Fail returning the external uri for resources in a disabled module."), + array(new \InvalidArgumentException(), "Fail returning the external uri for resources in a non-existent module."), + )); + } + } + + /** + * Test the Profile stream wrapper functions. + */ + public function testProfileStream() { + // Generate a profile stream wrapper instance. + /** @var \Drupal\Core\StreamWrapper\ProfileStream $instance */ + $instance = file_stream_wrapper_get_instance_by_scheme('profile'); + + // dirname() + $data = $this->providerProfileStream('dirname'); + $this->runTestOnInstanceMethod($instance, 'dirname', $data); + + // realpath() + $data = $this->providerProfileStream('realpath'); + $this->runTestOnInstanceMethod($instance, 'realpath', $data); + + // getExternalUrl() + $data = $this->providerProfileStream('getExternalUrl'); + $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data); + } + + /** + * Provides test data for testProfileStream() + */ + protected function providerProfileStream($method) { + $uris = array( + 'profile://minimal', + 'profile://minimal/config/install/block.block.stark_login.yml', + 'profile://minimal/config/install/node.type.article.yml', + 'profile://foo_bar/', + 'profile://current', + 'profile://current/standard.info.yml', + ); + + switch ($method) { + case 'dirname': + return array_combine($uris, array( + array('profile://minimal', "Get the dirname of an installed profile."), + array('profile://minimal/config/install', "Get the dirname of a resource located in a profile's subdirectory."), + array('profile://minimal/config/install', "Get the dirname of a non-existent resource in profile's subdirectory."), + array(new \InvalidArgumentException(), "Fail returning the dirname of a non-existent profile."), + array('profile://standard', String::format('Get the dirname of %current.', array('%current' => 'profile://current'))), + array('profile://standard', String::format('Get the dirname of a resource in %current path.', array('%current' => 'profile://current'))), + )); + + case 'realpath': + $base_path = DRUPAL_ROOT; + return array_combine($uris, array( + array($base_path . '/core/profiles/minimal', "Get the realpath of an installed profile."), + array($base_path . '/core/profiles/minimal/config/install/block.block.stark_login.yml', "Get the realpath of a resource located in a profile's subdirectory."), + array($base_path . '/core/profiles/minimal/config/install/node.type.article.yml', "Get the realpath of a non-existent resource in profile's subdirectory."), + array(new \InvalidArgumentException(), "Fail returning the realpath for a non-existent profile."), + array($base_path . '/core/profiles/standard', String::format('Get the realpath of %current.', array('%current' => 'profile://current'))), + array($base_path . '/core/profiles/standard/standard.info.yml', String::format('Get the realpath of a resource in %current path.', array('%current' => 'profile://current'))), + )); + + case 'getExternalUrl': + $base_url = \Drupal::request()->getUriForPath(base_path()); + return array_combine($uris, array( + array($base_url . 'core/profiles/minimal', "Return the external url for the profile directory path."), + array($base_url . 'core/profiles/minimal/config/install/block.block.stark_login.yml', "Return the external url of a file located in a subdirectory."), + array($base_url . 'core/profiles/minimal/config/install/node.type.article.yml', "Return the external url of a non-existent file located in a subdirectory."), + array(new \InvalidArgumentException(), "Fail returning the external uri for a disabled profile."), + array($base_url . 'core/profiles/standard', String::format('Lookup the external url of %current for a partial URI.', array('%current' => 'profile://current'))), + array($base_url . 'core/profiles/standard/standard.info.yml', String::format('Lookup the external url of %current for a resource.', array('%current' => 'profile://current'))), + )); + } + } + + /** + * Test the Theme stream wrapper functions. + */ + public function testThemeStream() { + // Generate a theme stream wrapper instance. + /** @var \Drupal\Core\Extension\ThemeHandler $theme_handler */ + $theme_handler = \Drupal::service('theme_handler'); + + // Install Stark, Bartik and Seven themes. + $theme_handler->install(array('bartik', 'seven', 'stark')); + + // Set admin theme to Seven. + $system_theme = \Drupal::config('system.theme'); + $this->assertNull($system_theme->get('admin'), 'No admin theme was set.'); + $system_theme->set('admin', 'seven')->save(); + $this->assertEqual($system_theme->get('admin'), 'seven', String::format('Make %seven the admin theme.', array('%seven' => 'Seven'))); + + // Set default theme to Bartik. + $theme_handler->setDefault('bartik'); + $this->assertEqual($theme_handler->getDefault(), 'bartik', String::format('Make %bartik the default theme.', array('%bartik' => 'Bartik'))); + + // Uninstall Stark theme. + $theme_handler->uninstall(array('stark')); + + /** @var \Drupal\Core\StreamWrapper\ThemeStream $instance */ + $instance = file_stream_wrapper_get_instance_by_scheme('theme'); + + // dirname() + $data = $this->providerThemeStream('dirname'); + $this->runTestOnInstanceMethod($instance, 'dirname', $data); + + // realpath() + $data = $this->providerThemeStream('realpath'); + $this->runTestOnInstanceMethod($instance, 'realpath', $data); + + // getExternalUrl() + $data = $this->providerThemeStream('getExternalUrl'); + $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data); + } + + /** + * Provides test data for testThemeStream() + */ + protected function providerThemeStream($method) { + $uris = array( + 'theme://seven', + 'theme://seven/style.css', + 'theme://bartik/color/preview.js', + 'theme://fifteen/screenshot.png', + 'theme://current', + 'theme://current/logo.png', + 'theme://default', + 'theme://default/bartik.info.yml', + 'theme://admin', + 'theme://admin/fake.info.yml', + 'theme://stark/stark.info.yml', + ); + + switch ($method) { + case 'dirname': + return array_combine($uris, array( + array('theme://seven', "Get the dirname of an installed theme."), + array('theme://seven', "Get the dirname of a resource located in a theme directory."), + array('theme://bartik/color', "Get the dirname of a resource located in a theme's subdirectory."), + array(new \InvalidArgumentException(), "Fail returning the dirname of a non-existent theme."), + array('theme://bartik', String::format('Get the dirname of %current.', array('%current' => 'theme://current'))), + array('theme://bartik', String::format('Get the dirname of a resource in %current.', array('%current' => 'theme://current'))), + array('theme://bartik', String::format('Get the dirname of %default.', array('%default' => 'theme://default'))), + array('theme://bartik', String::format('Get the dirname of a resource in %default.', array('%default' => 'theme://default'))), + array('theme://seven', String::format('Get the dirname of %admin.', array('%admin' => 'theme://admin'))), + array('theme://seven', String::format('Get the dirname of a non-existent resource in %admin.', array('%admin' => 'theme://admin'))), + array(new \InvalidArgumentException(), "Fail returning the dirname for an uninstalled theme."), + )); + + case 'realpath': + $base_path = DRUPAL_ROOT; + return array_combine($uris, array( + array($base_path . '/core/themes/seven', "Get the realpath of an installed theme."), + array($base_path . '/core/themes/seven/style.css', "Get the realpath of a resource located in a theme directory."), + array($base_path . '/core/themes/bartik/color/preview.js', "Get the realpath of a resource located in a theme's subdirectory."), + array(new \InvalidArgumentException(), "Fail returning the realpath of a non-existent theme."), + array($base_path . '/core/themes/bartik', String::format('Get the realpath of %current.', array('%current' => 'theme://current'))), + array($base_path . '/core/themes/bartik/logo.png', String::format('Get the realpath of a resource in %current.', array('%current' => 'theme://current'))), + array($base_path . '/core/themes/bartik', String::format('Get the realpath of %default.', array('%default' => 'theme://default'))), + array($base_path . '/core/themes/bartik/bartik.info.yml', String::format('Get the realpath of a resource in %default.', array('%default' => 'theme://default'))), + array($base_path . '/core/themes/seven', String::format('Get the realpath of %admin.', array('%admin' => 'theme://admin'))), + array($base_path . '/core/themes/seven/fake.info.yml', String::format('Get the realpath of a non-existent resource in %admin.', array('%admin' => 'theme://admin'))), + array(new \InvalidArgumentException(), "Fail returning the realpath for an uninstalled theme."), + )); + + case 'getExternalUrl': + $base_url = \Drupal::request()->getUriForPath(base_path()); + return array_combine($uris, array( + array($base_url . 'core/themes/seven', "Lookup theme's external url for a partial URI."), + array($base_url . 'core/themes/seven/style.css', "Lookup theme's external url for a resource located in a subdirectory."), + array($base_url . 'core/themes/bartik/color/preview.js', "Lookup theme's external url for a resource."), + array(new \InvalidArgumentException(), "Fail returning a non-existent theme's external url."), + array($base_url . 'core/themes/bartik', String::format('Lookup real external url of %current for a partial URI.', array('%current' => 'theme://current'))), + array($base_url . 'core/themes/bartik/logo.png', String::format('Lookup external url of %current for a resource.', array('%current' => 'theme://current'))), + array($base_url . 'core/themes/bartik', String::format('Lookup external url of %default for a partial URI.', array('%default' => 'theme://default'))), + array($base_url . 'core/themes/bartik/bartik.info.yml', String::format('Lookup external url of %default for a resource.', array('%default' => 'theme://default'))), + array($base_url . 'core/themes/seven', String::format('Lookup external url of %admin for a partial URI.', array('%admin' => 'theme://admin'))), + array($base_url . 'core/themes/seven/fake.info.yml', String::format('Lookup external url of %admin for a resource.', array('%admin' => 'theme://admin'))), + array(new \InvalidArgumentException(), "Fail returning a disabled theme's external url."), + )); + } + } + + + /** + * Helper method to run specific tests on each StreamWrapper method. + * + * @param \Drupal\Core\StreamWrapper\SystemStream $instance + * The stream wrapper instance to carry out the test on. + * @param string $method + * The SystemStream method to be tested. + * @param array $data + * An array of data to be used for the test, containing the following info: + * - The expected result. + * - The test result message. + */ + private function runTestOnInstanceMethod($instance, $method, array $data) { + foreach ($data as $uri => $info) { + $instance->setUri($uri); + if ($info[0] instanceof \InvalidArgumentException) { + try { + $instance->$method(); + $this->fail($info[1]); + } + catch (\InvalidArgumentException $e) { + $this->pass($info[1]); + } + } + else { + if ($method == 'realpath') { + // realpath() returns strings with the OS-specific DIRECTORY_SEPARATOR, + // make sure we are testing for that. + $info[0] = str_replace('/', DIRECTORY_SEPARATOR, $info[0]); + } + $this->assertEqual($instance->$method(), $info[0], $info[1]); + } + } + } + +} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index a44b1fe..95f72da 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -326,6 +326,24 @@ function system_stream_wrappers() { 'description' => new TranslationWrapper('Temporary local files for upload and previews.'), 'type' => STREAM_WRAPPERS_LOCAL_HIDDEN, ), + 'module' => array( + 'name' => t('Module files'), + 'class' => 'Drupal\Core\StreamWrapper\ModuleStream', + 'description' => t('Local module files.'), + 'type' => STREAM_WRAPPERS_READ, + ), + 'theme' => array( + 'name' => t('Theme files'), + 'class' => 'Drupal\Core\StreamWrapper\ThemeStream', + 'description' => t('Local theme files.'), + 'type' => STREAM_WRAPPERS_READ, + ), + 'profile' => array( + 'name' => t('Profile files'), + 'class' => 'Drupal\Core\StreamWrapper\ProfileStream', + 'description' => t('Local profile files.'), + 'type' => STREAM_WRAPPERS_READ, + ), ); // Only register the private file stream wrapper if a file path has been set.