diff --git a/core/config/core.extension.yml b/core/config/core.extension.yml
index eae39ef..1514a9e 100644
--- a/core/config/core.extension.yml
+++ b/core/config/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 f36eb44..47377f4 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -189,7 +189,7 @@ services:
arguments: ['%container.modules%', '@state', '@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', '@service_container', '@module_handler', '@cache.default', '@language_manager', '@string_translation']
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index aa158a2..d206e7f 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -685,6 +685,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,
@@ -1838,6 +1840,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 ab818ba..4f9c86d 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1076,6 +1076,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 b5d189c..cbbe811 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -9,26 +9,18 @@
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 controller 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 c63f679..12009a8 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -101,7 +101,19 @@ 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;
+ _drupal_theme_initialize(new Extension('theme', 'core/core.info.yml'));
+ return;
+ }
// Store the identifier for retrieving theme settings with.
$theme_key = $theme;
@@ -399,6 +411,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();
@@ -849,8 +863,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.
@@ -871,7 +885,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])) {
// Get the saved theme-specific settings from the configuration system.
$cache[$theme]->merge(\Drupal::config($theme . '.settings')->get());
@@ -973,7 +987,7 @@ function theme_settings_convert_to_config(array $theme_settings, Config $config)
* @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);
}
/**
@@ -988,7 +1002,7 @@ function theme_enable($theme_list) {
* @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 57183d6..171afa0 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -72,7 +72,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');
@@ -80,6 +80,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 aa3c709..aaceae9 100644
--- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
+++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
@@ -43,7 +43,7 @@ public function register(ContainerBuilder $container) {
$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 ac48c17..e70399d 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -656,8 +656,6 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// 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));
@@ -702,8 +700,18 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// 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);
}
@@ -810,6 +818,12 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// 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..804d99d 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -9,9 +9,9 @@
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\KeyValueStore\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\KeyValueStore\StateInterface
*/
- protected $cacheBackend;
+ protected $state;
/**
* The config installer to install configuration.
@@ -99,7 +99,7 @@ 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
+ * @param \Drupal\Core\KeyValueStore\StateInterface $state
* The cache backend to clear the local tasks cache.
* @param \Drupal\Core\Extension\InfoParserInterface $info_parser
* The info parser to parse the theme.info.yml files.
@@ -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 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle
/**
* {@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 isset($theme_data[$theme]->sort) ? $theme_data[$theme]->sort : 0;
+ }, $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,94 @@ public function enable(array $theme_list) {
->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;
+ // Do not disable the default theme.
+ if (in_array($name = $theme_config->get('default'), $theme_list, TRUE)) {
+ throw new \InvalidArgumentException("The current default theme $name cannot be disabled.");
+ }
+ // Do not disable the admin theme.
+ if (in_array($name = $theme_config->get('admin'), $theme_list, TRUE)) {
+ throw new \InvalidArgumentException("The current admin theme $name cannot be disabled.");
}
$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 +321,63 @@ public function disable(array $theme_list) {
* {@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 +387,8 @@ public function rebuildThemeData() {
$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 +413,12 @@ public function rebuildThemeData() {
);
$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 +433,8 @@ public function rebuildThemeData() {
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 +450,17 @@ public function rebuildThemeData() {
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..fbf57fd 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -17,11 +17,15 @@
*
* @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.
*
* @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.
@@ -32,13 +36,10 @@ public function enable(array $theme_list);
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 +77,14 @@ public function disable(array $theme_list);
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..7cc66f7 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -10,6 +10,7 @@
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,16 @@ public function __construct(CacheBackendInterface $cache, LockBackendInterface $
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 {
+ $this->theme = new Extension('theme', 'core/modules/system/system.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 084be15..cf02e09 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -152,7 +152,7 @@ function testImport() {
$installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
$uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
$expected = array('ban', 'action', 'text', 'options');
- $this->assertIdentical($expected, $installed, 'Ban, Action, Text and Options modules installed in the correct order.');
+ $this->assertIdentical($expected, $installed);
$this->assertTrue(empty($uninstalled), 'No modules uninstalled during import');
// Verify that the action.settings configuration object was only written
@@ -206,11 +206,11 @@ function testImport() {
$installed = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_installed', array());
$uninstalled = \Drupal::state()->get('ConfigImportUITest.core.extension.modules_uninstalled', array());
$expected = array('options', 'text', 'ban', 'action');
- $this->assertIdentical($expected, $uninstalled, 'Options, Text, Action and Ban modules uninstalled in the correct order.');
+ $this->assertIdentical($expected, $uninstalled);
$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 0db169e..00ff081 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 @@ public function __construct(CacheBackendInterface $cache_backend, LanguageManage
$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 405aaf5..4376a04 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
@@ -656,25 +656,6 @@ public function testAlterInfo() {
}
/**
- * 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 @@
+ '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 421791f..4f3c1b5 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 @@ public function systemAdminMenuBlockPage() {
*
* @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();
+ $themes = system_rebuild_theme_data();
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'])) {
@@ -321,6 +324,8 @@ public function themesPage() {
/**
* @todo Remove system_theme_default().
+ *
+ * @todo Move into ThemeController.
*/
public function themeSetDefault() {
module_load_include('admin.inc', 'system');
diff --git a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
index ece04d5..2fc246d 100644
--- a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
@@ -32,11 +32,12 @@ class ThemeController extends ControllerBase {
*/
public function disable(Request $request) {
$theme = $request->get('theme');
+ $theme_handler = \Drupal::service('theme_handler');
$config = $this->config('system.theme');
if (isset($theme)) {
// Get current list of themes.
- $themes = list_themes();
+ $themes = $theme_handler->listInfo();
// Check if the specified theme is one recognized by the system.
if (!empty($themes[$theme])) {
@@ -45,7 +46,7 @@ public function disable(Request $request) {
drupal_set_message(t('%theme is the default theme and cannot be disabled.', array('%theme' => $themes[$theme]->info['name'])), 'error');
}
else {
- theme_disable(array($theme));
+ $theme_handler->disable(array($theme));
drupal_set_message(t('The %theme theme has been disabled.', array('%theme' => $themes[$theme]->info['name'])));
}
}
@@ -74,14 +75,11 @@ public function disable(Request $request) {
*/
public function enable(Request $request) {
$theme = $request->get('theme');
+ $theme_handler = \Drupal::service('theme_handler');
if (isset($theme)) {
- // Get current list of themes.
- $themes = list_themes(TRUE);
-
- // Check if the specified theme is one recognized by the system.
- if (!empty($themes[$theme])) {
- theme_enable(array($theme));
+ if ($theme_handler->enable(array($theme))) {
+ $themes = $theme_handler->listInfo();
drupal_set_message(t('The %theme theme has been enabled.', array('%theme' => $themes[$theme]->info['name'])));
}
else {
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..06521a7 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 @@ public static function getInfo() {
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::service('theme_handler')->enable(array('seven', 'bartik'));
\Drupal::config('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/Extension/ThemeHandlerTest.php b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php
new file mode 100644
index 0000000..95fdee9
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php
@@ -0,0 +1,336 @@
+ '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.');
+ $this->assertTrue(system_rebuild_theme_data()['stark'], 'system_rebuild_theme_data() 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 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($message);
+ }
+
+ $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($message);
+ }
+
+ $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 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/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 @@ function testSystemInfoAlter() {
\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..14ec017 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
@@ -213,7 +213,7 @@ function testAdministrationTheme() {
// Reset to the default theme settings.
\Drupal::config('system.theme')
- ->set('default', 'bartik')
+ ->set('default', 'stark')
->save();
$edit = array(
'admin_theme' => '0',
@@ -222,10 +222,10 @@ function testAdministrationTheme() {
$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..9d34007 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,11 @@ function testFunctionOverride() {
* Test the list_themes() function.
*/
function testListThemes() {
+ \Drupal::service('theme_handler')->enable(array('test_subtheme'));
$themes = list_themes();
// 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 +223,7 @@ function testListThemes() {
* Test the theme_get_setting() function.
*/
function testThemeGetSetting() {
+ \Drupal::service('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 +280,4 @@ function testPreprocessHtml() {
$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 5689b3d..c920f2a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
@@ -77,24 +77,19 @@ function testTwigDebugOverride() {
*/
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']);
- // Reset the theme registry, so that the new theme is used.
- $this->container->set('theme.registry', NULL);
+ $theme_handler = \Drupal::service('theme_handler');
+ $theme_handler->enable(array('test_theme'));
+ $theme_handler->setDefault('test_theme');
- // Load array of Twig templates.
- $registry = $this->container->get('theme.registry');
- $registry->reset();
+ // The registry still works on theme globals, so set them here.
+ $GLOBALS['theme'] = 'test_theme';
+ $GLOBALS['theme_info'] = $theme_handler->listInfo()['test_theme'];
- $templates = $registry->getRuntime();
+ // Null out the container's theme registry to clear its properties.
+ $this->container->set('theme.registry', NULL);
+ // Load Twig templates in a new theme registry. reset() is necessary to
+ // invalidate the caches tagged with 'theme_registry'.
+ $templates = $this->container->get('theme.registry')->reset()->getRuntime();
// Get the template filename and the cache filename for
// theme_test.template_test.html.twig.
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 8cc1599..4d5d057 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -23,11 +23,10 @@ function system_theme_default() {
$themes = list_themes();
// 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)) {
- theme_enable(array($theme));
- }
+ // Or try to enable the theme.
+ if (isset($themes[$theme]) || \Drupal::service('theme_handler')->enable(array($theme))) {
+ $themes = list_themes();
+
// Set the default theme.
\Drupal::config('system.theme')
->set('default', $theme)
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 a870cb3..924eb12 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1301,51 +1301,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();
}
/**
@@ -1368,22 +1333,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 $theme
+ * A theme 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 (!is_object($theme)) {
+ $themes = list_themes();
+ 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/schema/test_basetheme.schema.yml b/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
new file mode 100644
index 0000000..9cfe9ec
--- /dev/null
+++ b/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
@@ -0,0 +1,10 @@
+test_basetheme.settings:
+ type: system.theme.global
+ label: 'Test base theme settings'
+ mapping:
+ 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_basetheme/config/system.date_format.fancy.yml b/core/modules/system/tests/themes/test_basetheme/config/system.date_format.fancy.yml
new file mode 100644
index 0000000..12ae886
--- /dev/null
+++ b/core/modules/system/tests/themes/test_basetheme/config/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/test_basetheme.settings.yml b/core/modules/system/tests/themes/test_basetheme/config/test_basetheme.settings.yml
new file mode 100644
index 0000000..4a06f9a
--- /dev/null
+++ b/core/modules/system/tests/themes/test_basetheme/config/test_basetheme.settings.yml
@@ -0,0 +1,4 @@
+features:
+ favicon: false
+base: only
+override: base
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..034734b
--- /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_default
+ 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_subtheme/config/test_subtheme.settings.yml b/core/modules/system/tests/themes/test_subtheme/config/test_subtheme.settings.yml
new file mode 100644
index 0000000..0589cab
--- /dev/null
+++ b/core/modules/system/tests/themes/test_subtheme/config/test_subtheme.settings.yml
@@ -0,0 +1 @@
+override: sub
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 @@ protected function setUp() {
// 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/system.theme.yml b/core/profiles/standard/config/system.theme.yml
new file mode 100644
index 0000000..57dadd4
--- /dev/null
+++ b/core/profiles/standard/config/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 26bef95..5f97703 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 @@
* @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 3864923..89577f3 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -8,9 +8,11 @@
namespace Drupal\Tests\Core\Extension;
use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\InfoParser;
use Drupal\Core\Extension\ThemeHandler;
-use Drupal\Core\Config\ConfigInstaller;
+use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
+use Drupal\Core\KeyValueStore\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\KeyValueStore\StateInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $cacheBackend;
+ protected $state;
/**
* The mocked config factory.
@@ -79,6 +81,16 @@ class ThemeHandlerTest extends UnitTestCase {
*/
protected $themeHandler;
+ protected $config = array(
+ 'core.extension' => array(
+ 'module' => array(),
+ 'theme' => array(),
+ 'disabled' => array(
+ 'theme' => array(),
+ ),
+ ),
+ );
+
/**
* {@inheritdoc}
*/
@@ -94,17 +106,9 @@ public static function getInfo() {
* {@inheritdoc}
*/
protected function setUp() {
- $this->configFactory = $this->getConfigFactoryStub(array(
- 'core.extension' => array(
- 'module' => array(),
- 'theme' => array(),
- 'disabled' => array(
- 'theme' => array(),
- ),
- ),
- ));
+ $this->configFactory = $this->getConfigFactoryStub($this->config);
$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,14 +117,17 @@ protected function setUp() {
$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);
+ $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+ $this->getContainerWithCacheBins($cache_backend);
}
/**
* Tests enabling a theme with a name longer than 50 chars.
*
+ * @requires function system__too_much_mocking
+ *
* @expectedException \Drupal\Core\Extension\ExtensionNameLengthException
* @expectedExceptionMessage Theme name thisNameIsFarTooLong0000000000000000000000000000051 is over the maximum allowed length of 50 characters.
*/
@@ -131,6 +138,8 @@ public function testThemeEnableWithTooLongName() {
/**
* Tests enabling a single theme.
*
+ * @requires function system__too_much_mocking
+ *
* @see \Drupal\Core\Extension\ThemeHandler::enable()
*/
public function testEnableSingleTheme() {
@@ -175,8 +184,89 @@ public function testEnableSingleTheme() {
}
/**
+ * Tests enabling a not existing theme.
+ *
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Unknown themes: not_existing
+ */
+ public function testEnableNotExistingTheme() {
+ $this->extensionDiscovery->expects($this->any())
+ ->method('scan')
+ ->will($this->returnValueMap(array(
+ array('theme_engine', NULL, array()),
+ array('theme', NULL, array())
+ )));
+ $this->moduleHandler->expects($this->any())
+ ->method('buildModuleDependencies')
+ ->will($this->returnValue(array()));
+
+ $this->themeHandler->enable(array('not_existing'));
+ }
+
+ /**
+ * Tests enabling a theme with too long name.
+ *
+ * @expectedException \Drupal\Core\Extension\ExtensionNameLengthException
+ * @expectedExceptionMessage Theme name test_theme_with_overwhelming_long_name_even_longer_than_you_expect_now is over the maximum allowed length of 50 characters.
+ */
+ public function testEnableTooLongName() {
+ $this->infoParser->expects($this->any())
+ ->method('parse')
+ ->will($this->returnCallback(function ($file) {
+ $info_parser = new InfoParser();
+ return $info_parser->parse($file);
+ }));
+
+ $this->extensionDiscovery->expects($this->any())
+ ->method('scan')
+ ->will($this->returnValueMap(array(
+ array('theme_engine', NULL, array()),
+ array('theme', NULL, array(
+ 'test_theme_with_overwhelming_long_name_even_longer_than_you_expect_now' => new Extension('theme', DRUPAL_ROOT . '/core/modules/system/tests/themes/test_theme/test_theme.info.yml', 'test_theme.info.yml')
+ ))
+ )));
+
+ $this->moduleHandler->expects($this->any())
+ ->method('buildModuleDependencies')
+ ->will($this->returnCallback(function($themes) {
+ $themes['test_theme_with_overwhelming_long_name_even_longer_than_you_expect_now']->requires = array();
+ return $themes;
+ }));
+
+ $this->themeHandler->enable(array('test_theme_with_overwhelming_long_name_even_longer_than_you_expect_now'));
+ }
+
+ /**
+ * Tests disabling the default theme.
+ *
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The current default theme test_theme cannot be disabled.
+ */
+ public function testDisableDefault() {
+ $this->config['system.theme']['default'] = 'test_theme';
+ $this->setUp();
+
+ $this->themeHandler->disable(array('test_theme'));
+ }
+
+ /**
+ * Tests disabling the admin theme.
+ *
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The current admin theme test_theme cannot be disabled.
+ */
+ public function testDisableAdmin() {
+ $this->config['system.theme']['admin'] = 'test_theme';
+ $this->setUp();
+
+ $this->themeHandler->disable(array('test_theme'));
+ }
+
+ /**
* Ensures that enabling a theme does clear the theme info listing.
*
+ * @requires function system__too_much_mocking
+ *
* @see \Drupal\Core\Extension\ThemeHandler::listInfo()
*/
public function testEnableAndListInfo() {
@@ -265,6 +355,9 @@ public function testRebuildThemeData() {
$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');
@@ -327,6 +420,9 @@ public function testRebuildThemeDataWithThemeParents() {
$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);
@@ -479,6 +575,10 @@ protected function systemThemeList() {
protected function systemListReset() {
}
+ public function setExtensionDiscovery(ExtensionDiscovery $extension_discovery) {
+ $this->extensionDiscovery = $extension_discovery;
+ }
+
}
if (!defined('DRUPAL_EXTENSION_NAME_MAX_LENGTH')) {