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 fe8d80d..a554f3d 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -187,7 +187,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.discovery', '@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 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 52ce316..cc764f1 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -103,7 +103,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; @@ -401,6 +413,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 +865,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 +888,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()) { @@ -981,7 +995,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); } /** @@ -996,7 +1010,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..b47fea1 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 $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,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 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 @@ 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..2c6752f --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php @@ -0,0 +1,335 @@ + '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 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 e33f79b..0dba3a9 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 52998de..2a5ff49 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -24,11 +24,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 ad8c084..42f8369 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1296,51 +1296,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(); } /** @@ -1363,22 +1328,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 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_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 index 2d5de34..4a06f9a 100644 --- 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 @@ -1,3 +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..7c4eeda 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\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. @@ -104,7 +106,7 @@ protected function setUp() { ), )); $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 +115,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 +136,8 @@ public function testThemeEnableWithTooLongName() { /** * Tests enabling a single theme. * + * @requires function system__too_much_mocking + * * @see \Drupal\Core\Extension\ThemeHandler::enable() */ public function testEnableSingleTheme() { @@ -177,6 +184,8 @@ public function testEnableSingleTheme() { /** * 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 +274,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 +339,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);