diff --git a/core/includes/module.inc b/core/includes/module.inc index 00a0da2..557f6dc 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: 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__); @@ -42,21 +34,12 @@ function system_list($type) { ); // Build a list of themes. $enabled_themes = (array) \Drupal::config('system.theme')->get('enabled'); - array_walk($enabled_themes, function (&$value, $key) { - $value = 'system.theme.data.' . $key; - }); - $theme_data = (array) \Drupal::state()->getMultiple($enabled_themes); - // @todo Remove me? - if (0 && empty($theme_data)) { - // system_list() may be called from _drupal_bootstrap_code() and - // which case system.module is not loaded yet. - // Prevent a filesystem scan in drupal_load() and include it directly. - require_once DRUPAL_ROOT . '/core/modules/system/system.module'; - $theme_data = system_rebuild_theme_data(); - } + + $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. $lists['filepaths'][] = array( 'type' => 'theme', 'name' => $name, @@ -80,7 +63,6 @@ function system_list($type) { function system_list_reset() { drupal_static_reset('system_list'); drupal_static_reset('system_rebuild_module_data'); - drupal_static_reset('system_rebuild_theme_data'); drupal_static_reset('list_themes'); \Drupal::cache('bootstrap')->delete('system_list'); \Drupal::cache()->delete('system_info'); @@ -91,13 +73,6 @@ function system_list_reset() { // \Drupal\Core\Extension\ModuleHandler::alter() and the fact that profiles // are recorded and handled as modules. Cache::invalidateTags(array('extension' => TRUE)); - - // Rebuild theme data to update theme states. - // 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. - system_rebuild_theme_data(); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index f98df80..ba0412d 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -974,7 +974,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); } /** @@ -989,7 +989,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 3d4d8a9..9c87bd2 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -20,6 +20,11 @@ function _drupal_maintenance_theme() { global $theme, $theme_key; + // If $theme is already set, assume the others are set too, and do nothing. + if (isset($theme)) { + return; + } + require_once DRUPAL_ROOT . '/' . Settings::get('path_inc', 'core/includes/path.inc'); require_once __DIR__ . '/theme.inc'; require_once __DIR__ . '/common.inc'; @@ -67,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'); @@ -79,7 +84,9 @@ function _drupal_maintenance_theme() { // If retrieval failed or 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])) { - $themes = _system_rebuild_theme_data(); + $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 diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index 9c437de..78c7a2b 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -41,7 +41,7 @@ class ThemeHandler implements ThemeHandlerInterface { * * @var array */ - protected $list = array(); + protected $list; /** * The config factory to get the enabled themes. @@ -128,48 +128,53 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle * @todo Rename to install(). */ public function enable(array $theme_list, $enable_dependencies = TRUE) { + $theme_config = $this->configFactory->get('system.theme'); + $disabled_themes = $this->configFactory->get('system.theme.disabled'); if ($enable_dependencies) { // Get all theme data so we can find dependencies and sort. - $theme_data = $this->rebuildThemeData(); - // Create an associative array with weights as values. - $theme_list = array_flip(array_values($theme_list)); + $theme_data = system_rebuild_theme_data(); + $theme_list = array_combine($theme_list, $theme_list); - while (list($theme) = each($theme_list)) { - if (!isset($theme_data[$theme])) { - // This theme is not found in the filesystem, abort. - return FALSE; - } - if ($theme_data[$theme]->status) { - // Skip already enabled themes. - unset($theme_list[$theme]); - continue; - } - $theme_list[$theme] = $theme_data[$theme]->sort; + if (array_diff_key($theme_list, $theme_data)) { + // One or more of the given themes doesn't exist. + return FALSE; + } + + // Only process themes that are not enabled currently. + $installed_themes = $theme_config->get('enabled') ?: array(); + if (!$theme_list = array_diff_key($theme_list, $installed_themes)) { + // Nothing to do. All themes already enabled. + return TRUE; + } + $installed_themes += $disabled_themes->get() ?: array(); - // Add dependencies to the list, with a placeholder weight. - // The new themes will be processed as the while loop continues. + 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_list[$dependency])) { - $theme_list[$dependency] = 0; + if (!isset($theme_data[$dependency])) { + // The dependency does not exist. + return FALSE; } - } - if (!$theme_list) { - // Nothing to do. All themes already enabled. - return TRUE; + // Skip already installed themes. + if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) { + $theme_list[$dependency] = $dependency; + } } } - // Sort the theme list by pre-calculated weights. + // 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); } - $this->clearCssCache(); $themes_installed = array(); - $themes_enabled = array(); - $theme_config = $this->configFactory->get('system.theme'); - $disabled_themes = $this->configFactory->get('system.theme.disabled'); foreach ($theme_list as $key) { // Only process themes that are not already enabled. $enabled = $theme_config->get("enabled.$key") !== NULL; @@ -196,14 +201,19 @@ public function enable(array $theme_list, $enable_dependencies = TRUE) { // Install default configuration of the theme. $this->configInstaller->installDefaultConfig('theme', $key); + $themes_installed[] = $key; + // Record the fact that it was installed. watchdog('system', '%theme theme installed.', array('%theme' => $key), WATCHDOG_INFO); } $this->resetSystem(); + $this->clearCssCache(); // 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_installed)); + + return !empty($themes_installed); } /** @@ -214,10 +224,7 @@ public function enable(array $theme_list, $enable_dependencies = TRUE) { public function disable(array $theme_list) { // Don't disable the default theme. if ($pos = array_search($this->configFactory->get('system.theme')->get('default'), $theme_list) !== FALSE) { - unset($theme_list[$pos]); - if (empty($theme_list)) { - return; - } + throw new \RuntimeException('The current default theme cannot be disabled.'); } $this->clearCssCache(); @@ -243,52 +250,41 @@ 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(); - } - catch (\Exception $e) { - // If the database is not available, rebuild the theme data. - $themes = $this->rebuildThemeData(); - } - + $themes = $this->systemThemeList(); foreach ($themes as $theme) { - foreach ($theme->info['stylesheets'] as $media => $stylesheets) { - foreach ($stylesheets as $stylesheet => $path) { - $theme->stylesheets[$media][$stylesheet] = $path; - } - } - foreach ($theme->info['scripts'] as $script => $path) { - $theme->scripts[$script] = $path; - } - 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; + $this->addTheme($theme); } } return $this->list; } + 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['scripts'] as $script => $path) { + $theme->scripts[$script] = $path; + } + 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; + } + /** * {@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->list = NULL; $this->systemListReset(); - $this->listInfo(); - $this->list = array(); } /** @@ -338,8 +334,7 @@ public function rebuildThemeData() { if (!empty($theme->info['base theme'])) { $sub_themes[] = $key; - // Add the defined base theme as dependency to the theme, in order to - // re-use the dependency graph resolution of _module_build_dependencies(). + // Add the base theme as a proper dependency. $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme']; } diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php index 013f8b9..92ab92f 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php @@ -18,19 +18,12 @@ * @param array $theme_list * An array of theme names. * @param bool $enable_dependencies - * If TRUE, dependencies will automatically be added and enabled in the + * (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 - * - * @todo D8: This is almost identical to module_enable() now. Only differences: - * - There is no required default .theme PHP file to load. - * - There is no schema to install. - * - System list, state, theme list, and static resets slightly differ. - * @todo hook_themes_installed(), hook_themes_enabled(), etc are not invoked in - * themes, since they are excluded from the hook system. */ public function enable(array $theme_list, $enable_dependencies = TRUE); @@ -43,13 +36,10 @@ public function enable(array $theme_list, $enable_dependencies = TRUE); 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. diff --git a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php index 2389a62..6c393b1 100644 --- a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php +++ b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php @@ -30,11 +30,10 @@ public function register(ContainerBuilder $container) { $container->register('config.storage', 'Drupal\Core\Config\InstallStorage'); // Replace services with in-memory implementations. - foreach (array('bootstrap', 'config', 'cache', 'menu', 'page', 'path') as $bin) { - $container - ->register("cache.$bin", 'Drupal\Core\Cache\MemoryBackend') - ->addArgument($bin); - } + $definition = $container->getDefinition('cache_factory'); + $definition->setClass('Drupal\Core\Cache\MemoryBackendFactory'); + $definition->setArguments(array()); + $definition->setMethodCalls(array()); $container ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory'); $container diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php index 181f5b9..d553440 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php @@ -37,9 +37,9 @@ function testAdminTheme() { $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes')); $this->drupalLogin($admin_user); - // Ensure the block admin page cannot be accessed for a disabled theme. + // Ensure that access to block admin page is denied when theme is disabled. $this->drupalGet('admin/structure/block/list/bartik'); - $this->assertResponse(404); + $this->assertResponse(403); // Enable admin theme and confirm that tab is accessible. theme_enable(array('bartik')); diff --git a/core/modules/system/config/system.theme.yml b/core/modules/system/config/system.theme.yml index e88d701..a1cbfa5 100644 --- a/core/modules/system/config/system.theme.yml +++ b/core/modules/system/config/system.theme.yml @@ -1,4 +1,3 @@ admin: '' -enabled: - stark: 0 -default: stark +enabled: {} +default: '' diff --git a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php index 7f1ad47..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,11 +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)) { - if (drupal_get_path('theme', $theme)) { - theme_enable(array($theme)); - $themes = list_themes(); + 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/Extension/ThemeInstallTest.php b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeInstallTest.php new file mode 100644 index 0000000..01d2af1 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeInstallTest.php @@ -0,0 +1,74 @@ + 'Theme installation', + 'description' => 'Tests installing, enabling, disabling, and uninstalling of themes.', + 'group' => 'Extension', + ); + } + + function setUp() { + parent::setUp(); + $this->installConfig(array('system')); + } + + /** + * Tests enabling and disabling of themes. + */ + function testEnableDisable() { + // Verify that no theme is installed/enabled/disabled by default. + $this->assertFalse($this->themeConfig()->get('enabled')); + $this->assertFalse($this->themeDisabledConfig()->get()); + $this->assertFalse($this->themeHandler()->listInfo()); + } + + /** + * 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 themeConfig() { + return $this->container->get('config.factory')->get('system.theme'); + } + + /** + * Returns the system.theme.disabled config object. + * + * @return \Drupal\Core\Config\Config + */ + protected function themeDisabledConfig() { + return $this->container->get('config.factory')->get('system.theme.disabled'); + } + +} 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 0f17091..9d34007 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php @@ -223,7 +223,7 @@ function testListThemes() { * Test the theme_get_setting() function. */ function testThemeGetSetting() { - theme_enable(array('test_subtheme')); + \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.'); diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 2cb14f5..81726b2 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -18,15 +18,14 @@ function system_theme_default() { $request = \Drupal::request(); $theme = $request->get('theme'); if (!empty($theme)) { + // Get current list of themes. + $themes = list_themes(); // Check if the specified theme is one recognized by the system. - if (drupal_get_path('theme', $theme)) { + // Or try to enable the theme. + if (isset($themes[$theme]) || \Drupal::service('theme_handler')->enable(array($theme))) { $themes = list_themes(); - // Enable the theme if it is currently disabled. - 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.module b/core/modules/system/system.module index fce3816..a53d196 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1364,7 +1364,6 @@ function system_get_info($type, $name = NULL) { } } else { - // @todo Migrate theme info to ModuleInfo implementation. $list = system_list($type); foreach ($list as $shortname => $item) { if (!empty($item->status)) { @@ -1509,48 +1508,30 @@ 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. */ function system_rebuild_theme_data() { - $themes = _system_rebuild_theme_data(); + $themes = \Drupal::service('theme_handler')->rebuildThemeData(); 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 + // Add human-readable name and status. $enabled_themes = (array) \Drupal::config('system.theme')->get('enabled'); $files = array(); foreach ($themes as $name => $theme) { - $theme->status = (int) isset($enabled_themes[$name]); $files[$name] = $theme->getPathname(); + if (!isset($enabled_themes[$name])) { + unset($themes[$name]); + continue; + } + $theme->status = 1; } - // Set up dependencies. + // Build dependencies. $themes = \Drupal::moduleHandler()->buildModuleDependencies($themes); // 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 @@ -1597,9 +1578,10 @@ function system_region_list($theme, $show = REGIONS_ALL) { $theme = $themes[$theme]; } $list = array(); + $info = $theme->info; // If requested, suppress hidden regions. See block_admin_display_form(). - foreach ($theme->info['regions'] as $name => $label) { - if ($show == REGIONS_ALL || !isset($theme->info['regions_hidden']) || !in_array($name, $theme->info['regions_hidden'])) { + foreach ($info['regions'] as $name => $label) { + if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) { $list[$name] = t($label); } }