diff -u b/core/includes/install.core.inc b/core/includes/install.core.inc --- b/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -482,8 +482,7 @@ ->addArgument(new Reference('config.factory')) ->addArgument(new Reference('module_handler')) ->addArgument(new Reference('cache.cache')) - ->addArgument(new Reference('info_parser')) - ; + ->addArgument(new Reference('info_parser')); } // Set the request in the kernel to the new created Request above diff -u b/core/lib/Drupal/Core/Theme/ThemeHandler.php b/core/lib/Drupal/Core/Theme/ThemeHandler.php --- b/core/lib/Drupal/Core/Theme/ThemeHandler.php +++ b/core/lib/Drupal/Core/Theme/ThemeHandler.php @@ -7,10 +7,13 @@ namespace Drupal\Core\Theme; +use Drupal\Component\Utility\String; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\Extension\ExtensionNameLengthException; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Routing\RouteBuilder; +use Drupal\Core\SystemListing; use Drupal\Core\SystemListingInfo; use Drupal\Core\Extension\InfoParserInterface; @@ -33,25 +36,82 @@ protected $list = array(); - public function __construct(ConfigFactory $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, RouteBuilder $route_builder = NULL) { + /** + * The config factory to get the enabled themes. + * + * @var \Drupal\Core\Config\ConfigFactory + */ + protected $configFactory; + + /** + * The module handler to fire themes_enabled/themes_disabled hooks. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The cache backend to clear the local tasks cache. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cacheBackend; + + /** + * The info parser to parse the theme.info.yml files. + * + * @var \Drupal\Core\Extension\InfoParserInterface + */ + protected $infoParser; + + /** + * The route builder to rebuild the routes if a theme is enabled. + * + * @var \Drupal\Core\Routing\RouteBuilder + */ + protected $routerBuilder; + + /** + * The system listing info + * + * @var \Drupal\Core\SystemListingInfo + */ + protected $systemListingInfo; + + /** + * Constructs a new ThemeHandler. + * + * @param \Drupal\Core\Config\ConfigFactory $config_factory + * The config factory to get the enabled themes. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to fire themes_enabled/themes_disabled hooks. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * The cache backend to clear the local tasks cache. + * @param \Drupal\Core\Extension\InfoParserInterface $info_parser + * The info parser to parse the theme.info.yml files. + * @param \Drupal\Core\Routing\RouteBuilder $route_builder + * The route builder to rebuild the routes if a theme is enabled. + */ + public function __construct(ConfigFactory $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, RouteBuilder $route_builder = NULL, SystemListingInfo $system_list_info = NULL) { $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; $this->cacheBackend = $cache_backend; $this->infoParser = $info_parser; $this->routeBuilder = $route_builder; + $this->systemListingInfo = $system_list_info; } /** * {@inheritdoc} */ public function enable(array $theme_list) { - drupal_clear_css_cache(); + $this->clearCssCache(); $theme_config = $this->configFactory->get('system.theme'); $disabled_themes = $this->configFactory->get('system.theme.disabled'); foreach ($theme_list as $key) { // Throw an exception if the theme name is too long. if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) { - throw new ExtensionNameLengthException(format_string('Theme name %name is over the maximum allowed length of @max characters.', array( + throw new ExtensionNameLengthException(String::format('Theme name %name is over the maximum allowed length of @max characters.', array( '%name' => $key, '@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH, ))); @@ -116,7 +176,7 @@ // Also check that the site is not in the middle of an install or update. if (!defined('MAINTENANCE_MODE')) { try { - $themes = system_list('theme'); + $themes = $this->systemThemeList(); } catch (\Exception $e) { // If the database is not available, rebuild the theme data. @@ -166,7 +226,7 @@ */ public function rebuildThemeData() { // Find themes. - $listing = new SystemListingInfo(); + $listing = $this->getSystemListingInfo(); $themes = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes', 'name', 1); // Allow modules to add further themes. if ($module_themes = $this->moduleHandler->invokeAll('system_theme_info')) { @@ -181,7 +241,7 @@ } // Find theme engines - $listing = new SystemListingInfo(); + $listing = $this->getSystemListingInfo(); $engines = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines', 'name', 1); // Set defaults for theme info. @@ -225,7 +285,7 @@ // Invoke hook_system_info_alter() to give installed modules a chance to // modify the data in the .info.yml files if necessary. $type = 'theme'; - drupal_alter('system_info', $themes[$key]->info, $themes[$key], $type); + $this->moduleHandler->alter('system_info', $themes[$key]->info, $themes[$key], $type); if (!empty($themes[$key]->info['base theme'])) { $sub_themes[] = $key; @@ -348,6 +408,13 @@ return $current_base_theme; } + protected function getSystemListingInfo() { + if (!isset($this->systemListingInfo)) { + $this->systemListingInfo = new SystemListing(); + } + return $this->systemListingInfo; + } + /** * Installs the default theme config. * @@ -362,6 +429,8 @@ $this->routeBuilder->rebuild(); } + // @todo It feels wrong to have the requirement to clear the local tasks + // cache here. $this->cacheBackend->deleteTags(array('local_task' => 1)); $this->themeRegistryRebuild(); } @@ -377 +446,5 @@ -} + protected function systemThemeList() { + return system_list('theme'); + } + +} diff -u b/core/modules/system/system.module b/core/modules/system/system.module --- b/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2161,6 +2161,10 @@ if (drupal_container()->isScopeActive('request')) { $request = \Drupal::request(); $path = $request->attributes->get('_system_path'); + debug($path); + debug(path_is_admin($path)); + debug(user_access('view the administration theme')); + debug(\Drupal::config('system.theme')->get('admin')); if (user_access('view the administration theme') && path_is_admin($path)) { return \Drupal::config('system.theme')->get('admin'); } only in patch2: unchanged: --- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php +++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php @@ -9,6 +9,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderInterface; +use Symfony\Component\DependencyInjection\Reference; /** * ServiceProvider class for update.php service overrides. @@ -38,6 +39,12 @@ public function register(ContainerBuilder $container) { ->register("cache_factory", 'Drupal\Core\Cache\MemoryBackendFactory'); $container ->register('router.builder', 'Drupal\Core\Routing\RouteBuilderStatic'); + + $container->register('theme_handler', 'Drupal\Core\Theme\ThemeHandler') + ->addArgument(new Reference('config.factory')) + ->addArgument(new Reference('module_handler')) + ->addArgument(new Reference('cache.cache')) + ->addArgument(new Reference('info_parser')); } } only in patch2: unchanged: --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Theme/ThemeHandlerTest.php @@ -0,0 +1,261 @@ + 'Theme handler', + 'description' => 'Tests the theme handler.', + 'group' => 'Theme', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->configFactory = $this->getConfigFactoryStub(array('system.theme' => array(), 'system.theme.disabled' => array())); + $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface'); + $this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder') + ->disableOriginalConstructor() + ->getMock(); + $this->systemListingInfo = $this->getMockBuilder('Drupal\Core\SystemListingInfo') + ->disableOriginalConstructor() + ->getMock(); + + $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->routeBuilder, $this->systemListingInfo); + } + + /** + * Tests enabling a theme with a name longer than 50 chars. + * + * @expectedException \Drupal\Core\Extension\ExtensionNameLengthException + * @expectedExceptionMessage Theme name thisNameIsFarTooLong0000000000000000000000000000051 is over the maximum allowed length of 50 characters. + */ + public function testThemeEnableWithTooLongName() { + $this->themeHandler->enable(array('thisNameIsFarTooLong0000000000000000000000000000051')); + } + + /** + * Tests enabling a single theme. + * + * @see \Drupal\Core\Theme\ThemeHandler::enable() + */ + public function testEnableSingleTheme() { + $theme_list = array('theme_test'); + + $this->configFactory->get('system.theme') + ->expects($this->once()) + ->method('set') + ->with('enabled.theme_test', 0) + ->will($this->returnSelf()); + $this->configFactory->get('system.theme') + ->expects($this->once()) + ->method('save'); + + $this->configFactory->get('system.theme.disabled') + ->expects($this->once()) + ->method('clear') + ->with('theme_test') + ->will($this->returnSelf()); + $this->configFactory->get('system.theme.disabled') + ->expects($this->once()) + ->method('save'); + + // Ensure that the themes_enabled hook is fired. + $this->moduleHandler->expects($this->once()) + ->method('invokeAll') + ->with('themes_enabled', array($theme_list)); + + $this->themeHandler->enable($theme_list); + + $this->assertTrue($this->themeHandler->clearedCssCache); + $this->assertTrue($this->themeHandler->registryRebuild); + $this->assertTrue($this->themeHandler->installedDefaultConfig['theme_test']); + } + + /** + * Ensures that enabling a theme does clear the theme info listing. + * + * @see \Drupal\Core\Theme\ThemeHandler::listInfo() + */ + public function testEnableAndListInfo() { + $this->configFactory->get('system.theme') + ->expects($this->exactly(2)) + ->method('set') + ->will($this->returnSelf()); + + $this->configFactory->get('system.theme.disabled') + ->expects($this->exactly(2)) + ->method('clear') + ->will($this->returnSelf()); + + $this->themeHandler->enable(array('bartik')); + $this->themeHandler->systemList['bartik'] = (object) array( + 'name' => 'bartik', + 'info' => array( + 'stylesheets' => array( + 'all' => array( + 'css/layout.css', + 'css/style.css', + 'css/colors.css', + ), + ), + 'scripts' => array( + 'example' => 'theme.js', + ), + 'engine' => 'twig', + 'base theme' => 'stark', + ), + ); + + $list_info = $this->themeHandler->listInfo(); + $this->assertCount(1, $list_info); + + $this->assertEquals($this->themeHandler->systemList['bartik']->info['stylesheets'], $list_info['bartik']->stylesheets); + $this->assertEquals($this->themeHandler->systemList['bartik']->scripts, $list_info['bartik']->scripts); + $this->assertEquals('twig', $list_info['bartik']->engine); + $this->assertEquals('stark', $list_info['bartik']->base_theme); + $this->assertEquals(0, $list_info['bartik']->status); + + $this->themeHandler->systemList['seven'] = (object) array( + 'name' => 'seven', + 'info' => array( + 'stylesheets' => array( + 'screen' => array( + 'style.css', + ), + ), + 'scripts' => array(), + ), + 'status' => 1, + ); + + $this->themeHandler->enable(array('seven')); + + $list_info = $this->themeHandler->listInfo(); + $this->assertCount(2, $list_info); + + $this->assertEquals($this->themeHandler->systemList['seven']->info['stylesheets'], $list_info['seven']->stylesheets); + $this->assertEquals(1, $list_info['seven']->status); + } + +} + +/** + * Extends the default theme handler to mock some drupal_ methods. + */ +class TestThemeHandler extends ThemeHandler { + + /** + * {@inheritdoc} + */ + protected function clearCssCache() { + $this->clearedCssCache = TRUE; + } + + /** + * {@inheritdoc} + */ + protected function themeRegistryRebuild() { + $this->registryRebuild = TRUE; + } + + /** + * {@inheritdoc} + */ + protected function configInstallDefaultConfig($theme) { + $this->installedDefaultConfig[$theme] = TRUE; + } + + /** + * {@inheritdoc} + */ + protected function systemThemeList() { + return $this->systemList; + } + +} + +if (!defined('DRUPAL_EXTENSION_NAME_MAX_LENGTH')) { + define('DRUPAL_EXTENSION_NAME_MAX_LENGTH', 50); +} +if (!defined('DRUPAL_PHP_FUNCTION_PATTERN')) { + define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'); +} +if (!defined('DRUPAL_ROOT')) { + define('DRUPAL_ROOT', dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))))); +}