diff --git a/core/core.services.yml b/core/core.services.yml index e947b3b..2adbe7a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1213,10 +1213,26 @@ services: class: Drupal\Core\StreamWrapper\ThemeStream tags: - { name: stream_wrapper, scheme: theme } + stream_wrapper.active_theme: + class: Drupal\Core\StreamWrapper\ActiveThemeStream + tags: + - { name: stream_wrapper, scheme: active-theme } + stream_wrapper.admin_theme: + class: Drupal\Core\StreamWrapper\AdminThemeStream + tags: + - { name: stream_wrapper, scheme: admin-theme } + stream_wrapper.default_theme: + class: Drupal\Core\StreamWrapper\DefaultThemeStream + tags: + - { name: stream_wrapper, scheme: default-theme } stream_wrapper.profile: class: Drupal\Core\StreamWrapper\ProfileStream tags: - { name: stream_wrapper, scheme: profile } + stream_wrapper.installed_profile: + class: Drupal\Core\StreamWrapper\InstalledProfileStream + tags: + - { name: stream_wrapper, scheme: installed-profile } kernel_destruct_subscriber: class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber tags: diff --git a/core/lib/Drupal/Core/StreamWrapper/ActiveThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/ActiveThemeStream.php new file mode 100644 index 0000000..5d2237c --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/ActiveThemeStream.php @@ -0,0 +1,59 @@ +themeNegotiator = \Drupal::service('theme.negotiator'); + $this->routeMatch = \Drupal::service('current_route_match'); + } + + /** + * {@inheritdoc} + */ + protected function getOwnerName() { + return $this->themeNegotiator->determineActiveTheme($this->routeMatch); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return t('Active theme files'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return t('Local active theme files.'); + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/AdminThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/AdminThemeStream.php new file mode 100644 index 0000000..a632c17 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/AdminThemeStream.php @@ -0,0 +1,51 @@ +configFactory = \Drupal::configFactory(); + } + + /** + * {@inheritdoc} + */ + protected function getOwnerName() { + return $this->configFactory->get('system.theme')->get('admin'); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return t('Admin theme files'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return t('Local admin theme files.'); + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/DefaultThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/DefaultThemeStream.php new file mode 100644 index 0000000..2189f21 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/DefaultThemeStream.php @@ -0,0 +1,51 @@ +themeHandler = \Drupal::service('theme_handler'); + } + + /** + * {@inheritdoc} + */ + protected function getOwnerName() { + return $this->themeHandler->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return t('Default theme files'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return t('Local default theme files.'); + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/InstalledProfileStream.php b/core/lib/Drupal/Core/StreamWrapper/InstalledProfileStream.php new file mode 100644 index 0000000..0302c38 --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/InstalledProfileStream.php @@ -0,0 +1,44 @@ +getOwnerName())); + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php index de03e6d..539ffc5 100644 --- a/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php @@ -17,9 +17,6 @@ class ProfileStream extends SystemStream { */ protected function getOwnerName() { $name = parent::getOwnerName(); - if ($name == 'current') { - $name = drupal_get_profile(); - } if (!is_null(drupal_get_filename('profile', $name))) { return $name; diff --git a/core/lib/Drupal/Core/StreamWrapper/PseudoSystemStreamBase.php b/core/lib/Drupal/Core/StreamWrapper/PseudoSystemStreamBase.php new file mode 100644 index 0000000..2ad51be --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/PseudoSystemStreamBase.php @@ -0,0 +1,55 @@ +uri; + } + + list($scheme) = explode('://', $uri, 2); + $target = $this->getTarget($uri); + + $dirname = dirname($target); + + if ($dirname === '.' || $dirname === '\\') { + $dirname = ''; + } + + return "$scheme://" . trim($dirname, '/'); + } + + /** + * {@inheritdoc} + */ + protected function getTarget($uri = NULL) { + if (!isset($uri)) { + $uri = $this->uri; + } + + $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)); + } + + $target = trim($uri_parts[1], '\/'); + + return !empty($target) ? "/$target" : ''; + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/PseudoThemeStreamBase.php b/core/lib/Drupal/Core/StreamWrapper/PseudoThemeStreamBase.php new file mode 100644 index 0000000..697784c --- /dev/null +++ b/core/lib/Drupal/Core/StreamWrapper/PseudoThemeStreamBase.php @@ -0,0 +1,23 @@ +getOwnerName())); + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/SystemStream.php b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php index 5bb3ca5..bf064d2 100644 --- a/core/lib/Drupal/Core/StreamWrapper/SystemStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php @@ -7,8 +7,6 @@ namespace Drupal\Core\StreamWrapper; -use Symfony\Component\HttpFoundation\RequestStack; - /** * Defines a base stream wrapper implementation. * diff --git a/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php index 3a60f0f..d39015f 100644 --- a/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php @@ -7,12 +7,6 @@ namespace Drupal\Core\StreamWrapper; -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Extension\ThemeHandlerInterface; -use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\Theme\ThemeNegotiatorInterface; -use Symfony\Component\HttpFoundation\RequestStack; - /** * Defines the read-only theme:// stream wrapper for theme files. */ @@ -26,35 +20,11 @@ class ThemeStream extends SystemStream { protected $themeHandler; /** - * The theme negotiator service. - * - * @var \Drupal\Core\Theme\ThemeNegotiatorInterface - */ - protected $themeNegotiator; - - /** - * The config factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $configFactory; - - /** - * The route matching service. - * - * @var \Drupal\Core\Routing\RouteMatchInterface - */ - protected $routeMatch; - - /** * Constructs a new ThemeStream object. */ public function __construct() { parent::__construct(); $this->themeHandler = \Drupal::service('theme_handler'); - $this->themeNegotiator = \Drupal::service('theme.negotiator'); - $this->configFactory = \Drupal::configFactory(); - $this->routeMatch = \Drupal::service('current_route_match'); } /** @@ -62,17 +32,7 @@ public function __construct() { */ protected function getOwnerName() { $name = parent::getOwnerName(); - switch ($name) { - case 'current': - $name = $this->getActiveTheme(); - break; - case 'default': - $name = $this->themeHandler->getDefault(); - break; - case 'admin': - $name = $this->configFactory->get('system.theme')->get('admin'); - break; - } + // Return name only for installed themes. if ($this->themeHandler->themeExists($name)) { return $name; @@ -91,16 +51,6 @@ protected function getDirectoryPath() { } /** - * Gets the currently active theme. - * - * @return string|null - * Returns the active theme name, else return NULL. - */ - protected function getActiveTheme() { - return $this->themeNegotiator->determineActiveTheme($this->routeMatch); - } - - /** * {@inheritdoc} */ public function getName() { diff --git a/core/modules/system/src/Tests/File/SystemStreamTest.php b/core/modules/system/src/Tests/File/SystemStreamTest.php index 5d4d505..94c85db 100644 --- a/core/modules/system/src/Tests/File/SystemStreamTest.php +++ b/core/modules/system/src/Tests/File/SystemStreamTest.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\Core\Theme\ActiveTheme; use Drupal\simpletest\KernelTestBase; /** @@ -133,8 +134,8 @@ protected function moduleStreamTestCasesProvider($method) { 'module://system/css', 'module://file_test', 'module://file_test/src', - [new \InvalidArgumentException(), 'Module ckeditor does not exist or is not installed'], - [new \InvalidArgumentException(), 'Module foo_bar does not exist or is not installed'], + new \InvalidArgumentException('Module ckeditor does not exist or is not installed'), + new \InvalidArgumentException('Module foo_bar does not exist or is not installed'), ]); case 'realpath': return array_combine($uris, [ @@ -142,8 +143,8 @@ protected function moduleStreamTestCasesProvider($method) { DRUPAL_ROOT . '/core/modules/system/css/system.admin.css', DRUPAL_ROOT . '/core/modules/file/tests/file_test/file_test.dummy.inc', DRUPAL_ROOT . '/core/modules/file/tests/file_test/src/file_test.dummy.inc', - [new \InvalidArgumentException(), 'Module ckeditor does not exist or is not installed'], - [new \InvalidArgumentException(), 'Module foo_bar does not exist or is not installed'], + new \InvalidArgumentException('Module ckeditor does not exist or is not installed'), + new \InvalidArgumentException('Module foo_bar does not exist or is not installed'), ]); case 'getExternalUrl': $base_url = \Drupal::request()->getUriForPath(base_path()); @@ -152,8 +153,8 @@ protected function moduleStreamTestCasesProvider($method) { $base_url . 'core/modules/system/css/system.admin.css', $base_url . 'core/modules/file/tests/file_test/file_test.dummy.inc', $base_url . 'core/modules/file/tests/file_test/src/file_test.dummy.inc', - [new \InvalidArgumentException(), 'Module ckeditor does not exist or is not installed'], - [new \InvalidArgumentException(), 'Module foo_bar does not exist or is not installed'], + new \InvalidArgumentException('Module ckeditor does not exist or is not installed'), + new \InvalidArgumentException('Module foo_bar does not exist or is not installed'), ]); } } @@ -193,8 +194,6 @@ protected function profileStreamTestCasesProvider($method) { 'profile://minimal/config/install/block.block.stark_login.yml', 'profile://minimal/config/install/node.type.article.yml', 'profile://foo_bar/', - 'profile://current', - 'profile://current/minimal.info.yml', ]; switch ($method) { @@ -203,17 +202,76 @@ protected function profileStreamTestCasesProvider($method) { 'profile://minimal', 'profile://minimal/config/install', 'profile://minimal/config/install', - [new \InvalidArgumentException(), 'Profile foo_bar does not exist'], - 'profile://minimal', - 'profile://minimal', + new \InvalidArgumentException('Profile foo_bar does not exist'), ]); case 'realpath': return array_combine($uris, [ DRUPAL_ROOT . '/core/profiles/minimal', DRUPAL_ROOT . '/core/profiles/minimal/config/install/block.block.stark_login.yml', DRUPAL_ROOT . '/core/profiles/minimal/config/install/node.type.article.yml', - [new \InvalidArgumentException(), 'Profile foo_bar does not exist'], + new \InvalidArgumentException('Profile foo_bar does not exist'), + ]); + case 'getExternalUrl': + $base_url = \Drupal::request()->getUriForPath(base_path()); + return array_combine($uris, [ + $base_url . 'core/profiles/minimal', + $base_url . 'core/profiles/minimal/config/install/block.block.stark_login.yml', + $base_url . 'core/profiles/minimal/config/install/node.type.article.yml', + new \InvalidArgumentException('Profile foo_bar does not exist'), + ]); + } + } + + /** + * Test the installed profile stream wrapper functions. + */ + public function testInstalledProfileStream() { + // Generate a profile stream wrapper instance. + $instance = $this->streamWrapperManager->getViaScheme('installed-profile'); + + // Test dirname(). + $data = $this->installedProfileStreamTestCasesProvider('dirname'); + $this->runTestOnInstanceMethod($instance, 'dirname', $data); + + // Test realpath(). + $data = $this->installedProfileStreamTestCasesProvider('realpath'); + $this->runTestOnInstanceMethod($instance, 'realpath', $data); + + // Test getExternalUrl(). + $data = $this->installedProfileStreamTestCasesProvider('getExternalUrl'); + $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data); + } + + /** + * Provides test cases for testProfileStream(). + * + * @param string $method + * The method to be tested. + * + * @return array + * Associative array with test cases as values, keyed by uri. + */ + protected function installedProfileStreamTestCasesProvider($method) { + $uris = [ + 'installed-profile://', + 'installed-profile://config/install/block.block.stark_login.yml', + 'installed-profile://config/install/node.type.article.yml', + 'installed-profile://minimal.info.yml', + ]; + + switch ($method) { + case 'dirname': + return array_combine($uris, [ + 'installed-profile://', + 'installed-profile://config/install', + 'installed-profile://config/install', + 'installed-profile://', + ]); + case 'realpath': + return array_combine($uris, [ DRUPAL_ROOT . '/core/profiles/minimal', + DRUPAL_ROOT . '/core/profiles/minimal/config/install/block.block.stark_login.yml', + DRUPAL_ROOT . '/core/profiles/minimal/config/install/node.type.article.yml', DRUPAL_ROOT . '/core/profiles/minimal/minimal.info.yml', ]); case 'getExternalUrl': @@ -222,8 +280,6 @@ protected function profileStreamTestCasesProvider($method) { $base_url . 'core/profiles/minimal', $base_url . 'core/profiles/minimal/config/install/block.block.stark_login.yml', $base_url . 'core/profiles/minimal/config/install/node.type.article.yml', - [new \InvalidArgumentException(), 'Profile foo_bar does not exist'], - $base_url . 'core/profiles/minimal', $base_url . 'core/profiles/minimal/minimal.info.yml', ]); } @@ -233,28 +289,10 @@ protected function profileStreamTestCasesProvider($method) { * Test the Theme stream wrapper functions. */ public function testThemeStream() { - /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ - $config_factory = $this->container->get('config.factory'); /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */ $theme_installer = $this->container->get('theme_installer'); - /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */ - $theme_handler = $this->container->get('theme_handler'); - - // Install Stark, Bartik and Seven themes. - $theme_installer->install(['bartik', 'seven', 'stark']); - - // Set admin theme to Seven. - $system_theme = $config_factory->getEditable('system.theme'); - $this->assertNull($system_theme->get('admin')); - $system_theme->set('admin', 'seven')->save(); - $this->assertEqual($system_theme->get('admin'), 'seven'); - - // Set default theme to Bartik. - $theme_handler->setDefault('bartik'); - $this->assertEqual($theme_handler->getDefault(), 'bartik'); - - // Uninstall Stark theme. - $theme_installer->uninstall(['stark']); + // Install Bartik and Seven themes. + $theme_installer->install(['bartik', 'seven']); // Generate a theme stream wrapper instance. $instance = $this->streamWrapperManager->getViaScheme('theme'); @@ -287,12 +325,6 @@ protected function themeStreamTestCasesProvider($method) { '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', ]; @@ -302,28 +334,16 @@ protected function themeStreamTestCasesProvider($method) { 'theme://seven', 'theme://seven', 'theme://bartik/color', - [new \InvalidArgumentException(), 'Theme fifteen does not exist or is not installed'], - 'theme://bartik', - 'theme://bartik', - 'theme://bartik', - 'theme://bartik', - 'theme://seven', - 'theme://seven', - [new \InvalidArgumentException(), 'Theme stark does not exist or is not installed'], + new \InvalidArgumentException('Theme fifteen does not exist or is not installed'), + new \InvalidArgumentException('Theme stark does not exist or is not installed'), ]); case 'realpath': return array_combine($uris, [ DRUPAL_ROOT . '/core/themes/seven', DRUPAL_ROOT . '/core/themes/seven/style.css', DRUPAL_ROOT . '/core/themes/bartik/color/preview.js', - [new \InvalidArgumentException(), 'Theme fifteen does not exist or is not installed'], - DRUPAL_ROOT . '/core/themes/bartik', - DRUPAL_ROOT . '/core/themes/bartik/logo.png', - DRUPAL_ROOT . '/core/themes/bartik', - DRUPAL_ROOT . '/core/themes/bartik/bartik.info.yml', - DRUPAL_ROOT . '/core/themes/seven', - DRUPAL_ROOT . '/core/themes/seven/fake.info.yml', - [new \InvalidArgumentException(), 'Theme stark does not exist or is not installed'], + new \InvalidArgumentException('Theme fifteen does not exist or is not installed'), + new \InvalidArgumentException('Theme stark does not exist or is not installed'), ]); case 'getExternalUrl': $base_url = \Drupal::request()->getUriForPath(base_path()); @@ -331,19 +351,90 @@ protected function themeStreamTestCasesProvider($method) { $base_url . 'core/themes/seven', $base_url . 'core/themes/seven/style.css', $base_url . 'core/themes/bartik/color/preview.js', - [new \InvalidArgumentException(), 'Theme fifteen does not exist or is not installed'], + new \InvalidArgumentException('Theme fifteen does not exist or is not installed'), + new \InvalidArgumentException('Theme stark does not exist or is not installed'), + ]); + } + } + + /** + * Test the pseudo theme stream wrapper functions. + */ + public function testPseudoThemeStreams() { + /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */ + $config_factory = $this->container->get('config.factory'); + /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */ + $theme_installer = $this->container->get('theme_installer'); + + // Install Bartik theme. + $theme_installer->install(['bartik']); + // Set default and admin theme to Bartik. + $config_factory->getEditable('system.theme') + ->set('default', 'bartik') + ->set('admin', 'bartik') + ->save(); + + foreach (['active', 'default', 'admin'] as $scheme) { + $scheme .= '-theme'; + + // Generate a theme stream wrapper instance. + $instance = $this->streamWrapperManager->getViaScheme($scheme); + + // Test dirname(). + $data = $this->pseudoThemeStreamTestCasesProvider('dirname', $scheme); + $this->runTestOnInstanceMethod($instance, 'dirname', $data); + + // Test realpath(). + $data = $this->pseudoThemeStreamTestCasesProvider('realpath', $scheme); + $this->runTestOnInstanceMethod($instance, 'realpath', $data); + + // Test getExternalUrl(). + $data = $this->pseudoThemeStreamTestCasesProvider('getExternalUrl', $scheme); + $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data); + } + } + + /** + * Provides test cases for testPseudoThemeStreams(). + * + * @param string $method + * The method to be tested. + * @param string $scheme + * The scheme to be tested. + * + * @return array + * Associative array with test cases as values, keyed by uri. + */ + protected function pseudoThemeStreamTestCasesProvider($method, $scheme) { + $uris = [ + "$scheme://", + "$scheme://logo.png", + "$scheme://color/preview.js", + ]; + + switch ($method) { + case 'dirname': + return array_combine($uris, [ + "$scheme://", + "$scheme://", + "$scheme://color", + ]); + case 'realpath': + return array_combine($uris, [ + DRUPAL_ROOT . '/core/themes/bartik', + DRUPAL_ROOT . '/core/themes/bartik/logo.png', + DRUPAL_ROOT . '/core/themes/bartik/color/preview.js', + ]); + case 'getExternalUrl': + $base_url = \Drupal::request()->getUriForPath(base_path()); + return array_combine($uris, [ $base_url . 'core/themes/bartik', $base_url . 'core/themes/bartik/logo.png', - $base_url . 'core/themes/bartik', - $base_url . 'core/themes/bartik/bartik.info.yml', - $base_url . 'core/themes/seven', - $base_url . 'core/themes/seven/fake.info.yml', - [new \InvalidArgumentException(), "Theme stark does not exist or is not installed"], + $base_url . 'core/themes/bartik/color/preview.js', ]); } } - /** * Helper method to run specific tests on each StreamWrapper method. * @@ -357,24 +448,25 @@ protected function themeStreamTestCasesProvider($method) { * - The test result message. */ private function runTestOnInstanceMethod(StreamWrapperInterface $instance, $method, array $data) { - foreach ($data as $uri => $info) { + foreach ($data as $uri => $case) { $instance->setUri($uri); - if (is_array($info)) { - $message = sprintf('Exception thrown: \InvalidArgumentException("%s").', $info[1]); + if ($case instanceof \InvalidArgumentException) { + /** @var \InvalidArgumentException $case */ + $message = sprintf('Exception thrown: \InvalidArgumentException("%s").', $case->getMessage()); try { $instance->$method(); $this->fail($message); } catch (\InvalidArgumentException $e) { - $this->assertIdentical($e->getMessage(), $info[1], $message); + $this->assertIdentical($e->getMessage(), $case->getMessage(), $message); } } - else { + elseif (is_string($case)) { if ($method == 'realpath') { // realpath() returns strings with OS-specific DIRECTORY_SEPARATOR. - $info = str_replace('/', DIRECTORY_SEPARATOR, $info); + $case = str_replace('/', DIRECTORY_SEPARATOR, $case); } - $this->assertEqual($instance->$method(), $info); + $this->assertEqual($instance->$method(), $case); } } }