summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2014-04-22 18:37:12 (GMT)
committerwebchick2014-04-22 18:37:12 (GMT)
commit2c90c67c4eb72e4e7d305e730c4e2a9c0f3b3369 (patch)
tree53ec2826edd117d693cd936232a434bc69da2b66
parent45e59c9b6668199cdbd725cce2c751bfb7393327 (diff)
Issue #1067408 by alexpott, Jalandhar, jessebeach, Désiré, neetu morwani, dawehner, sun: Themes do not have an installation status.
-rw-r--r--core/config/install/core.extension.yml3
-rw-r--r--core/core.services.yml2
-rw-r--r--core/includes/install.core.inc30
-rw-r--r--core/includes/install.inc1
-rw-r--r--core/includes/module.inc59
-rw-r--r--core/includes/theme.inc34
-rw-r--r--core/includes/theme.maintenance.inc10
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php2
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandler.php18
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeHandler.php293
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php27
-rw-r--r--core/lib/Drupal/Core/Theme/Registry.php25
-rw-r--r--core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php2
-rw-r--r--core/modules/config_translation/config_translation.module22
-rw-r--r--core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php8
-rw-r--r--core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php19
-rw-r--r--core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php88
-rw-r--r--core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module12
-rw-r--r--core/modules/system/lib/Drupal/system/Controller/SystemController.php9
-rw-r--r--core/modules/system/lib/Drupal/system/Controller/ThemeController.php16
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php7
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php1
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php426
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php46
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php23
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php9
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php34
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php20
-rw-r--r--core/modules/system/system.install7
-rw-r--r--core/modules/system/system.module61
-rw-r--r--core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml11
-rw-r--r--core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml1
-rw-r--r--core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml3
-rw-r--r--core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml1
-rw-r--r--core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml7
-rw-r--r--core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml5
-rw-r--r--core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php6
-rw-r--r--core/profiles/standard/config/install/system.theme.yml2
-rw-r--r--core/profiles/standard/standard.info.yml3
-rw-r--r--core/profiles/standard/standard.install11
-rw-r--r--core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php143
41 files changed, 1036 insertions, 471 deletions
diff --git a/core/config/install/core.extension.yml b/core/config/install/core.extension.yml
index eae39ef..1514a9e 100644
--- a/core/config/install/core.extension.yml
+++ b/core/config/install/core.extension.yml
@@ -1,5 +1,4 @@
module: {}
-theme:
- stark: 0
+theme: {}
disabled:
theme: {}
diff --git a/core/core.services.yml b/core/core.services.yml
index 46ca46a..9b9c80b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -203,7 +203,7 @@ services:
arguments: ['%container.modules%', '@cache.bootstrap']
theme_handler:
class: Drupal\Core\Extension\ThemeHandler
- arguments: ['@config.factory', '@module_handler', '@cache.default', '@info_parser', '@config.installer', '@router.builder']
+ arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@config.installer', '@router.builder']
entity.manager:
class: Drupal\Core\Entity\EntityManager
arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation']
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 9f8088a..202f8d5 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -686,6 +686,8 @@ function install_tasks($install_state) {
'display_name' => t('Install site'),
'type' => 'batch',
),
+ 'install_profile_themes' => array(
+ ),
'install_import_translations' => array(
'display_name' => t('Set up translations'),
'display' => $needs_translations,
@@ -1828,6 +1830,34 @@ function install_profile_modules(&$install_state) {
}
/**
+ * Installs themes.
+ *
+ * This does not use a batch, since installing themes is faster than modules and
+ * because an installation profile typically enables 1-3 themes only (default
+ * theme, base theme, admin theme).
+ *
+ * @param $install_state
+ * An array of information about the current installation state.
+ */
+function install_profile_themes(&$install_state) {
+ $theme_handler = \Drupal::service('theme_handler');
+
+ // ThemeHandler::enable() resets the current list of themes. The theme used in
+ // the installer is not necessarily in the list of themes to install, so
+ // retain the current list.
+ // @see _drupal_maintenance_theme()
+ $current_themes = $theme_handler->listInfo();
+
+ // Install the themes specified by the installation profile.
+ $themes = $install_state['profile_info']['themes'];
+ $theme_handler->enable($themes);
+
+ foreach ($current_themes as $theme) {
+ $theme_handler->addTheme($theme);
+ }
+}
+
+/**
* Imports languages via a batch process during installation.
*
* @param $install_state
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 3789ff3..ac6e00e 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1077,6 +1077,7 @@ function install_profile_info($profile, $langcode = 'en') {
// Set defaults for module info.
$defaults = array(
'dependencies' => array(),
+ 'themes' => array('stark'),
'description' => '',
'version' => NULL,
'hidden' => FALSE,
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 18de4fc..cbbe811 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -9,26 +9,18 @@ use Drupal\Core\Cache\Cache;
use Drupal\Core\Extension\ExtensionDiscovery;
/**
- * Builds a list of bootstrap modules and enabled modules and themes.
+ * Builds a list of enabled themes.
*
* @param $type
* The type of list to return:
- * - module_enabled: All enabled modules.
- * - bootstrap: All enabled modules required for bootstrap.
- * - theme: All themes.
+ * - theme: All enabled themes.
*
* @return
- * An associative array of modules or themes, keyed by name. For $type
- * 'bootstrap' and 'module_enabled', the array values equal the keys.
+ * An associative array of themes, keyed by name.
* For $type 'theme', the array values are objects representing the
* respective database row, with the 'info' property already unserialized.
*
* @see list_themes()
- *
- * @todo There are too many layers/levels of caching involved for system_list()
- * data. Consider to add a \Drupal::config($name, $cache = TRUE) argument to allow
- * callers like system_list() to force-disable a possible configuration
- * storage cache or some other way to circumvent it/take it over.
*/
function system_list($type) {
$lists = &drupal_static(__FUNCTION__);
@@ -40,32 +32,15 @@ function system_list($type) {
'theme' => array(),
'filepaths' => array(),
);
- // Build a list of themes.
- $enabled_themes = \Drupal::config('core.extension')->get('theme') ?: array();
- // @todo Themes include all themes, including disabled/uninstalled. This
- // system.theme.data state will go away entirely as soon as themes have
- // a proper installation status.
- // @see http://drupal.org/node/1067408
- $theme_data = \Drupal::state()->get('system.theme.data');
- if (empty($theme_data)) {
- // @todo: system_list() may be called from _drupal_bootstrap_code(), in
- // which case system.module is not loaded yet.
- // Prevent a filesystem scan in drupal_load() and include it directly.
- // @see http://drupal.org/node/1067408
- require_once DRUPAL_ROOT . '/core/modules/system/system.module';
- $theme_data = system_rebuild_theme_data();
- }
+ // ThemeHandler maintains the 'system.theme.data' state record.
+ $theme_data = \Drupal::state()->get('system.theme.data', array());
foreach ($theme_data as $name => $theme) {
- $theme->status = (int) isset($enabled_themes[$name]);
$lists['theme'][$name] = $theme;
- // Build a list of filenames so drupal_get_filename can use it.
- if (isset($enabled_themes[$name])) {
- $lists['filepaths'][] = array(
- 'type' => 'theme',
- 'name' => $name,
- 'filepath' => $theme->getPathname(),
- );
- }
+ $lists['filepaths'][] = array(
+ 'type' => 'theme',
+ 'name' => $name,
+ 'filepath' => $theme->getPathname(),
+ );
}
\Drupal::cache('bootstrap')->set('system_list', $lists);
}
@@ -84,25 +59,17 @@ function system_list($type) {
function system_list_reset() {
drupal_static_reset('system_list');
drupal_static_reset('system_rebuild_module_data');
- drupal_static_reset('list_themes');
\Drupal::cache('bootstrap')->delete('system_list');
- \Drupal::cache()->delete('system_info');
// Clear the library info cache.
// Libraries may be provided by all extension types, and may be altered by any
// other extensions (types) due to the nature of
// \Drupal\Core\Extension\ModuleHandler::alter() and the fact that profiles
// are recorded and handled as modules.
+ // @todo Trigger an event upon module install/uninstall and theme
+ // enable/disable, and move this into an event subscriber.
+ // @see https://drupal.org/node/2206347
Cache::invalidateTags(array('extension' => TRUE));
-
- // Remove last known theme data state.
- // This causes system_list() to call system_rebuild_theme_data() on its next
- // invocation. When enabling a module that implements hook_system_info_alter()
- // to inject a new (testing) theme or manipulate an existing theme, then that
- // will cause system_list_reset() to be called, but theme data is not
- // necessarily rebuilt afterwards.
- // @todo Obsolete with proper installation status for themes.
- \Drupal::state()->delete('system.theme.data');
}
/**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d45c2e5..dc3570b 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -103,7 +103,21 @@ function drupal_theme_initialize() {
// Determine the active theme for the theme negotiator service. This includes
// the default theme as well as really specific ones like the ajax base theme.
$request = \Drupal::request();
- $theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request) ?: 'stark';
+ $theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request);
+
+ // If no theme could be negotiated, or if the negotiated theme is not within
+ // the list of enabled themes, fall back to the default theme output of core
+ // and modules (similar to Stark, but without a theme extension at all). This
+ // is possible, because _drupal_theme_initialize() always loads the Twig theme
+ // engine.
+ if (!$theme || !isset($themes[$theme])) {
+ $theme = 'core';
+ $theme_key = $theme;
+ // /core/core.info.yml does not actually exist, but is required because
+ // Extension expects a pathname.
+ _drupal_theme_initialize(new Extension('theme', 'core/core.info.yml'));
+ return;
+ }
// Store the identifier for retrieving theme settings with.
$theme_key = $theme;
@@ -401,6 +415,8 @@ function _theme($hook, $variables = array()) {
if (!$module_handler->isLoaded() && !defined('MAINTENANCE_MODE')) {
throw new Exception(t('_theme() may not be called until all modules are loaded.'));
}
+ // Ensure the theme is initialized.
+ drupal_theme_initialize();
/** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */
$theme_registry = \Drupal::service('theme.registry')->getRuntime();
@@ -851,8 +867,8 @@ function theme_get_setting($setting_name, $theme = NULL) {
// Get the values for the theme-specific settings from the .info.yml files
// of the theme and all its base themes.
- if ($theme) {
- $themes = list_themes();
+ $themes = list_themes();
+ if ($theme && isset($themes[$theme])) {
$theme_object = $themes[$theme];
// Create a list which includes the current theme and all its base themes.
@@ -874,7 +890,7 @@ function theme_get_setting($setting_name, $theme = NULL) {
// Get the global settings from configuration.
$cache[$theme]->merge(\Drupal::config('system.theme.global')->get());
- if ($theme) {
+ if ($theme && isset($themes[$theme])) {
// Retrieve configured theme-specific settings, if any.
try {
if ($theme_settings = \Drupal::config($theme . '.settings')->get()) {
@@ -975,13 +991,16 @@ function theme_settings_convert_to_config(array $theme_settings, Config $config)
* @param $theme_list
* An array of theme names.
*
+ * @return bool
+ * Whether any of the given themes have been enabled.
+ *
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::service('theme_handler')->enable().
*
* @see \Drupal\Core\Extension\ThemeHandler::enable().
*/
function theme_enable($theme_list) {
- \Drupal::service('theme_handler')->enable($theme_list);
+ return \Drupal::service('theme_handler')->enable($theme_list);
}
/**
@@ -990,13 +1009,16 @@ function theme_enable($theme_list) {
* @param $theme_list
* An array of theme names.
*
+ * @return bool
+ * Whether any of the given themes have been disabled.
+ *
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::service('theme_handler')->disable().
*
* @see \Drupal\Core\Extension\ThemeHandler::disable().
*/
function theme_disable($theme_list) {
- \Drupal::service('theme_handler')->disable($theme_list);
+ return \Drupal::service('theme_handler')->disable($theme_list);
}
/**
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 1cd436c..a79adca 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -66,7 +66,7 @@ function _drupal_maintenance_theme() {
}
// Ensure that system.module is loaded.
- if (!function_exists('_system_rebuild_theme_data')) {
+ if (!function_exists('system_rebuild_theme_data')) {
$module_handler = \Drupal::moduleHandler();
$module_handler->addModule('system', 'core/modules/system');
$module_handler->load('system');
@@ -74,6 +74,14 @@ function _drupal_maintenance_theme() {
$themes = list_themes();
+ // If no themes are installed yet, or if the requested custom theme is not
+ // installed, retrieve all available themes.
+ if (empty($themes) || !isset($themes[$custom_theme])) {
+ $theme_handler = \Drupal::service('theme_handler');
+ $themes = $theme_handler->rebuildThemeData();
+ $theme_handler->addTheme($themes[$custom_theme]);
+ }
+
// list_themes() triggers a \Drupal\Core\Extension\ModuleHandler::alter() in
// maintenance mode, but we can't let themes alter the .info.yml data until
// we know a theme's base themes. So don't set global $theme until after
diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
index 60dfb53..32ad9b7 100644
--- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
+++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
@@ -41,7 +41,7 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier
$container->register('theme_handler', 'Drupal\Core\Extension\ThemeHandler')
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('module_handler'))
- ->addArgument(new Reference('cache.default'))
+ ->addArgument(new Reference('state'))
->addArgument(new Reference('info_parser'));
}
}
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 537a844..08a3ec5 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -758,8 +758,6 @@ class ModuleHandler implements ModuleHandlerInterface {
// Refresh the schema to include it.
drupal_get_schema(NULL, TRUE);
- // Update the theme registry to include it.
- drupal_theme_rebuild();
// Allow modules to react prior to the installation of a module.
$this->invokeAll('module_preinstall', array($module));
@@ -804,8 +802,18 @@ class ModuleHandler implements ModuleHandlerInterface {
// Record the fact that it was installed.
$modules_installed[] = $module;
+ // Update the theme registry to include it.
+ drupal_theme_rebuild();
+
+ // Modules can alter theme info, so refresh theme data.
+ // @todo ThemeHandler cannot be injected into ModuleHandler, since that
+ // causes a circular service dependency.
+ // @see https://drupal.org/node/2208429
+ \Drupal::service('theme_handler')->refreshInfo();
+
// Allow the module to perform install tasks.
$this->invoke($module, 'install');
+
// Record the fact that it was installed.
watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO);
}
@@ -912,6 +920,12 @@ class ModuleHandler implements ModuleHandlerInterface {
// Update the theme registry to remove the newly uninstalled module.
drupal_theme_rebuild();
+ // Modules can alter theme info, so refresh theme data.
+ // @todo ThemeHandler cannot be injected into ModuleHandler, since that
+ // causes a circular service dependency.
+ // @see https://drupal.org/node/2208429
+ \Drupal::service('theme_handler')->refreshInfo();
+
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
$schema_store->delete($module);
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index b43d3e4..b3b368b 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -9,9 +9,9 @@ namespace Drupal\Core\Extension;
use Drupal\Component\Utility\String;
use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigInstallerInterface;
+use Drupal\Core\State\StateInterface;
use Drupal\Core\Routing\RouteBuilder;
/**
@@ -41,7 +41,7 @@ class ThemeHandler implements ThemeHandlerInterface {
*
* @var array
*/
- protected $list = array();
+ protected $list;
/**
* The config factory to get the enabled themes.
@@ -58,11 +58,11 @@ class ThemeHandler implements ThemeHandlerInterface {
protected $moduleHandler;
/**
- * The cache backend to clear the local tasks cache.
+ * The state backend.
*
- * @var \Drupal\Core\Cache\CacheBackendInterface
+ * @var \Drupal\Core\State\StateInterface
*/
- protected $cacheBackend;
+ protected $state;
/**
* The config installer to install configuration.
@@ -99,8 +99,8 @@ class ThemeHandler implements ThemeHandlerInterface {
* 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\State\StateInterface $state
+ * The state store.
* @param \Drupal\Core\Extension\InfoParserInterface $info_parser
* The info parser to parse the theme.info.yml files.
* @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
@@ -112,10 +112,10 @@ class ThemeHandler implements ThemeHandlerInterface {
* @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery
* (optional) A extension discovery instance (for unit tests).
*/
- public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
+ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
$this->configFactory = $config_factory;
$this->moduleHandler = $module_handler;
- $this->cacheBackend = $cache_backend;
+ $this->state = $state;
$this->infoParser = $info_parser;
$this->configInstaller = $config_installer;
$this->routeBuilder = $route_builder;
@@ -125,10 +125,90 @@ class ThemeHandler implements ThemeHandlerInterface {
/**
* {@inheritdoc}
*/
- public function enable(array $theme_list) {
- $this->clearCssCache();
+ public function getDefault() {
+ return $this->configFactory->get('system.theme')->get('default');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setDefault($name) {
+ if (!isset($this->list)) {
+ $this->listInfo();
+ }
+ if (!isset($this->list[$name])) {
+ throw new \InvalidArgumentException("$name theme is not enabled.");
+ }
+ $this->configFactory->get('system.theme')
+ ->set('default', $name)
+ ->save();
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enable(array $theme_list, $enable_dependencies = TRUE) {
$extension_config = $this->configFactory->get('core.extension');
+
+ $theme_data = $this->rebuildThemeData();
+
+ if ($enable_dependencies) {
+ $theme_list = array_combine($theme_list, $theme_list);
+
+ if ($missing = array_diff_key($theme_list, $theme_data)) {
+ // One or more of the given themes doesn't exist.
+ throw new \InvalidArgumentException(String::format('Unknown themes: !themes.', array(
+ '!themes' => implode(', ', $missing),
+ )));
+ }
+
+ // Only process themes that are not enabled currently.
+ $installed_themes = $extension_config->get('theme') ?: array();
+ if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
+ // Nothing to do. All themes already enabled.
+ return TRUE;
+ }
+ $installed_themes += $extension_config->get('disabled.theme') ?: array();
+
+ while (list($theme) = each($theme_list)) {
+ // Add dependencies to the list. The new themes will be processed as
+ // the while loop continues.
+ foreach (array_keys($theme_data[$theme]->requires) as $dependency) {
+ if (!isset($theme_data[$dependency])) {
+ // The dependency does not exist.
+ return FALSE;
+ }
+
+ // Skip already installed themes.
+ if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) {
+ $theme_list[$dependency] = $dependency;
+ }
+ }
+ }
+
+ // Set the actual theme weights.
+ $theme_list = array_map(function ($theme) use ($theme_data) {
+ return $theme_data[$theme]->sort;
+ }, $theme_list);
+
+ // Sort the theme list by their weights (reverse).
+ arsort($theme_list);
+ $theme_list = array_keys($theme_list);
+ }
+ else {
+ $installed_themes = $extension_config->get('theme') ?: array();
+ $installed_themes += $extension_config->get('disabled.theme') ?: array();
+ }
+
+ $themes_enabled = array();
foreach ($theme_list as $key) {
+ // Only process themes that are not already enabled.
+ $enabled = $extension_config->get("theme.$key") !== NULL;
+ if ($enabled) {
+ continue;
+ }
+
// Throw an exception if the theme name is too long.
if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
throw new ExtensionNameLengthException(String::format('Theme name %name is over the maximum allowed length of @max characters.', array(
@@ -143,53 +223,107 @@ class ThemeHandler implements ThemeHandlerInterface {
->clear("disabled.theme.$key")
->save();
- // Refresh the theme list as installation of default configuration needs
- // an updated list to work.
- $this->reset();
-
- // The default config installation storage only knows about the currently
- // enabled list of themes, so it has to be reset in order to pick up the
- // default config of the newly installed theme. However, do not reset the
- // source storage when synchronizing configuration, since that would
- // needlessly trigger a reload of the whole configuration to be imported.
- if (!$this->configInstaller->isSyncing()) {
- $this->configInstaller->resetSourceStorage();
+ // Add the theme to the current list.
+ // @todo Remove all code that relies on $status property.
+ $theme_data[$key]->status = 1;
+ $this->addTheme($theme_data[$key]);
+
+ // Update the current theme data accordingly.
+ $current_theme_data = $this->state->get('system.theme.data', array());
+ $current_theme_data[$key] = $theme_data[$key];
+ $this->state->set('system.theme.data', $current_theme_data);
+
+ // Reset theme settings.
+ $theme_settings = &drupal_static('theme_get_setting');
+ unset($theme_settings[$key]);
+
+ // @todo Remove system_list().
+ $this->systemListReset();
+
+ // Only install default configuration if this theme has not been installed
+ // already.
+ if (!isset($installed_themes[$key])) {
+ // The default config installation storage only knows about the currently
+ // enabled list of themes, so it has to be reset in order to pick up the
+ // default config of the newly installed theme. However, do not reset the
+ // source storage when synchronizing configuration, since that would
+ // needlessly trigger a reload of the whole configuration to be imported.
+ if (!$this->configInstaller->isSyncing()) {
+ $this->configInstaller->resetSourceStorage();
+ }
+
+ // Install default configuration of the theme.
+ $this->configInstaller->installDefaultConfig('theme', $key);
}
- // Install default configuration of the theme.
- $this->configInstaller->installDefaultConfig('theme', $key);
+
+ $themes_enabled[] = $key;
+
+ // Record the fact that it was enabled.
+ watchdog('system', '%theme theme enabled.', array('%theme' => $key), WATCHDOG_INFO);
}
+ $this->clearCssCache();
$this->resetSystem();
// Invoke hook_themes_enabled() after the themes have been enabled.
- $this->moduleHandler->invokeAll('themes_enabled', array($theme_list));
+ $this->moduleHandler->invokeAll('themes_enabled', array($themes_enabled));
+
+ return !empty($themes_enabled);
}
/**
* {@inheritdoc}
*/
public function disable(array $theme_list) {
- // Don't disable the default or admin themes.
$theme_config = $this->configFactory->get('system.theme');
- $default_theme = $theme_config->get('default');
- $admin_theme = $theme_config->get('admin');
- $theme_list = array_diff($theme_list, array($default_theme, $admin_theme));
- if (empty($theme_list)) {
- return;
+
+ foreach ($theme_list as $key) {
+ if (!isset($this->list[$key])) {
+ throw new \InvalidArgumentException("Unknown theme: $key.");
+ }
+ if ($key === $theme_config->get('default')) {
+ throw new \InvalidArgumentException("The current default theme $key cannot be disabled.");
+ }
+ if ($key === $theme_config->get('admin')) {
+ throw new \InvalidArgumentException("The current admin theme $key cannot be disabled.");
+ }
+ // Base themes cannot be disabled if sub themes are enabled, and if they
+ // are not disabled at the same time.
+ if (!empty($this->list[$key]->sub_themes)) {
+ foreach ($this->list[$key]->sub_themes as $sub_key => $sub_label) {
+ if (isset($this->list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) {
+ throw new \InvalidArgumentException("The base theme $key cannot be disabled, because theme $sub_key depends on it.");
+ }
+ }
+ }
}
$this->clearCssCache();
$extension_config = $this->configFactory->get('core.extension');
+ $current_theme_data = $this->state->get('system.theme.data', array());
foreach ($theme_list as $key) {
// The value is not used; the weight is ignored for themes currently.
$extension_config
->clear("theme.$key")
->set("disabled.theme.$key", 0);
+
+ // Remove the theme from the current list.
+ unset($this->list[$key]);
+
+ // Update the current theme data accordingly.
+ unset($current_theme_data[$key]);
+
+ // Reset theme settings.
+ $theme_settings = &drupal_static('theme_get_setting');
+ unset($theme_settings[$key]);
+
+ // @todo Remove system_list().
+ $this->systemListReset();
}
$extension_config->save();
+ $this->state->set('system.theme.data', $current_theme_data);
- $this->reset();
$this->resetSystem();
// Invoke hook_themes_disabled after the themes have been disabled.
@@ -200,52 +334,63 @@ class ThemeHandler implements ThemeHandlerInterface {
* {@inheritdoc}
*/
public function listInfo() {
- if (empty($this->list)) {
+ if (!isset($this->list)) {
$this->list = array();
- try {
- $themes = $this->systemThemeList();
+ $themes = $this->systemThemeList();
+ foreach ($themes as $theme) {
+ $this->addTheme($theme);
}
- catch (\Exception $e) {
- // If the database is not available, rebuild the theme data.
- $themes = $this->rebuildThemeData();
+ }
+ return $this->list;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTheme(Extension $theme) {
+ // @todo Remove this 100% unnecessary duplication of properties.
+ foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
+ foreach ($stylesheets as $stylesheet => $path) {
+ $theme->stylesheets[$media][$stylesheet] = $path;
}
+ }
+ foreach ($theme->info['libraries'] as $library => $name) {
+ $theme->libraries[$library] = $name;
+ }
+ if (isset($theme->info['engine'])) {
+ $theme->engine = $theme->info['engine'];
+ }
+ if (isset($theme->info['base theme'])) {
+ $theme->base_theme = $theme->info['base theme'];
+ }
+ $this->list[$theme->getName()] = $theme;
+ }
- foreach ($themes as $theme) {
- foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
- foreach ($stylesheets as $stylesheet => $path) {
- $theme->stylesheets[$media][$stylesheet] = $path;
- }
- }
- foreach ($theme->info['libraries'] as $library => $name) {
- $theme->libraries[$library] = $name;
- }
- if (isset($theme->info['engine'])) {
- $theme->engine = $theme->info['engine'];
- }
- if (isset($theme->info['base theme'])) {
- $theme->base_theme = $theme->info['base theme'];
- }
- // Status is normally retrieved from the database. Add zero values when
- // read from the installation directory to prevent notices.
- if (!isset($theme->status)) {
- $theme->status = 0;
- }
- $this->list[$theme->getName()] = $theme;
+ /**
+ * {@inheritdoc}
+ */
+ public function refreshInfo() {
+ $this->reset();
+ $extension_config = $this->configFactory->get('core.extension');
+ $enabled = $extension_config->get('theme') ?: array();
+
+ // @todo Avoid re-scanning all themes by retaining the original (unaltered)
+ // theme info somewhere.
+ $list = $this->rebuildThemeData();
+ foreach ($list as $name => $theme) {
+ if (isset($enabled[$name])) {
+ $this->list[$name] = $theme;
}
}
- return $this->list;
+ $this->state->set('system.theme.data', $this->list);
}
/**
* {@inheritdoc}
*/
public function reset() {
- // listInfo() calls system_info() which has a lot of side effects that have
- // to be triggered like the classloading of theme classes.
- $this->list = array();
$this->systemListReset();
- $this->listInfo();
- $this->list = array();
+ $this->list = NULL;
}
/**
@@ -255,6 +400,8 @@ class ThemeHandler implements ThemeHandlerInterface {
$listing = $this->getExtensionDiscovery();
$themes = $listing->scan('theme');
$engines = $listing->scan('theme_engine');
+ $extension_config = $this->configFactory->get('core.extension');
+ $enabled = $extension_config->get('theme') ?: array();
// Set defaults for theme info.
$defaults = array(
@@ -279,8 +426,12 @@ class ThemeHandler implements ThemeHandlerInterface {
);
$sub_themes = array();
+ $files = array();
// Read info files for each theme.
foreach ($themes as $key => $theme) {
+ // @todo Remove all code that relies on the $status property.
+ $theme->status = (int) isset($enabled[$key]);
+
$theme->info = $this->infoParser->parse($theme->getPathname()) + $defaults;
// Add the info file modification time, so it becomes available for
@@ -295,6 +446,8 @@ class ThemeHandler implements ThemeHandlerInterface {
if (!empty($theme->info['base theme'])) {
$sub_themes[] = $key;
+ // Add the base theme as a proper dependency.
+ $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme'];
}
// Defaults to 'twig' (see $defaults above).
@@ -310,7 +463,17 @@ class ThemeHandler implements ThemeHandlerInterface {
if (!empty($theme->info['screenshot'])) {
$theme->info['screenshot'] = $path . '/' . $theme->info['screenshot'];
}
+
+ $files[$key] = $theme->getPathname();
}
+ // Build dependencies.
+ // @todo Move into a generic ExtensionHandler base class.
+ // @see https://drupal.org/node/2208429
+ $themes = $this->moduleHandler->buildModuleDependencies($themes);
+
+ // Store filenames to allow system_list() and drupal_get_filename() to
+ // retrieve them without having to scan the filesystem.
+ $this->state->set('system.theme.files', $files);
// After establishing the full list of available themes, fill in data for
// sub-themes.
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
index 2ba52cd..c6194ea 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -17,28 +17,35 @@ interface ThemeHandlerInterface {
*
* @param array $theme_list
* An array of theme names.
+ * @param bool $enable_dependencies
+ * (optional) If TRUE, dependencies will automatically be installed in the
+ * correct order. This incurs a significant performance cost, so use FALSE
+ * if you know $theme_list is already complete and in the correct order.
+ *
+ * @return bool
+ * Whether any of the given themes have been enabled.
*
* @throws \Drupal\Core\Extension\ExtensionNameLengthException
* Thrown when the theme name is to long
*/
- public function enable(array $theme_list);
+ public function enable(array $theme_list, $enable_dependencies = TRUE);
/**
* Disables a given list of themes.
*
* @param array $theme_list
* An array of theme names.
+ *
+ * @return bool
+ * Whether any of the given themes have been disabled.
*/
public function disable(array $theme_list);
/**
- * Returns a list of all currently available themes.
- *
- * Retrieved from the database, if available and the site is not in
- * maintenance mode; otherwise compiled freshly from the filesystem.
+ * Returns a list of currently enabled themes.
*
* @return \Drupal\Core\Extension\Extension[]
- * An associative array of the currently available themes. The keys are the
+ * An associative array of the currently enabled themes. The keys are the
* themes' machine names and the values are objects having the following
* properties:
* - filename: The filepath and name of the .info.yml file.
@@ -76,6 +83,14 @@ interface ThemeHandlerInterface {
public function listInfo();
/**
+ * Refreshes the theme info data of currently enabled themes.
+ *
+ * Modules can alter theme info, so this is typically called after a module
+ * has been installed or uninstalled.
+ */
+ public function refreshInfo();
+
+ /**
* Resets the internal state of the theme handler.
*/
public function reset();
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index e84b33b..402c9f7 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -10,6 +10,7 @@ namespace Drupal\Core\Theme;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DestructableInterface;
+use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Utility\ThemeRegistry;
@@ -145,23 +146,17 @@ class Registry implements DestructableInterface {
protected function init($theme_name = NULL) {
// Unless instantiated for a specific theme, use globals.
if (!isset($theme_name)) {
- // #1: The theme registry might get instantiated before the theme was
- // initialized. Cope with that.
- if (!isset($GLOBALS['theme_info']) || !isset($GLOBALS['theme'])) {
- unset($this->runtimeRegistry);
- unset($this->registry);
- drupal_theme_initialize();
+ if (isset($GLOBALS['theme']) && isset($GLOBALS['theme_info'])) {
+ $this->theme = $GLOBALS['theme_info'];
+ $this->baseThemes = $GLOBALS['base_theme_info'];
+ $this->engine = $GLOBALS['theme_engine'];
}
- // #2: The testing framework only cares for the global $theme variable at
- // this point. Cope with that.
- if ($GLOBALS['theme'] != $GLOBALS['theme_info']->getName()) {
- unset($this->runtimeRegistry);
- unset($this->registry);
- $this->initializeTheme();
+ else {
+ // @see drupal_theme_initialize()
+ $this->theme = new Extension('theme', 'core/core.info.yml');
+ $this->baseThemes = array();
+ $this->engine = 'twig';
}
- $this->theme = $GLOBALS['theme_info'];
- $this->baseThemes = $GLOBALS['base_theme_info'];
- $this->engine = $GLOBALS['theme_engine'];
}
// Instead of the global theme, a specific theme was requested.
else {
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
index 92b7656..b95a666 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -211,7 +211,7 @@ class ConfigImportUITest extends WebTestBase {
$this->assertTrue(empty($installed), 'No modules installed during import');
$theme_info = \Drupal::service('theme_handler')->listInfo();
- $this->assertTrue(isset($theme_info['bartik']) && !$theme_info['bartik']->status, 'Bartik theme disabled during import.');
+ $this->assertFalse(isset($theme_info['bartik']), 'Bartik theme disabled during import.');
// Verify that the action.settings configuration object was only deleted
// once during the import process.
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 74047f7..e30f09a 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -56,6 +56,28 @@ function config_translation_theme() {
}
/**
+ * Implements hook_themes_enabled().
+ */
+function config_translation_themes_enabled() {
+ // Themes can provide *.config_translation.yml declarations.
+ // @todo Make ThemeHandler trigger an event instead and make
+ // ConfigMapperManager plugin manager subscribe to it.
+ // @see https://drupal.org/node/2206347
+ \Drupal::service('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
+}
+
+/**
+ * Implements hook_themes_disabled().
+ */
+function config_translation_themes_disabled() {
+ // Themes can provide *.config_translation.yml declarations.
+ // @todo Make ThemeHandler trigger an event instead and make
+ // ConfigMapperManager plugin manager subscribe to it.
+ // @see https://drupal.org/node/2206347
+ \Drupal::service('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
+}
+
+/**
* Implements hook_entity_type_alter().
*/
function config_translation_entity_type_alter(array &$entity_types) {
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php b/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php
index bdfbe55..e90c5bf 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php
@@ -67,6 +67,14 @@ class ConfigMapperManager extends DefaultPluginManager implements ConfigMapperMa
$this->typedConfigManager = $typed_config_manager;
// Look at all themes and modules.
+ // @todo If the list of enabled modules and themes is changed, new
+ // definitions are not picked up immediately and obsolete definitions are
+ // not removed, because the list of search directories is only compiled
+ // once in this constructor. The current code only works due to
+ // coincidence: The request that enables e.g. a new theme does not
+ // instantiate this plugin manager at the beginning of the request; when
+ // routes are being rebuilt at the end of the request, this service only
+ // happens to get instantiated with the updated list of enabled themes.
$directories = array();
foreach ($module_handler->getModuleList() as $name => $module) {
$directories[$name] = $module->getPath();
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
index 551b7f2..46ef47f 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
@@ -655,25 +655,6 @@ class ConfigTranslationUiTest extends WebTestBase {
}
/**
- * Tests that theme provided *.config_translation.yml files are found.
- */
- public function testThemeDiscovery() {
- // Enable the test theme and rebuild routes.
- $theme = 'config_translation_test_theme';
- theme_enable(array($theme));
- // Enabling a theme will cause the kernel terminate event to rebuild the
- // router. Simulate that here.
- \Drupal::service('router.builder')->rebuildIfNeeded();
-
- $this->drupalLogin($this->admin_user);
-
- $translation_base_url = 'admin/config/development/performance/translate';
- $this->drupalGet($translation_base_url);
- $this->assertResponse(200);
- $this->assertLinkByHref("$translation_base_url/fr/add");
- }
-
- /**
* Gets translation from locale storage.
*
* @param $config_name
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php
new file mode 100644
index 0000000..b51cee3
--- /dev/null
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_translation\Tests\ConfigTranslationUiThemeTest.
+ */
+
+namespace Drupal\config_translation\Tests;
+
+use Drupal\Core\Language\Language;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Functional tests for the Language list configuration forms.
+ */
+class ConfigTranslationUiThemeTest extends WebTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('config_translation', 'config_translation_test');
+
+ /**
+ * Languages to enable.
+ *
+ * @var array
+ */
+ protected $langcodes = array('fr', 'ta');
+
+ /**
+ * Administrator user for tests.
+ *
+ * @var \Drupal\user\UserInterface
+ */
+ protected $admin_user;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Theme Configuration Translation',
+ 'description' => 'Verifies theme configuration translation settings.',
+ 'group' => 'Configuration Translation',
+ );
+ }
+
+ public function setUp() {
+ parent::setUp();
+
+ $admin_permissions = array(
+ 'administer themes',
+ 'administer languages',
+ 'administer site configuration',
+ 'translate configuration',
+ );
+ // Create and login user.
+ $this->admin_user = $this->drupalCreateUser($admin_permissions);
+
+ // Add languages.
+ foreach ($this->langcodes as $langcode) {
+ $language = new Language(array('id' => $langcode));
+ language_save($language);
+ }
+ }
+
+ /**
+ * Tests that theme provided *.config_translation.yml files are found.
+ */
+ public function testThemeDiscovery() {
+ // Enable the test theme and rebuild routes.
+ $theme = 'config_translation_test_theme';
+
+ $this->drupalLogin($this->admin_user);
+
+ $this->drupalGet('admin/appearance');
+ $elements = $this->xpath('//a[normalize-space()=:label and contains(@href, :theme)]', array(
+ ':label' => 'Enable and set as default',
+ ':theme' => $theme,
+ ));
+ $this->drupalGet($GLOBALS['base_root'] . $elements[0]['href'], array('external' => TRUE));
+
+ $translation_base_url = 'admin/config/development/performance/translate';
+ $this->drupalGet($translation_base_url);
+ $this->assertResponse(200);
+ $this->assertLinkByHref("$translation_base_url/fr/add");
+ }
+
+}
diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module
index c71166f..d38be97 100644
--- a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module
+++ b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module
@@ -5,6 +5,18 @@
* Configuration Translation Test module.
*/
+use Drupal\Core\Extension\Extension;
+
+/**
+ * Implements hook_system_info_alter().
+ */
+function config_translation_test_system_info_alter(array &$info, Extension $file, $type) {
+ // @see \Drupal\config_translation\Tests\ConfigTranslationUiThemeTest
+ if ($file->getType() == 'theme' && $file->getName() == 'config_translation_test_theme') {
+ $info['hidden'] = FALSE;
+ }
+}
+
/**
* Implements hook_entity_type_alter().
*/
diff --git a/core/modules/system/lib/Drupal/system/Controller/SystemController.php b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
index eda620d..923fe6a 100644
--- a/core/modules/system/lib/Drupal/system/Controller/SystemController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
@@ -183,16 +183,19 @@ class SystemController extends ControllerBase {
*
* @return string
* An HTML string of the theme listing page.
+ *
+ * @todo Move into ThemeController.
*/
public function themesPage() {
$config = $this->config('system.theme');
- // Get current list of themes.
- $themes = $this->themeHandler->listInfo();
+ // Get all available themes.
+ $themes = $this->themeHandler->rebuildThemeData();
uasort($themes, 'system_sort_modules_by_info_name');
$theme_default = $config->get('default');
- $theme_groups = array();
+ $theme_groups = array('enabled' => array(), 'disabled' => array());
$admin_theme = $config->get('admin');
+ $admin_theme_options = array();
foreach ($themes as &$theme) {
if (!empty($theme->info['hidden'])) {
diff --git a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
index 86f6a4d..51d91f9 100644
--- a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
@@ -115,12 +115,8 @@ class ThemeController extends ControllerBase {
$theme = $request->get('theme');
if (isset($theme)) {
- // Get current list of themes.
- $themes = $this->themeHandler->listInfo();
-
- // Check if the specified theme is one recognized by the system.
- if (!empty($themes[$theme])) {
- $this->themeHandler->enable(array($theme));
+ if ($this->themeHandler->enable(array($theme))) {
+ $themes = $this->themeHandler->listInfo();
drupal_set_message($this->t('The %theme theme has been enabled.', array('%theme' => $themes[$theme]->info['name'])));
}
else {
@@ -154,11 +150,9 @@ class ThemeController extends ControllerBase {
$themes = $this->themeHandler->listInfo();
// Check if the specified theme is one recognized by the system.
- if (!empty($themes[$theme])) {
- // Enable the theme if it is currently disabled.
- if (empty($themes[$theme]->status)) {
- $this->themeHandler->enable(array($theme));
- }
+ // Or try to enable the theme.
+ if (isset($themes[$theme]) || $this->themeHandler->enable(array($theme))) {
+ $themes = $this->themeHandler->listInfo();
// Set the default theme.
$config->set('default', $theme)->save();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php
index f268e02..9c15510 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php
@@ -35,11 +35,12 @@ class PageTest extends WebTestBase {
function testBatchProgressPageTheme() {
// Make sure that the page which starts the batch (an administrative page)
// is using a different theme than would normally be used by the batch API.
- \Drupal::config('system.theme')
+ $this->container->get('theme_handler')->enable(array('seven', 'bartik'));
+ $this->container->get('config.factory')->get('system.theme')
->set('default', 'bartik')
+ ->set('admin', 'seven')
->save();
- theme_enable(array('seven'));
- \Drupal::config('system.theme')->set('admin', 'seven')->save();
+
// Log in as an administrator who can see the administrative theme.
$admin_user = $this->drupalCreateUser(array('view the administration theme'));
$this->drupalLogin($admin_user);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
index ef76a07..15a1904 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
@@ -33,7 +33,6 @@ class RenderElementTypesTest extends DrupalUnitTestBase {
protected function setUp() {
parent::setUp();
$this->installConfig(array('system'));
- $this->container->get('theme_handler')->enable(array('stark'));
}
/**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php
new file mode 100644
index 0000000..0f5ecdc
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php
@@ -0,0 +1,426 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Extension\ThemeHandlerTest.
+ */
+
+namespace Drupal\system\Tests\Extension;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Extension\ExtensionNameLengthException;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests installing/enabling, disabling, and uninstalling of themes.
+ */
+class ThemeHandlerTest extends DrupalUnitTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('system');
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Theme handler',
+ 'description' => 'Tests installing/enabling, disabling, and uninstalling of themes.',
+ 'group' => 'Extension',
+ );
+ }
+
+ public function containerBuild(ContainerBuilder $container) {
+ parent::containerBuild($container);
+ // Some test methods involve ModuleHandler operations, which attempt to
+ // rebuild and dump routes.
+ $container
+ ->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
+ }
+
+ function setUp() {
+ parent::setUp();
+ $this->installConfig(array('system'));
+ }
+
+ /**
+ * Verifies that no themes are installed/enabled/disabled by default.
+ */
+ function testEmpty() {
+ $this->assertFalse($this->extensionConfig()->get('theme'));
+ $this->assertFalse($this->extensionConfig()->get('disabled.theme'));
+
+ $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
+ $this->assertFalse(array_keys(system_list('theme')));
+
+ // Rebuilding available themes should always yield results though.
+ $this->assertTrue($this->themeHandler()->rebuildThemeData()['stark'], 'ThemeHandler::rebuildThemeData() yields all available themes.');
+
+ // theme_get_setting() should return global default theme settings.
+ $this->assertIdentical(theme_get_setting('features.favicon'), TRUE);
+ }
+
+ /**
+ * Tests enabling a theme.
+ */
+ function testEnable() {
+ $name = 'test_basetheme';
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(isset($themes[$name]));
+
+ $this->themeHandler()->enable(array($name));
+
+ $this->assertIdentical($this->extensionConfig()->get("theme.$name"), 0);
+ $this->assertNull($this->extensionConfig()->get("disabled.theme.$name"));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertEqual($themes[$name]->getName(), $name);
+
+ $this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
+
+ // Verify that test_basetheme.settings is active.
+ $this->assertIdentical(theme_get_setting('features.favicon', $name), FALSE);
+ $this->assertEqual(theme_get_setting('base', $name), 'only');
+ $this->assertEqual(theme_get_setting('override', $name), 'base');
+ }
+
+ /**
+ * Tests enabling a sub-theme.
+ */
+ function testEnableSubTheme() {
+ $name = 'test_subtheme';
+ $base_name = 'test_basetheme';
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(array_keys($themes));
+
+ $this->themeHandler()->enable(array($name));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$base_name]));
+
+ $this->themeHandler()->disable(array($name));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$base_name]));
+ }
+
+ /**
+ * Tests enabling a non-existing theme.
+ */
+ function testEnableNonExisting() {
+ $name = 'non_existing_theme';
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(array_keys($themes));
+
+ try {
+ $message = 'ThemeHandler::enable() throws InvalidArgumentException upon enabling a non-existing theme.';
+ $this->themeHandler()->enable(array($name));
+ $this->fail($message);
+ }
+ catch (\InvalidArgumentException $e) {
+ $this->pass(get_class($e) . ': ' . $e->getMessage());
+ }
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(array_keys($themes));
+ }
+
+ /**
+ * Tests enabling a theme with a too long name.
+ */
+ function testEnableNameTooLong() {
+ $name = 'test_theme_having_veery_long_name_which_is_too_long';
+
+ try {
+ $message = 'ThemeHandler::enable() throws ExtensionNameLengthException upon enabling a theme with a too long name.';
+ $this->themeHandler()->enable(array($name));
+ $this->fail($message);
+ }
+ catch (ExtensionNameLengthException $e) {
+ $this->pass(get_class($e) . ': ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * Tests disabling a theme.
+ */
+ function testDisable() {
+ $name = 'test_basetheme';
+ $this->themeHandler()->enable(array($name));
+
+ // Prime the relevant drupal_static()s.
+ $this->assertEqual(array_keys(system_list('theme')), array($name));
+ $this->assertIdentical(theme_get_setting('features.favicon', $name), FALSE);
+
+ $this->themeHandler()->disable(array($name));
+
+ $this->assertIdentical($this->extensionConfig()->get('theme'), array());
+ $this->assertIdentical($this->extensionConfig()->get("disabled.theme.$name"), 0);
+
+ $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
+ $this->assertFalse(array_keys(system_list('theme')));
+
+ // Verify that test_basetheme.settings no longer applies, even though the
+ // configuration still exists.
+ $this->assertIdentical(theme_get_setting('features.favicon', $name), TRUE);
+ $this->assertNull(theme_get_setting('base', $name));
+ $this->assertNull(theme_get_setting('override', $name));
+
+ // The theme is not uninstalled, so its configuration must still exist.
+ $this->assertTrue($this->config("$name.settings")->get());
+ }
+
+ /**
+ * Tests disabling and enabling a theme.
+ *
+ * Verifies that
+ * - themes can be re-enabled
+ * - default configuration is not re-imported upon re-enabling an already
+ * installed theme.
+ */
+ function testDisableEnable() {
+ $name = 'test_basetheme';
+
+ $this->themeHandler()->enable(array($name));
+ $this->themeHandler()->disable(array($name));
+
+ $this->assertIdentical($this->config("$name.settings")->get('base'), 'only');
+ $this->assertIdentical($this->config('system.date_format.fancy')->get('label'), 'Fancy date');
+
+ // Default configuration never overwrites custom configuration, so just
+ // changing values in existing configuration will cause ConfigInstaller to
+ // simply skip those files. To ensure that no default configuration is
+ // re-imported, the custom configuration has to be deleted.
+ $this->configStorage()->delete("$name.settings");
+ $this->configStorage()->delete('system.date_format.fancy');
+ // Reflect direct storage operations in ConfigFactory.
+ $this->container->get('config.factory')->reset();
+
+ $this->themeHandler()->enable(array($name));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertEqual($themes[$name]->getName(), $name);
+
+ $this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
+
+ $this->assertFalse($this->config("$name.settings")->get());
+ $this->assertNull($this->config('system.date_format.fancy')->get('label'));
+ }
+
+ /**
+ * Tests disabling the default theme.
+ */
+ function testDisableDefault() {
+ $name = 'stark';
+ $other_name = 'bartik';
+ $this->themeHandler()->enable(array($name, $other_name));
+ $this->themeHandler()->setDefault($name);
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$other_name]));
+
+ try {
+ $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling default theme.';
+ $this->themeHandler()->disable(array($name));
+ $this->fail($message);
+ }
+ catch (\InvalidArgumentException $e) {
+ $this->pass(get_class($e) . ': ' . $e->getMessage());
+ }
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$other_name]));
+ }
+
+ /**
+ * Tests disabling the admin theme.
+ */
+ function testDisableAdmin() {
+ $name = 'stark';
+ $other_name = 'bartik';
+ $this->themeHandler()->enable(array($name, $other_name));
+ $this->config('system.theme')->set('admin', $name)->save();
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$other_name]));
+
+ try {
+ $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling admin theme.';
+ $this->themeHandler()->disable(array($name));
+ $this->fail($message);
+ }
+ catch (\InvalidArgumentException $e) {
+ $this->pass(get_class($e) . ': ' . $e->getMessage());
+ }
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$other_name]));
+ }
+
+ /**
+ * Tests disabling a sub-theme.
+ */
+ function testDisableSubTheme() {
+ $name = 'test_subtheme';
+ $base_name = 'test_basetheme';
+
+ $this->themeHandler()->enable(array($name));
+ $this->themeHandler()->disable(array($name));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$base_name]));
+ }
+
+ /**
+ * Tests disabling a base theme before its sub-theme.
+ */
+ function testDisableBaseBeforeSubTheme() {
+ $name = 'test_basetheme';
+ $sub_name = 'test_subtheme';
+
+ $this->themeHandler()->enable(array($sub_name));
+
+ try {
+ $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling base theme before sub theme.';
+ $this->themeHandler()->disable(array($name));
+ $this->fail($message);
+ }
+ catch (\InvalidArgumentException $e) {
+ $this->pass(get_class($e) . ': ' . $e->getMessage());
+ }
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]));
+ $this->assertTrue(isset($themes[$sub_name]));
+
+ // Verify that disabling both at the same time works.
+ $this->themeHandler()->disable(array($name, $sub_name));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(isset($themes[$name]));
+ $this->assertFalse(isset($themes[$sub_name]));
+ }
+
+ /**
+ * Tests disabling a non-existing theme.
+ */
+ function testDisableNonExisting() {
+ $name = 'non_existing_theme';
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(array_keys($themes));
+
+ try {
+ $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling a non-existing theme.';
+ $this->themeHandler()->disable(array($name));
+ $this->fail($message);
+ }
+ catch (\InvalidArgumentException $e) {
+ $this->pass(get_class($e) . ': ' . $e->getMessage());
+ }
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(array_keys($themes));
+ }
+
+ /**
+ * Tests that theme info can be altered by a module.
+ *
+ * @see module_test_system_info_alter()
+ */
+ function testThemeInfoAlter() {
+ $name = 'seven';
+ $this->container->get('state')->set('module_test.hook_system_info_alter', TRUE);
+ $module_handler = $this->container->get('module_handler');
+
+ $this->themeHandler()->enable(array($name));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(isset($themes[$name]->info['regions']['test_region']));
+
+ $module_handler->install(array('module_test'), FALSE);
+ $this->assertTrue($module_handler->moduleExists('module_test'));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertTrue(isset($themes[$name]->info['regions']['test_region']));
+
+ // Legacy assertions.
+ // @todo Remove once theme initialization/info has been modernized.
+ // @see https://drupal.org/node/2228093
+ $info = system_get_info('theme', $name);
+ $this->assertTrue(isset($info['regions']['test_region']));
+ $regions = system_region_list($name);
+ $this->assertTrue(isset($regions['test_region']));
+ $system_list = system_list('theme');
+ $this->assertTrue(isset($system_list[$name]->info['regions']['test_region']));
+
+ $module_handler->uninstall(array('module_test'));
+ $this->assertFalse($module_handler->moduleExists('module_test'));
+
+ $themes = $this->themeHandler()->listInfo();
+ $this->assertFalse(isset($themes[$name]->info['regions']['test_region']));
+
+ // Legacy assertions.
+ // @todo Remove once theme initialization/info has been modernized.
+ // @see https://drupal.org/node/2228093
+ $info = system_get_info('theme', $name);
+ $this->assertFalse(isset($info['regions']['test_region']));
+ $regions = system_region_list($name);
+ $this->assertFalse(isset($regions['test_region']));
+ $system_list = system_list('theme');
+ $this->assertFalse(isset($system_list[$name]->info['regions']['test_region']));
+ }
+
+ /**
+ * Returns the theme handler service.
+ *
+ * @return \Drupal\Core\Extension\ThemeHandlerInterface
+ */
+ protected function themeHandler() {
+ return $this->container->get('theme_handler');
+ }
+
+ /**
+ * Returns the system.theme config object.
+ *
+ * @return \Drupal\Core\Config\Config
+ */
+ protected function extensionConfig() {
+ return $this->config('core.extension');
+ }
+
+ /**
+ * Returns a given config object.
+ *
+ * @param string $name
+ * The name of the config object to load.
+ *
+ * @return \Drupal\Core\Config\Config
+ */
+ protected function config($name) {
+ return $this->container->get('config.factory')->get($name);
+ }
+
+ /**
+ * Returns the active configuration storage.
+ *
+ * @return \Drupal\Core\Config\ConfigStorageInterface
+ */
+ protected function configStorage() {
+ return $this->container->get('config.storage');
+ }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
index 03f8ca1..3779f25 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
@@ -35,13 +35,6 @@ class MenuRouterTest extends WebTestBase {
*/
protected $default_theme;
- /**
- * Name of an alternate theme to use for tests.
- *
- * @var string
- */
- protected $alternate_theme;
-
public static function getInfo() {
return array(
'name' => 'Menu router',
@@ -359,42 +352,32 @@ class MenuRouterTest extends WebTestBase {
* Tests theme integration.
*/
public function testThemeIntegration() {
- $this->initializeTestThemeConfiguration();
+ $this->default_theme = 'bartik';
+ $this->admin_theme = 'seven';
+
+ $theme_handler = $this->container->get('theme_handler');
+ $theme_handler->enable(array($this->default_theme, $this->admin_theme));
+ $this->container->get('config.factory')->get('system.theme')
+ ->set('default', $this->default_theme)
+ ->set('admin', $this->admin_theme)
+ ->save();
+ $theme_handler->disable(array('stark'));
+
$this->doTestThemeCallbackMaintenanceMode();
- $this->initializeTestThemeConfiguration();
$this->doTestThemeCallbackFakeTheme();
- $this->initializeTestThemeConfiguration();
$this->doTestThemeCallbackAdministrative();
- $this->initializeTestThemeConfiguration();
$this->doTestThemeCallbackNoThemeRequested();
- $this->initializeTestThemeConfiguration();
$this->doTestThemeCallbackOptionalTheme();
}
/**
- * Explicitly set the default and admin themes.
- */
- protected function initializeTestThemeConfiguration() {
- $this->default_theme = 'bartik';
- $this->admin_theme = 'seven';
- $this->alternate_theme = 'stark';
- theme_enable(array($this->default_theme));
- \Drupal::config('system.theme')
- ->set('default', $this->default_theme)
- ->set('admin', $this->admin_theme)
- ->save();
- theme_disable(array($this->alternate_theme));
- }
-
- /**
* Test the theme negotiation when it is set to use an administrative theme.
*/
protected function doTestThemeCallbackAdministrative() {
- theme_enable(array($this->admin_theme));
$this->drupalGet('menu-test/theme-callback/use-admin-theme');
$this->assertText('Active theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme negotiation.');
$this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page.");
@@ -405,7 +388,6 @@ class MenuRouterTest extends WebTestBase {
*/
protected function doTestThemeCallbackMaintenanceMode() {
$this->container->get('state')->set('system.maintenance_mode', TRUE);
- theme_enable(array($this->admin_theme));
// For a regular user, the fact that the site is in maintenance mode means
// we expect the theme callback system to be bypassed entirely.
@@ -432,10 +414,14 @@ class MenuRouterTest extends WebTestBase {
$this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");
// Now enable the theme and request it again.
- theme_enable(array($this->alternate_theme));
+ $theme_handler = $this->container->get('theme_handler');
+ $theme_handler->enable(array('stark'));
+
$this->drupalGet('menu-test/theme-callback/use-stark-theme');
$this->assertText('Active theme: stark. Actual theme: stark.', 'The theme negotiation system uses an optional theme once it has been enabled.');
$this->assertRaw('stark/css/layout.css', "The optional theme's CSS appears on the page.");
+
+ $theme_handler->disable(array('stark'));
}
/**
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php b/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php
index 5175b7f..31387d0 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php
@@ -7,12 +7,15 @@
namespace Drupal\system\Tests\System;
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\DrupalUnitTestBase;
/**
* Tests the effectiveness of hook_system_info_alter().
*/
-class InfoAlterTest extends WebTestBase {
+class InfoAlterTest extends DrupalUnitTestBase {
+
+ public static $modules = array('system');
+
public static function getInfo() {
return array(
'name' => 'System info alter',
@@ -32,24 +35,12 @@ class InfoAlterTest extends WebTestBase {
\Drupal::state()->set('module_test.hook_system_info_alter', TRUE);
$info = system_rebuild_module_data();
$this->assertFalse(isset($info['node']->info['required']), 'Before the module_test is installed the node module is not required.');
- // Enable seven and the test module.
- theme_enable(array('seven'));
+
+ // Enable the test module.
\Drupal::moduleHandler()->install(array('module_test'), FALSE);
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('module_test'), 'Test module is enabled.');
- // Verify that the rebuilt and altered theme info is returned.
- $info = system_get_info('theme', 'seven');
- $this->assertTrue(isset($info['regions']['test_region']), 'Altered theme info was returned by system_get_info().');
- $seven_regions = system_region_list('seven');
- $this->assertTrue(isset($seven_regions['test_region']), 'Altered theme info was returned by system_region_list().');
- $system_list_themes = system_list('theme');
- $info = $system_list_themes['seven']->info;
- $this->assertTrue(isset($info['regions']['test_region']), 'Altered theme info was returned by system_list().');
- $list_themes = list_themes();
- $this->assertTrue(isset($list_themes['seven']->info['regions']['test_region']), 'Altered theme info was returned by list_themes().');
- system_list_reset();
$info = system_rebuild_module_data();
$this->assertTrue($info['node']->info['required'], 'After the module_test is installed the node module is required.');
- \Drupal::state()->set('module_test.hook_system_info_alter', FALSE);
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
index 520cb11..c7911b5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
@@ -178,7 +178,7 @@ class ThemeTest extends WebTestBase {
* Test the administration theme functionality.
*/
function testAdministrationTheme() {
- theme_enable(array('bartik', 'seven'));
+ $this->container->get('theme_handler')->enable(array('seven'));
// Enable an administration theme and show it on the node admin pages.
$edit = array(
@@ -212,9 +212,6 @@ class ThemeTest extends WebTestBase {
$this->assertRaw('core/themes/stark', 'Site default theme used on the add content page.');
// Reset to the default theme settings.
- \Drupal::config('system.theme')
- ->set('default', 'bartik')
- ->save();
$edit = array(
'admin_theme' => '0',
'use_admin_theme' => FALSE,
@@ -222,10 +219,10 @@ class ThemeTest extends WebTestBase {
$this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
$this->drupalGet('admin');
- $this->assertRaw('core/themes/bartik', 'Site default theme used on administration page.');
+ $this->assertRaw('core/themes/stark', 'Site default theme used on administration page.');
$this->drupalGet('node/add');
- $this->assertRaw('core/themes/bartik', 'Site default theme used on the add content page.');
+ $this->assertRaw('core/themes/stark', 'Site default theme used on the add content page.');
}
/**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
index 76edbcd..7d5e16e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
@@ -202,11 +202,13 @@ class ThemeTest extends WebTestBase {
* Test the list_themes() function.
*/
function testListThemes() {
- $themes = list_themes();
+ $theme_handler = $this->container->get('theme_handler');
+ $theme_handler->enable(array('test_subtheme'));
+ $themes = $theme_handler->listInfo();
+
// Check if drupal_theme_access() retrieves enabled themes properly from list_themes().
$this->assertTrue(drupal_theme_access('test_theme'), 'Enabled theme detected');
- // Check if list_themes() returns disabled themes.
- $this->assertTrue(array_key_exists('test_basetheme', $themes), 'Disabled theme detected');
+
// Check for base theme and subtheme lists.
$base_theme_list = array('test_basetheme' => 'Theme test base theme');
$sub_theme_list = array('test_subtheme' => 'Theme test subtheme');
@@ -223,6 +225,7 @@ class ThemeTest extends WebTestBase {
* Test the theme_get_setting() function.
*/
function testThemeGetSetting() {
+ $this->container->get('theme_handler')->enable(array('test_subtheme'));
$GLOBALS['theme_key'] = 'test_theme';
$this->assertIdentical(theme_get_setting('theme_test_setting'), 'default value', 'theme_get_setting() uses the default theme automatically.');
$this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), 'Base theme\'s default settings values can be overridden by subtheme.');
@@ -279,29 +282,4 @@ class ThemeTest extends WebTestBase {
$this->assertText('theme test page bottom markup', 'Modules are able to set the page bottom region.');
}
- /**
- * Test that themes can be disabled programmatically but admin theme and default theme can not.
- */
- function testDisableTheme() {
- // Enable Bartik, Seven and Stark.
- \Drupal::service('theme_handler')->enable(array('bartik', 'seven', 'stark'));
-
- // Set Bartik as the default theme and Seven as the admin theme.
- \Drupal::config('system.theme')
- ->set('default', 'bartik')
- ->set('admin', 'seven')
- ->save();
-
- $theme_list = array_keys(\Drupal::service('theme_handler')->listInfo());
- // Attempt to disable all themes. theme_disable() ensures that the default
- // theme and the admin theme will not be disabled.
- \Drupal::service('theme_handler')->disable($theme_list);
-
- $theme_list = \Drupal::service('theme_handler')->listInfo();
-
- // Ensure Bartik and Seven are still enabled and Stark is disabled.
- $this->assertTrue($theme_list['bartik']->status == 1, 'Default theme is enabled.');
- $this->assertTrue($theme_list['seven']->status == 1, 'Admin theme is enabled.');
- $this->assertTrue($theme_list['stark']->status == 0, 'Stark is disabled.');
- }
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
index e33f79b..6dff9ac 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
@@ -77,23 +77,21 @@ class TwigSettingsTest extends WebTestBase {
*/
function testTwigCacheOverride() {
$extension = twig_extension();
- theme_enable(array('test_theme'));
- \Drupal::config('system.theme')
- ->set('default', 'test_theme')
- ->save();
-
- // Unset the global variables, so \Drupal\Core\Theme\Registry::init() fires
- // drupal_theme_initialize, which fills up the global variables properly
- // and chosen the current active theme.
- unset($GLOBALS['theme_info']);
- unset($GLOBALS['theme']);
+ $theme_handler = $this->container->get('theme_handler');
+ $theme_handler->enable(array('test_theme'));
+ $theme_handler->setDefault('test_theme');
+
+ // The registry still works on theme globals, so set them here.
+ $GLOBALS['theme'] = 'test_theme';
+ $GLOBALS['theme_info'] = $theme_handler->listInfo()['test_theme'];
+
// Reset the theme registry, so that the new theme is used.
$this->container->set('theme.registry', NULL);
// Load array of Twig templates.
+ // reset() is necessary to invalidate caches tagged with 'theme_registry'.
$registry = $this->container->get('theme.registry');
$registry->reset();
-
$templates = $registry->getRuntime();
// Get the template filename and the cache filename for
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ca5d128..421491a 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -567,13 +567,6 @@ function system_requirements($phase) {
* Implements hook_install().
*/
function system_install() {
- // Enable and set the default theme. Can't use theme_enable() this early in
- // installation.
- \Drupal::service('config.installer')->installDefaultConfig('theme', 'stark');
- \Drupal::config('system.theme')
- ->set('default', 'stark')
- ->save();
-
// Populate the cron key state variable.
$cron_key = Crypt::randomBytesBase64(55);
\Drupal::state()->set('system.cron_key', $cron_key);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index d719b5a..50cd796 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1290,51 +1290,16 @@ function system_rebuild_module_data() {
}
/**
- * Helper function to scan and collect theme .info.yml data and their engines.
- *
- * @return \Drupal\Core\Extension\Extension[]
- * An associative array of themes information.
- *
- * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- * Use \Drupal::service('theme_handler')->rebuildThemeData().
- */
-function _system_rebuild_theme_data() {
- return \Drupal::service('theme_handler')->rebuildThemeData();
-}
-
-/**
* Rebuild, save, and return data about all currently available themes.
*
* @return \Drupal\Core\Extension\Extension[]
* Array of all available themes and their data.
+ *
+ * @deprecated 8.x
+ * Use \Drupal::service('theme_handler')->rebuildThemeData().
*/
function system_rebuild_theme_data() {
- $themes = _system_rebuild_theme_data();
- ksort($themes);
- // @todo This function has no business in determining/setting the status of
- // a theme, but various other functions expect it to return themes with a
- // $status property. system_list() stores the return value of this function
- // in state, and ensures to set/override the $status property for each theme
- // based on the current config. Remove this code when themes have a proper
- // installation status.
- // @see http://drupal.org/node/1067408
- $enabled_themes = \Drupal::config('core.extension')->get('theme') ?: array();
- $files = array();
- foreach ($themes as $name => $theme) {
- $theme->status = (int) isset($enabled_themes[$name]);
- $files[$name] = $theme->getPathname();
- }
- // Replace last known theme data state.
- // @todo Obsolete with proper installation status for themes.
- \Drupal::state()->set('system.theme.data', $themes);
-
- // Store filenames to allow system_list() and drupal_get_filename() to
- // retrieve them without having to rebuild or scan the filesystem.
- \Drupal::state()->set('system.theme.files', $files);
-
- return $themes;
+ return \Drupal::service('theme_handler')->rebuildThemeData();
}
/**
@@ -1357,22 +1322,24 @@ function _system_default_theme_features() {
/**
* Get a list of available regions from a specified theme.
*
- * @param $theme_key
- * The name of a theme.
+ * @param \Drupal\Core\Extension\Extension|string $theme
+ * A theme extension object, or the name of a theme.
* @param $show
* Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden
* regions.
* @return
* An array of regions in the form $region['name'] = 'description'.
*/
-function system_region_list($theme_key, $show = REGIONS_ALL) {
- $themes = list_themes();
- if (!isset($themes[$theme_key])) {
- return array();
+function system_region_list($theme, $show = REGIONS_ALL) {
+ if (!$theme instanceof Extension) {
+ $themes = \Drupal::service('theme_handler')->listInfo();
+ if (!isset($themes[$theme])) {
+ return array();
+ }
+ $theme = $themes[$theme];
}
-
$list = array();
- $info = $themes[$theme_key]->info;
+ $info = $theme->info;
// If requested, suppress hidden regions. See block_admin_display_form().
foreach ($info['regions'] as $name => $label) {
if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) {
diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml b/core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml
new file mode 100644
index 0000000..12ae886
--- /dev/null
+++ b/core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml
@@ -0,0 +1,11 @@
+# Themes are not supposed to provide/install this kind of config normally.
+# This exists for testing purposes only.
+# @see \Drupal\system\Tests\Extension\ThemeHandlerTest
+id: fancy
+label: 'Fancy date'
+status: true
+langcode: en
+locked: false
+pattern:
+ php: 'U'
+ intl: 'EEEE, LLLL d, yyyy - kk:mm'
diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml b/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml
index 2d5de34..4a06f9a 100644
--- a/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml
+++ b/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml
@@ -1,3 +1,4 @@
features:
favicon: false
base: only
+override: base
diff --git a/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml b/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
index 8a4bd45..e22c3c5 100644
--- a/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
+++ b/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
@@ -5,3 +5,6 @@ test_basetheme.settings:
base:
type: string
label: 'Base theme setting'
+ override:
+ type: string
+ label: 'Whether the setting has been overridden'
diff --git a/core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml b/core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml
new file mode 100644
index 0000000..0589cab
--- /dev/null
+++ b/core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml
@@ -0,0 +1 @@
+override: sub
diff --git a/core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml b/core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml
new file mode 100644
index 0000000..9c7b0ce
--- /dev/null
+++ b/core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml
@@ -0,0 +1,7 @@
+test_subtheme.settings:
+ type: theme_settings
+ label: 'Test sub theme settings'
+ mapping:
+ override:
+ type: string
+ label: 'Whether the setting has been overridden'
diff --git a/core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml b/core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml
new file mode 100644
index 0000000..fa0a207
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml
@@ -0,0 +1,5 @@
+type: theme
+core: 8.x
+name: 'Test theme with a too long name'
+version: VERSION
+hidden: true
diff --git a/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php b/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php
index e6da900..49dafdc 100644
--- a/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php
+++ b/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php
@@ -49,15 +49,13 @@ abstract class TourTestBasic extends TourTestBase {
// Make sure we are using distinct default and administrative themes for
// the duration of these tests.
+ $this->container->get('theme_handler')->enable(array('bartik', 'seven'));
$this->container->get('config.factory')
->get('system.theme')
->set('default', 'bartik')
- ->save();
- theme_enable(array('seven'));
- $this->container->get('config.factory')
- ->get('system.theme')
->set('admin', 'seven')
->save();
+
$this->permissions[] = 'view the administration theme';
//Create an admin user to view tour tips.
diff --git a/core/profiles/standard/config/install/system.theme.yml b/core/profiles/standard/config/install/system.theme.yml
new file mode 100644
index 0000000..57dadd4
--- /dev/null
+++ b/core/profiles/standard/config/install/system.theme.yml
@@ -0,0 +1,2 @@
+admin: seven
+default: bartik
diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml
index 30f9c261..3f8116b 100644
--- a/core/profiles/standard/standard.info.yml
+++ b/core/profiles/standard/standard.info.yml
@@ -35,3 +35,6 @@ dependencies:
- views
- views_ui
- tour
+themes:
+ - bartik
+ - seven
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 60c6f72..73ea074 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -14,15 +14,6 @@ use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
* @see system_install()
*/
function standard_install() {
- // Enable Bartik theme and set it as default theme instead of Stark.
- // @see system_install()
- $default_theme = 'bartik';
- \Drupal::config('system.theme')
- ->set('default', $default_theme)
- ->save();
- theme_enable(array($default_theme));
- theme_disable(array('stark'));
-
// Set front page to "node".
\Drupal::config('system.site')->set('page.front', 'node')->save();
@@ -78,7 +69,5 @@ function standard_install() {
$shortcut->save();
// Enable the admin theme.
- theme_enable(array('seven'));
- \Drupal::config('system.theme')->set('admin', 'seven')->save();
\Drupal::config('node.settings')->set('use_admin_theme', '1')->save();
}
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
index b9637fe..cdbd0f6 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -11,6 +11,8 @@ use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\InfoParser;
use Drupal\Core\Extension\ThemeHandler;
use Drupal\Core\Config\ConfigInstaller;
+use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
+use Drupal\Core\State\State;
use Drupal\Tests\UnitTestCase;
/**
@@ -38,11 +40,11 @@ class ThemeHandlerTest extends UnitTestCase {
protected $infoParser;
/**
- * The mocked cache backend.
+ * The mocked state backend.
*
- * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $cacheBackend;
+ protected $state;
/**
* The mocked config factory.
@@ -104,7 +106,7 @@ class ThemeHandlerTest extends UnitTestCase {
),
));
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
- $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+ $this->state = new State(new KeyValueMemoryFactory());
$this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface');
$this->configInstaller = $this->getMock('Drupal\Core\Config\ConfigInstallerInterface');
$this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder')
@@ -113,131 +115,10 @@ class ThemeHandlerTest extends UnitTestCase {
$this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
->disableOriginalConstructor()
->getMock();
- $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
+ $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
- $this->getContainerWithCacheBins($this->cacheBackend);
- }
-
- /**
- * Tests enabling a theme with a name longer than 50 chars.
- *
- * @expectedException \Drupal\Core\Extension\ExtensionNameLengthException
- * @expectedExceptionMessage Theme name <em class="placeholder">thisNameIsFarTooLong0000000000000000000000000000051</em> 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\Extension\ThemeHandler::enable()
- */
- public function testEnableSingleTheme() {
- $theme_list = array('theme_test');
-
- $this->configFactory->get('core.extension')
- ->expects($this->once())
- ->method('set')
- ->with('theme.theme_test', 0)
- ->will($this->returnSelf());
- $this->configFactory->get('core.extension')
- ->expects($this->once())
- ->method('save');
-
- $this->configFactory->get('core.extension')
- ->expects($this->once())
- ->method('clear')
- ->with('disabled.theme.theme_test')
- ->will($this->returnSelf());
- $this->configFactory->get('core.extension')
- ->expects($this->once())
- ->method('save');
-
- $this->extensionDiscovery->expects($this->any())
- ->method('scan')
- ->will($this->returnValue(array()));
-
- // Ensure that the themes_enabled hook is fired.
- $this->moduleHandler->expects($this->at(0))
- ->method('invokeAll')
- ->with('themes_enabled', array($theme_list));
-
- // Ensure the config installer will be called.
- $this->configInstaller->expects($this->once())
- ->method('installDefaultConfig')
- ->with('theme', $theme_list[0]);
-
- $this->themeHandler->enable($theme_list);
-
- $this->assertTrue($this->themeHandler->clearedCssCache);
- $this->assertTrue($this->themeHandler->registryRebuild);
- }
-
- /**
- * Ensures that enabling a theme does clear the theme info listing.
- *
- * @see \Drupal\Core\Extension\ThemeHandler::listInfo()
- */
- public function testEnableAndListInfo() {
- $this->configFactory->get('core.extension')
- ->expects($this->exactly(2))
- ->method('set')
- ->will($this->returnSelf());
-
- $this->configFactory->get('core.extension')
- ->expects($this->exactly(2))
- ->method('clear')
- ->will($this->returnSelf());
-
- $this->extensionDiscovery->expects($this->any())
- ->method('scan')
- ->will($this->returnValue(array()));
-
- $this->themeHandler->enable(array('bartik'));
- $this->themeHandler->systemList['bartik'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/bartik/bartik.info.yml', 'bartik.info.yml');
- $this->themeHandler->systemList['bartik']->info = array(
- 'stylesheets' => array(
- 'all' => array(
- 'css/layout.css',
- 'css/style.css',
- 'css/colors.css',
- ),
- ),
- 'libraries' => array(
- 'example/theme',
- ),
- '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']->libraries, $list_info['bartik']->libraries);
- $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'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml');
- $this->themeHandler->systemList['seven']->info = array(
- 'stylesheets' => array(
- 'screen' => array(
- 'style.css',
- ),
- ),
- 'libraries' => array(),
- );
- $this->themeHandler->systemList['seven']->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);
+ $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+ $this->getContainerWithCacheBins($cache_backend);
}
/**
@@ -265,6 +146,9 @@ class ThemeHandlerTest extends UnitTestCase {
$info_parser = new InfoParser();
return $info_parser->parse($file);
}));
+ $this->moduleHandler->expects($this->once())
+ ->method('buildModuleDependencies')
+ ->will($this->returnArgument(0));
$this->moduleHandler->expects($this->once())
->method('alter');
@@ -328,6 +212,9 @@ class ThemeHandlerTest extends UnitTestCase {
$info_parser = new InfoParser();
return $info_parser->parse($file);
}));
+ $this->moduleHandler->expects($this->once())
+ ->method('buildModuleDependencies')
+ ->will($this->returnArgument(0));
$theme_data = $this->themeHandler->rebuildThemeData();
$this->assertCount(2, $theme_data);