diff --git a/core/includes/module.inc b/core/includes/module.inc index 429f5db..b45f447 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -11,7 +11,7 @@ * * @param $type * The type of list to return: - * - module_enabled: All enabled modules. + * - module: All enabled modules. * - bootstrap: All enabled modules required for bootstrap. * - theme: All themes. * @@ -40,30 +40,26 @@ function system_list($type) { ); // Build a list of themes. $enabled_themes = (array) \Drupal::config('system.theme')->get('enabled'); - // @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 + 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. - // @see http://drupal.org/node/1067408 require_once DRUPAL_ROOT . '/core/modules/system/system.module'; $theme_data = system_rebuild_theme_data(); } 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->filename, - ); - } + $lists['filepaths'][] = array( + 'type' => 'theme', + 'name' => $name, + 'filepath' => $theme->filename, + ); } // @todo Move into list_themes(). Read info for a particular requested // theme from state instead. @@ -112,14 +108,12 @@ function system_list_reset() { drupal_static_reset('list_themes'); cache('bootstrap')->delete('system_list'); cache()->delete('system_info'); - // 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'); + // 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.maintenance.inc b/core/includes/theme.maintenance.inc index 96dcd2f..4e8a1e6 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -19,11 +19,6 @@ function _drupal_maintenance_theme() { global $theme, $theme_key, $conf; - // 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'; @@ -74,6 +69,9 @@ function _drupal_maintenance_theme() { } $themes = list_themes(); + if (empty($themes) || !isset($themes[$custom_theme])) { + $themes = _system_rebuild_theme_data(); + } // list_themes() triggers a drupal_alter() in maintenance mode, but we can't // let themes alter the .info.yml data until we know a theme's base themes. So diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index 40e59cb..ce69ab6 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -112,11 +112,56 @@ public function __construct(ConfigFactory $config_factory, ModuleHandlerInterfac /** * {@inheritdoc} */ - public function enable(array $theme_list) { + public function enable(array $theme_list, $enable_dependencies = TRUE) { + 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)); + + 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; + + // Add dependencies to the list, with a placeholder weight. + // 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 (!$theme_list) { + // Nothing to do. All themes already enabled. + return TRUE; + } + } + + // Sort the theme list by pre-calculated weights. + 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; + 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( @@ -126,20 +171,65 @@ public function enable(array $theme_list) { } // The value is not used; the weight is ignored for themes currently. - $theme_config->set("enabled.$key", 0)->save(); - $disabled_themes->clear($key)->save(); + $theme_config + ->set("enabled.$key", 0) + ->save(); + $disabled_themes + ->clear($key) + ->save(); // Refresh the theme list as config_install_default_config() needs an // updated list to work. $this->reset(); // Install default configuration of the theme. $this->configInstallDefaultConfig($key); + + // Allow modules to react prior to the installation of a theme. + $this->moduleHandler->invokeAll('themes_preinstall', array($key)); + // Now install the theme if necessary. + if (drupal_get_installed_schema_version($key, TRUE) == SCHEMA_UNINSTALLED) { + $version = SCHEMA_INSTALLED; + + // Install default configuration of the theme. + config_install_default_config('theme', $key); + + drupal_set_installed_schema_version($key, $version); + // Allow the theme to perform install tasks. + $function = $key . '_install'; + if (function_exists($function)) { + $function(); + } + // Record the fact that it was installed. + $themes_installed[] = $key; + watchdog('system', '%theme theme installed.', array('%theme' => $key), WATCHDOG_INFO); + } + + // Allow modules to react prior to the enabling of a theme. + $this->moduleHandler->invokeAll('themes_preenable', array($key)); + + // Enable the theme. + $function = $key . '_enable'; + if (function_exists($function)) { + $function(); + } + + // Record the fact that it was enabled. + $themes_enabled[] = $key; + watchdog('system', '%theme theme enabled.', array('%theme' => $key), WATCHDOG_INFO); } $this->resetSystem(); - // Invoke hook_themes_enabled() after the themes have been enabled. - $this->moduleHandler->invokeAll('themes_enabled', array($theme_list)); + // If any themes were newly installed, invoke hook_themes_installed(). + if (!empty($themes_installed)) { + $this->moduleHandler->invokeAll('themes_installed', $themes_installed); + } + + // If any themes were newly enabled, invoke hook_themes_enabled(). + if (!empty($themes_enabled)) { + $this->moduleHandler->invokeAll('themes_enabled', array($themes_enabled)); + } + return TRUE; } /** @@ -293,6 +383,9 @@ public function rebuildThemeData() { if (!empty($themes[$key]->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(). + $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme']; } $engine = $themes[$key]->info['engine']; diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php index eef3381..98b4139 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php @@ -17,11 +17,22 @@ * * @param array $theme_list * An array of theme names. + * @param bool $enable_dependencies + * If TRUE, dependencies will automatically be added and enabled 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); + public function enable(array $theme_list, $enable_dependencies = TRUE); /** * Disables a given list of themes. diff --git a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php index 75afddc..06376fd 100644 --- a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php +++ b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php @@ -35,7 +35,7 @@ public function access(Route $route, Request $request, AccountInterface $account */ public function checkAccess($theme) { $themes = list_themes(); - return !empty($themes[$theme]->status); + return isset($themes[$theme]) && !empty($themes[$theme]->status); } } diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php index d553440..181f5b9 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 that access to block admin page is denied when theme is disabled. + // Ensure the block admin page cannot be accessed for a disabled theme. $this->drupalGet('admin/structure/block/list/bartik'); - $this->assertResponse(403); + $this->assertResponse(404); // Enable admin theme and confirm that tab is accessible. theme_enable(array('bartik')); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 265012e..2ee1d39 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -830,6 +830,11 @@ protected function setUp() { $this->rebuildContainer(); } + // Re-initialize the theme to ensure that tests do not see an inconsistent + // behavior when calling functions that would initialize the theme if it has + // not been initialized yet. + drupal_theme_initialize(); + // Reset/rebuild all data structures after enabling the modules. $this->resetAll(); diff --git a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php index 852d2b6..14e7e34 100644 --- a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php +++ b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php @@ -103,12 +103,9 @@ public function enable(Request $request) { $theme = $request->get('theme'); 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])) { + if (drupal_get_path('theme', $theme)) { theme_enable(array($theme)); + $themes = list_themes(); 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..c0824d6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php @@ -35,10 +35,10 @@ 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_hander')->enable(array('seven', 'bartik')); \Drupal::config('system.theme') ->set('default', 'bartik') ->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')); 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 554450a..801bc0c 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 492dfd6..616f928 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() { + theme_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 3af30c8..c59ad87 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -21,8 +21,9 @@ function system_themes_page() { uasort($themes, 'system_sort_modules_by_info_name'); $theme_default = \Drupal::config('system.theme')->get('default'); - $theme_groups = array(); + $theme_groups = array('enabled' => array(), 'disabled' => array()); $admin_theme = \Drupal::config('system.theme')->get('admin'); + $admin_theme_options = array(); foreach ($themes as &$theme) { if (!empty($theme->info['hidden'])) { @@ -191,14 +192,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 (!empty($themes[$theme])) { + if (drupal_get_path('theme', $theme)) { + $themes = list_themes(); // Enable the theme if it is currently disabled. - if (empty($themes[$theme]->status)) { - theme_enable(array($theme)); + if (!isset($themes[$theme])) { + \Drupal::service('theme_handler')->enable(array($theme)); + $themes = list_themes(); } // Set the default theme. \Drupal::config('system.theme') diff --git a/core/modules/system/system.module b/core/modules/system/system.module index fac4c21..426b4f3 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2308,6 +2308,7 @@ 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)) { @@ -2333,6 +2334,8 @@ function system_get_info($type, $name = NULL) { * returned. * * @see \Drupal\Core\Utility\ModuleInfo + * + * @todo Merge this helper entirely into system_get_info()? */ function system_get_module_info($property) { static $info; @@ -2472,29 +2475,29 @@ function _system_rebuild_theme_data() { * Array of all available themes and their data. */ function system_rebuild_theme_data() { - $themes = _system_rebuild_theme_data(); + $files = array(); + $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 name, status, and schema version. $enabled_themes = (array) \Drupal::config('system.theme')->get('enabled'); - $files = array(); + $disabled_themes = (array) \Drupal::config('system.theme.disabled')->get(); + $all_themes = $enabled_themes + $disabled_themes; foreach ($themes as $name => $theme) { + $theme->name = $name; + $theme->weight = isset($all_themes[$name]) ? $all_themes[$name] : 0; $theme->status = (int) isset($enabled_themes[$name]); + $theme->schema_version = SCHEMA_UNINSTALLED; $files[$name] = $theme->filename; + + // Replace last known theme data state. + \Drupal::state()->set('system.theme.data.' . $name, $theme); } - // Replace last known theme data state. - // @todo Obsolete with proper installation status for themes. - \Drupal::state()->set('system.theme.data', $themes); + $themes = \Drupal::moduleHandler()->buildModuleDependencies($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; } @@ -2518,25 +2521,26 @@ 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; // 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'])) { + foreach ($theme->info['regions'] as $name => $label) { + if ($show == REGIONS_ALL || !isset($theme->info['regions_hidden']) || !in_array($name, $theme->info['regions_hidden'])) { $list[$name] = t($label); } }