diff --git a/core/config/install/core.extension.yml b/core/config/install/core.extension.yml index 1514a9e..eae39ef 100644 --- a/core/config/install/core.extension.yml +++ b/core/config/install/core.extension.yml @@ -1,4 +1,5 @@ module: {} -theme: {} +theme: + stark: 0 disabled: theme: {} diff --git a/core/core.services.yml b/core/core.services.yml index a58f779..14beb67 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -145,7 +145,7 @@ services: arguments: [default] form_builder: class: Drupal\Core\Form\FormBuilder - arguments: ['@form_validator', '@form_submitter', '@module_handler', '@keyvalue.expirable', '@event_dispatcher', '@request_stack', '@class_resolver', '@?csrf_token', '@?http_kernel'] + arguments: ['@form_validator', '@form_submitter', '@module_handler', '@keyvalue.expirable', '@event_dispatcher', '@request_stack', '@class_resolver', '@theme', '@?csrf_token', '@?http_kernel'] form_validator: class: Drupal\Core\Form\FormValidator arguments: ['@request_stack', '@string_translation', '@csrf_token'] @@ -223,10 +223,6 @@ services: arguments: ['@access_check.theme'] tags: - { name: service_collector, tag: theme_negotiator, call: addNegotiator } - theme.negotiator.request_subscriber: - class: Drupal\Core\EventSubscriber\ThemeNegotiatorRequestSubscriber - tags: - - { name: event_subscriber } theme.negotiator.default: class: Drupal\Core\Theme\DefaultNegotiator arguments: ['@config.factory'] @@ -829,6 +825,12 @@ services: class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry feed.writer.wellformedwebrendererentry: class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry + theme: + class: Drupal\Core\Theme\ThemeManager + arguments: ['@theme_handler', '@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack'] + theme.initialization: + class: Drupal\Core\Theme\Initialization + arguments: ['@theme_handler', '@state'] theme.registry: class: Drupal\Core\Theme\Registry arguments: ['@cache.default', '@lock', '@module_handler'] diff --git a/core/includes/common.inc b/core/includes/common.inc index df5677f..73e2c4c 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1187,8 +1187,21 @@ function _drupal_add_css($data = NULL, $options = NULL) { * * @see _drupal_add_css() */ -function drupal_get_css($css = NULL, $skip_alter = FALSE) { - global $theme_info; +function drupal_get_css($css = NULL, $skip_alter = FALSE, $theme_add_css = TRUE) { + $theme_info = \Drupal::theme()->getActiveTheme(); + + // @todo There are probably better place to add the CSS from themes. + if ($theme_add_css) { + foreach ($theme_info->getStyleSheets() as $media => $stylesheets) { + foreach ($stylesheets as $stylesheet) { + _drupal_add_css($stylesheet, array( + 'group' => CSS_AGGREGATE_THEME, + 'every_page' => TRUE, + 'media' => $media + )); + } + } + } if (!isset($css)) { $css = _drupal_add_css(); @@ -1197,24 +1210,25 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { // Allow modules and themes to alter the CSS items. if (!$skip_alter) { \Drupal::moduleHandler()->alter('css', $css); + \Drupal::theme()->alter('css', $css); } // Sort CSS items, so that they appear in the correct order. uasort($css, 'drupal_sort_css_js'); // Allow themes to remove CSS files by basename. - if (!empty($theme_info->stylesheets_remove)) { + if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) { foreach ($css as $key => $options) { - if (isset($options['basename']) && isset($theme_info->stylesheets_remove[$options['basename']])) { + if (isset($options['basename']) && isset($stylesheet_remove[$options['basename']])) { unset($css[$key]); } } } // Allow themes to conditionally override CSS files by basename. - if (!empty($theme_info->stylesheets_override)) { + if ($stylesheet_override = $theme_info->getStyleSheetsOverride()) { foreach ($css as $key => $options) { - if (isset($options['basename']) && isset($theme_info->stylesheets_override[$options['basename']])) { - $css[$key]['data'] = $theme_info->stylesheets_override[$options['basename']]; + if (isset($options['basename']) && isset($stylesheet_override[$options['basename']])) { + $css[$key]['data'] = $stylesheet_override[$options['basename']]; } } } @@ -1847,7 +1861,15 @@ function drupal_js_defaults($data = NULL) { * @see locale_js_alter() * @see drupal_js_defaults() */ -function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE, $is_ajax = FALSE) { +function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE, $is_ajax = FALSE, $theme_add_js = TRUE) { + $active_theme = \Drupal::theme()->getActiveTheme(); + if ($theme_add_js) { + // Add libraries used by this theme. + foreach ($active_theme->getLibraries() as $library) { + _drupal_add_library($library); + } + } + if (!isset($javascript)) { $javascript = _drupal_add_js(); } @@ -1858,6 +1880,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS // Allow modules to alter the JavaScript. if (!$skip_alter) { \Drupal::moduleHandler()->alter('js', $javascript); + \Drupal::theme()->alter('js', $javascript); } // Filter out elements of the given scope. @@ -1874,7 +1897,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS // Don't add settings if there is no other JavaScript on the page, unless // this is an AJAX request. if (!empty($items['settings']) || $is_ajax) { - global $theme_key; + $theme_key = $active_theme->getName(); // Provide the page with information about the theme that's used, so that // a later AJAX request can be rendered using the same theme. // @see \Drupal\Core\Theme\AjaxBasePageNegotiator @@ -2921,6 +2944,7 @@ function drupal_prepare_page($page) { // Modules alter the $page as needed. Blocks are populated into regions like // 'sidebar_first', 'footer', etc. \Drupal::moduleHandler()->alter('page', $page); + \Drupal::theme()->alter('page', $page); // The "main" and "secondary" menus are never part of the page-level render // array and therefore their cache tags will never bubble up into the page @@ -4034,7 +4058,7 @@ function drupal_flush_all_caches() { // use it. Unlike regular usages of this function, the installer and update // scripts need to flush all caches during GET requests/page building. if (function_exists('_drupal_maintenance_theme')) { - unset($GLOBALS['theme']); + \Drupal::theme()->resetActiveTheme(); drupal_maintenance_theme(); } } diff --git a/core/includes/errors.inc b/core/includes/errors.inc index 43fe7e7..51add03 100644 --- a/core/includes/errors.inc +++ b/core/includes/errors.inc @@ -119,17 +119,18 @@ function error_displayable($error = NULL) { function _drupal_log_error($error, $fatal = FALSE) { $is_installer = drupal_installation_attempted(); // Initialize a maintenance theme if the bootstrap was not complete. - // Do it early because drupal_set_message() triggers a drupal_theme_initialize(). + // Do it early because drupal_set_message() triggers a + // \Drupal\Core\Theme\ThemeManager::initTheme(). if ($fatal && drupal_get_bootstrap_phase() < DRUPAL_BOOTSTRAP_CODE) { // The installer initializes a maintenance theme at the earliest possible // point in time already. Do not unset that. if (!$is_installer) { - unset($GLOBALS['theme']); + \Drupal::theme()->setActiveTheme(NULL); } if (!defined('MAINTENANCE_MODE')) { define('MAINTENANCE_MODE', 'error'); } - // No-op if $GLOBALS['theme'] is set already. + // No-op if the active theme is set already. drupal_maintenance_theme(); } diff --git a/core/includes/form.inc b/core/includes/form.inc index 7131a7b..dfe945b 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -3056,9 +3056,8 @@ function batch_set($batch_definition) { function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) { $batch =& batch_get(); - drupal_theme_initialize(); - if (isset($batch)) { + $a = 123; // Add process information $process_info = array( 'current_set' => 0, @@ -3067,7 +3066,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NU 'url_options' => array(), 'source_url' => current_path(), 'redirect' => $redirect, - 'theme' => $GLOBALS['theme_key'], + 'theme' => drupal_installation_attempted() ? 'seven' : \Drupal::theme()->getActiveTheme()->getName(), 'redirect_callback' => $redirect_callback, ); $batch += $process_info; diff --git a/core/includes/theme.inc b/core/includes/theme.inc index f23a864..6dc2e8b 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -91,210 +91,6 @@ function drupal_theme_access($theme) { } /** - * Initializes the theme system by loading the theme. - * - * @param RouteMatch $route_match - * The route match to use for theme initialization. -// @todo Force calling methods to provide as RouteMatch. - */ -function drupal_theme_initialize(RouteMatch $route_match = NULL) { - global $theme, $theme_key; - - // If $theme is already set, assume the others are set, too, and do nothing - if (isset($theme)) { - return; - } - - $themes = list_themes(); - - // 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. - if (!$route_match) { - $route_match = \Drupal::routeMatch(); - } - $theme = \Drupal::service('theme.negotiator')->determineActiveTheme($route_match); - - // If no theme could be negotiated, or if the negotiated theme is not within - // the list of enabled themes, fall back to the default theme output of core - // and modules (similar to Stark, but without a theme extension at all). This - // is possible, because _drupal_theme_initialize() always loads the Twig theme - // engine. - if (!$theme || !isset($themes[$theme])) { - $theme = 'core'; - $theme_key = $theme; - // /core/core.info.yml does not actually exist, but is required because - // Extension expects a pathname. - _drupal_theme_initialize(new Extension('theme', 'core/core.info.yml')); - return; - } - - // Store the identifier for retrieving theme settings with. - $theme_key = $theme; - - // Find all our ancestor themes and put them in an array. - $base_theme = array(); - $ancestor = $theme; - while ($ancestor && isset($themes[$ancestor]->base_theme)) { - $ancestor = $themes[$ancestor]->base_theme; - $base_theme[] = $themes[$ancestor]; - } - _drupal_theme_initialize($themes[$theme], array_reverse($base_theme)); -} - -/** - * Initializes the theme system given already loaded information. - * - * This function is useful to initialize a theme when no database is present. - * - * @param \Drupal\Core\Extension\Extension $theme - * The theme extension object. - * @param \Drupal\Core\Extension\Extension[] $base_theme - * An optional array of objects that represent the 'base theme' if the - * theme is meant to be derivative of another theme. It requires - * the same information as the $theme object. It should be in - * 'oldest first' order, meaning the top level of the chain will - * be first. - */ -function _drupal_theme_initialize($theme, $base_theme = array()) { - global $theme_info, $base_theme_info, $theme_engine, $theme_path; - $theme_info = $theme; - $base_theme_info = $base_theme; - - $theme_path = $theme->getPath(); - - // Prepare stylesheets from this theme as well as all ancestor themes. - // We work it this way so that we can have child themes override parent - // theme stylesheets easily. - $final_stylesheets = array(); - // CSS file basenames to override, pointing to the final, overridden filepath. - $theme->stylesheets_override = array(); - // CSS file basenames to remove. - $theme->stylesheets_remove = array(); - - // Grab stylesheets from base theme - foreach ($base_theme as $base) { - if (!empty($base->stylesheets)) { - foreach ($base->stylesheets as $media => $stylesheets) { - foreach ($stylesheets as $name => $stylesheet) { - $final_stylesheets[$media][$name] = $stylesheet; - } - } - } - $base_theme_path = $base->getPath(); - if (!empty($base->info['stylesheets-remove'])) { - foreach ($base->info['stylesheets-remove'] as $basename) { - $theme->stylesheets_remove[$basename] = $base_theme_path . '/' . $basename; - } - } - if (!empty($base->info['stylesheets-override'])) { - foreach ($base->info['stylesheets-override'] as $name) { - $basename = drupal_basename($name); - $theme->stylesheets_override[$basename] = $base_theme_path . '/' . $name; - } - } - } - - // Add stylesheets used by this theme. - if (!empty($theme->stylesheets)) { - foreach ($theme->stylesheets as $media => $stylesheets) { - foreach ($stylesheets as $name => $stylesheet) { - $final_stylesheets[$media][$name] = $stylesheet; - } - } - } - if (!empty($theme->info['stylesheets-remove'])) { - foreach ($theme->info['stylesheets-remove'] as $basename) { - $theme->stylesheets_remove[$basename] = $theme_path . '/' . $basename; - - if (isset($theme->stylesheets_override[$basename])) { - unset($theme->stylesheets_override[$basename]); - } - } - } - if (!empty($theme->info['stylesheets-override'])) { - foreach ($theme->info['stylesheets-override'] as $name) { - $basename = drupal_basename($name); - $theme->stylesheets_override[$basename] = $theme_path . '/' . $name; - - if (isset($theme->stylesheets_remove[$basename])) { - unset($theme->stylesheets_remove[$basename]); - } - } - } - - // And now add the stylesheets properly. - $css = array(); - foreach ($final_stylesheets as $media => $stylesheets) { - foreach ($stylesheets as $stylesheet) { - $css['#attached']['css'][$stylesheet] = array( - 'group' => CSS_AGGREGATE_THEME, - 'every_page' => TRUE, - 'media' => $media - ); - } - } - drupal_render($css); - - // Do basically the same as the above for libraries - $final_libraries = array(); - - // Grab libraries from base theme - foreach ($base_theme as $base) { - if (!empty($base->libraries)) { - foreach ($base->libraries as $library) { - $final_libraries[] = $library; - } - } - } - - // Add libraries used by this theme. - if (!empty($theme->libraries)) { - foreach ($theme->libraries as $library) { - $final_libraries[] = $library; - } - } - - // Add libraries used by this theme. - $libraries = array(); - foreach ($final_libraries as $library) { - $libraries['#attached']['library'][] = $library; - } - drupal_render($libraries); - - $theme_engine = NULL; - - // Initialize the theme. - if (isset($theme->engine)) { - // Include the engine. - include_once DRUPAL_ROOT . '/' . $theme->owner; - - $theme_engine = $theme->engine; - if (function_exists($theme_engine . '_init')) { - foreach ($base_theme as $base) { - call_user_func($theme_engine . '_init', $base); - } - call_user_func($theme_engine . '_init', $theme); - } - } - else { - // include non-engine theme files - foreach ($base_theme as $base) { - // Include the theme file or the engine. - if (!empty($base->owner)) { - include_once DRUPAL_ROOT . '/' . $base->owner; - } - } - // and our theme gets one too. - if (!empty($theme->owner)) { - include_once DRUPAL_ROOT . '/' . $theme->owner; - } - } - - // Always include Twig as the default theme engine. - include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine'; -} - -/** * Gets the theme registry. * * @param bool $complete @@ -417,6 +213,7 @@ function _theme($hook, $variables = array()) { static $default_attributes; $module_handler = \Drupal::moduleHandler(); + $active_theme = \Drupal::theme()->getActiveTheme(); // If called before all modules are loaded, we do not necessarily have a full // theme registry to work with, and therefore cannot process the theme @@ -424,8 +221,6 @@ 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(); @@ -469,11 +264,6 @@ function _theme($hook, $variables = array()) { } $info = $theme_registry->get($hook); - global $theme_path; - $temp = $theme_path; - // point path_to_theme() to the currently used theme path: - $theme_path = $info['theme path']; - // If a renderable array is passed as $variables, then set $variables to // the arguments expected by the theme function. @@ -533,6 +323,7 @@ function _theme($hook, $variables = array()) { 'theme_suggestions_' . $base_theme_hook, ); $module_handler->alter($hooks, $suggestions, $variables, $base_theme_hook); + \Drupal::theme()->alter($hooks, $suggestions, $variables, $base_theme_hook); // Check if each suggestion exists in the theme registry, and if so, // use it instead of the hook that _theme() was called with. For example, a @@ -593,7 +384,7 @@ function _theme($hook, $variables = array()) { $extension = '.html.twig'; // The theme engine may use a different extension and a different renderer. - global $theme_engine; + $theme_engine = $active_theme->getEngine(); if (isset($theme_engine)) { if ($info['type'] != 'module') { if (function_exists($theme_engine . '_render_template')) { @@ -654,32 +445,10 @@ function _theme($hook, $variables = array()) { $output = $render_function($template_file, $variables); } - // restore path_to_theme() - $theme_path = $temp; return (string) $output; } /** - * Returns the path to the current themed element. - * - * It can point to the active theme or the module handling a themed - * implementation. For example, when invoked within the scope of a theming call - * it will depend on where the theming function is handled. If implemented from - * a module, it will point to the module. If implemented from the active theme, - * it will point to the active theme. When called outside the scope of a - * theming call, it will always point to the active theme. - */ -function path_to_theme() { - global $theme_path; - - if (!isset($theme_path)) { - drupal_theme_initialize(); - } - - return $theme_path; -} - -/** * Allows themes and/or theme engines to discover overridden theme functions. * * @param $cache @@ -765,7 +534,7 @@ function drupal_find_theme_templates($cache, $extension, $path) { } } } - global $theme; + $theme = \Drupal::theme()->getActiveTheme()->getName(); $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array(); // Escape the periods in the extension. @@ -867,7 +636,7 @@ function theme_get_setting($setting_name, $theme = NULL) { // If no key is given, use the current theme if we can determine it. if (!isset($theme)) { - $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : ''; + $theme = \Drupal::theme()->getActiveTheme()->getName(); } if (empty($cache[$theme])) { @@ -1881,7 +1650,7 @@ function template_preprocess_container(&$variables) { */ function template_preprocess(&$variables, $hook, $info) { // Tell all templates where they are located. - $variables['directory'] = path_to_theme(); + $variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath(); // Merge in variables that don't depend on hook and don't change during a // single page request. @@ -1971,8 +1740,7 @@ function drupal_pre_render_html(array $element) { * @see system_element_info() */ function drupal_pre_render_page(array $element) { - global $theme; - $element['#cache']['tags']['theme'] = $theme; + $element['#cache']['tags']['theme'] = \Drupal::theme()->getActiveTheme()->getName(); $element['#cache']['tags']['theme_global_settings'] = TRUE; return $element; } @@ -2093,7 +1861,7 @@ function template_preprocess_page(&$variables) { $variables['show_messages'] = $variables['page']['#show_messages']; $variables['title'] = $variables['page']['#title']; - foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) { + foreach (system_region_list(\Drupal::theme()->getActiveTheme()->getName()) as $region_key => $region_name) { if (!isset($variables['page'][$region_key])) { $variables['page'][$region_key] = array(); } diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index 8eb0775..2adb930 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -7,6 +7,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Site\Settings; +use Drupal\Core\Theme\ActiveTheme; /** * Sets up the theming system for maintenance page. @@ -18,10 +19,8 @@ * setting a "maintenance_theme" key in the $settings variable in settings.php. */ 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)) { + // If the theme is already set, assume the others are set too, and do nothing. + if (\Drupal::theme()->hasActiveTheme()) { return; } @@ -76,10 +75,13 @@ function _drupal_maintenance_theme() { // If no themes are installed yet, or if the requested custom theme is not // installed, retrieve all available themes. + /** @var \Drupal\Core\Theme\Initialization $theme_init */ + $theme_init = \Drupal::service('theme.initialization'); if (empty($themes) || !isset($themes[$custom_theme])) { $theme_handler = \Drupal::service('theme_handler'); $themes = $theme_handler->rebuildThemeData(); $theme_handler->addTheme($themes[$custom_theme]); + \Drupal::theme()->setActiveTheme($theme_init->getActiveTheme($themes[$custom_theme], array())); } // list_themes() triggers a \Drupal\Core\Extension\ModuleHandler::alter() in @@ -88,9 +90,6 @@ function _drupal_maintenance_theme() { // list_themes() builds its cache. $theme = $custom_theme; - // Store the identifier for retrieving theme settings with. - $theme_key = $theme; - // Find all our ancestor themes and put them in an array. $base_theme = array(); $ancestor = $theme; @@ -98,7 +97,9 @@ function _drupal_maintenance_theme() { $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme]; $ancestor = $themes[$ancestor]->base_theme; } - _drupal_theme_initialize($themes[$theme], array_reverse($base_theme)); + // @todo This is just a workaround. Find a better way how to handle themes + // on maintenance pages. + \Drupal::theme()->setActiveTheme($theme_init->getActiveTheme($themes[$custom_theme], array_reverse($base_theme))); // Prime the theme registry. // @todo Remove global theme variables. Drupal::service('theme.registry'); diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index b5e27de..c555d39 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -624,6 +624,15 @@ public static function formBuilder() { } /** + * Gets the theme service. + * + * @return \Drupal\Core\Theme\ThemeManagerInterface + */ + public static function theme() { + return static::$container->get('theme'); + } + + /** * Gets the syncing state. * * @return bool diff --git a/core/lib/Drupal/Core/EventSubscriber/ThemeNegotiatorRequestSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ThemeNegotiatorRequestSubscriber.php deleted file mode 100644 index 322981d..0000000 --- a/core/lib/Drupal/Core/EventSubscriber/ThemeNegotiatorRequestSubscriber.php +++ /dev/null @@ -1,48 +0,0 @@ -getRequestType() == HttpKernelInterface::MASTER_REQUEST) { - if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { - // @todo Refactor drupal_theme_initialize() into a request subscriber. - // @see https://drupal.org/node/2228093 - drupal_theme_initialize(RouteMatch::createFromRequest($event->getRequest())); - } - } - } - - /** - * Registers the methods in this class that should be listeners. - * - * @return array - * An array of event listener definitions. - */ - public static function getSubscribedEvents() { - $events[KernelEvents::REQUEST][] = array('onKernelRequestThemeNegotiator', 29); - return $events; - } - -} diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 6270c8e..12292e8 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -482,30 +482,6 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { } } } - // Allow the theme to alter variables after the theme system has been - // initialized. - global $theme, $base_theme_info; - if (isset($theme)) { - $theme_keys = array(); - foreach ($base_theme_info as $base) { - $theme_keys[] = $base->getName(); - } - $theme_keys[] = $theme; - foreach ($theme_keys as $theme_key) { - $function = $theme_key . '_' . $hook; - if (function_exists($function)) { - $this->alterFunctions[$cid][] = $function; - } - if (isset($extra_types)) { - foreach ($extra_types as $extra_type) { - $function = $theme_key . '_' . $extra_type . '_alter'; - if (function_exists($function)) { - $this->alterFunctions[$cid][] = $function; - } - } - } - } - } } foreach ($this->alterFunctions[$cid] as $function) { diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index dbe2c78..cdcc492 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -336,6 +336,12 @@ public function listInfo() { if (!isset($this->list)) { $this->list = array(); $themes = $this->systemThemeList(); + // @FIXME: this is just a workaround + if (empty($themes)) { + $this->refreshInfo(); + $this->list = $this->list ?: array(); + $themes = \Drupal::state()->get('system.theme.data', array()); + } foreach ($themes as $theme) { $this->addTheme($theme); } @@ -371,7 +377,7 @@ public function addTheme(Extension $theme) { public function refreshInfo() { $this->reset(); $extension_config = $this->configFactory->get('core.extension'); - $enabled = $extension_config->get('theme') ?: array(); + $enabled = $extension_config->get('theme'); // @todo Avoid re-scanning all themes by retaining the original (unaltered) // theme info somewhere. diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index c92f2d6..649e234 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -18,6 +18,7 @@ use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; use Drupal\Core\Render\Element; use Drupal\Core\Site\Settings; +use Drupal\Core\Theme\ThemeManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -90,6 +91,13 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS protected $currentUser; /** + * The theme manager. + * + * @var \Drupal\Core\Theme\ThemeManagerInterface + */ + protected $themeManager; + + /** * @var \Drupal\Core\Form\FormValidatorInterface */ protected $formValidator; @@ -121,7 +129,7 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS * @param \Drupal\Core\HttpKernel $http_kernel * The HTTP kernel. */ - public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) { + public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) { $this->formValidator = $form_validator; $this->formSubmitter = $form_submitter; $this->moduleHandler = $module_handler; @@ -131,6 +139,7 @@ public function __construct(FormValidatorInterface $form_validator, FormSubmitte $this->classResolver = $class_resolver; $this->csrfToken = $csrf_token; $this->httpKernel = $http_kernel; + $this->themeManager = $theme_manager; } /** @@ -648,6 +657,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { } $hooks[] = 'form_' . $form_id; $this->moduleHandler->alter($hooks, $form, $form_state, $form_id); + $this->themeManager->alter($hooks, $form, $form_state, $form_id); } /** diff --git a/core/lib/Drupal/Core/Theme/ActiveTheme.php b/core/lib/Drupal/Core/Theme/ActiveTheme.php new file mode 100644 index 0000000..5958c7f --- /dev/null +++ b/core/lib/Drupal/Core/Theme/ActiveTheme.php @@ -0,0 +1,180 @@ +name = $values['name']; + $this->path = $values['path']; + if (isset($values['parent'])) { + $this->parent = $values['parent']; + } + $this->engine = $values['engine']; + $this->owner = $values['owner']; + $this->styleSheets = $values['stylesheets']; + $this->styleSheetsRemove = $values['stylesheets_remove']; + $this->styleSheetsOverride = $values['stylesheets_override']; + $this->libraries = $values['libraries']; + $this->extension = $values['extension']; + $this->baseThemes = $values['base_themes']; + } + + /** + * Returns the machine name of the theme. + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Returns the path to the theme directory. + * + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * Returns the parent active theme object or NULL if this is the root element. + * + * @return static|NULL + */ + public function getParent() { + return $this->parent; + } + + /** + * Returns the theme engine. + * + * @return string + */ + public function getEngine() { + return $this->engine; + } + + /** + * Returns the path theme engine file. + * + * @return mixed + */ + public function getOwner() { + return $this->owner; + } + + /** + * Returns the extension object. + * + * @return \Drupal\Core\Extension\Extension + */ + public function getExtension() { + return $this->extension; + } + + /** + * Returns the libraries provided by the theme. + * + * @return mixed + */ + public function getLibraries() { + return $this->libraries; + } + + /** + * Returns the stylesheets provided by the theme. + * + * @return mixed + */ + public function getStyleSheets() { + return $this->styleSheets; + } + + /** + * Returns the overridden stylesheets by the theme. + * + * @return mixed + */ + public function getStyleSheetsOverride() { + return $this->styleSheetsOverride; + } + + /** + * Returns the removed stylesheets by the theme. + * + * @return mixed + */ + public function getStyleSheetsRemove() { + return $this->styleSheetsRemove; + } + + /** + * Returns an array of base theme active theme objects keyed by name. + * + * @return static[] + */ + public function getBaseThemes() { + return $this->baseThemes; + } + +} diff --git a/core/lib/Drupal/Core/Theme/DefaultNegotiator.php b/core/lib/Drupal/Core/Theme/DefaultNegotiator.php index 2f0e6f7..6000315 100644 --- a/core/lib/Drupal/Core/Theme/DefaultNegotiator.php +++ b/core/lib/Drupal/Core/Theme/DefaultNegotiator.php @@ -43,7 +43,7 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function determineActiveTheme(RouteMatchInterface $route_match) { - return $this->config->get('default'); + return $this->config->get('default') ?: 'stark'; } } diff --git a/core/lib/Drupal/Core/Theme/Initialization.php b/core/lib/Drupal/Core/Theme/Initialization.php new file mode 100644 index 0000000..1efa00e --- /dev/null +++ b/core/lib/Drupal/Core/Theme/Initialization.php @@ -0,0 +1,256 @@ +themeHandler = $theme_handler; + $this->state = $state; + } + + /** + * Initializes a given theme. + * + * This loads the active theme, for example include its engine file. + * + * @param string $theme_name + * The machine name of the theme. + * + * @return \Drupal\Core\Theme\ActiveTheme + * An active theme object instance for the given theme. + */ + public function initTheme($theme_name) { + $active_theme = $this->getActiveThemeByName($theme_name); + $this->loadActiveTheme($active_theme); + + return $active_theme; + } + + /** + * Builds an active theme object. + * + * @param string $theme_name + * The machine name of the theme. + * + * @return \Drupal\Core\Theme\ActiveTheme + * An active theme object instance for the given theme. + */ + public function getActiveThemeByName($theme_name) { + if ($active_theme = $this->state->get('theme.active_theme.' . $theme_name)) { + return $active_theme; + } + else { + $themes = $this->themeHandler->listInfo(); + + if (empty($themes)) { + throw new \RuntimeException('No theme is enabled.'); + } + if (!isset($themes[$theme_name])) { + throw new \InvalidArgumentException(String::format('Theme %theme is not enabled/does not exist.', array('theme' => $theme_name))); + } + + // Find all our ancestor themes and put them in an array. + $base_themes = array(); + $ancestor = $theme_name; + while ($ancestor && isset($themes[$ancestor]->base_theme)) { + $ancestor = $themes[$ancestor]->base_theme; + $base_themes[] = $themes[$ancestor]; + } + + $active_theme = $this->getActiveTheme($themes[$theme_name], $base_themes); + + $this->state->set('theme.active_theme.' . $theme_name, $active_theme); + return $active_theme; + } + } + + /** + * Build active theme objects for all themes. + */ + protected function buildActiveThemes() { + } + + /** + * Loads a theme, so it is ready to be used. + * + * Loading a theme includes loading and initializing the engine, + * each base theme and its engines. + * + * @param ActiveTheme $active_theme + */ + public function loadActiveTheme(ActiveTheme $active_theme) { + // Initialize the theme. + if ($theme_engine = $active_theme->getEngine()) { + // Include the engine. + include_once DRUPAL_ROOT . '/' . $active_theme->getOwner(); + + if (function_exists($theme_engine . '_init')) { + foreach ($active_theme->getBaseThemes() as $base) { + call_user_func($theme_engine . '_init', $base->getExtension()); + } + call_user_func($theme_engine . '_init', $active_theme->getExtension()); + } + } + else { + // include non-engine theme files + foreach ($active_theme->getBaseThemes() as $base) { + // Include the theme file or the engine. + if ($base->getOwner()) { + include_once DRUPAL_ROOT . '/' . $base->getOwner(); + } + } + // and our theme gets one too. + if ($active_theme->getOwner()) { + include_once DRUPAL_ROOT . '/' . $active_theme->getOwner(); + } + } + + // Always include Twig as the default theme engine. + include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine'; + } + + /** + * Builds up the active theme object. + * + * @param \Drupal\Core\Extension\Extension $theme + * The theme extension object. + * @param \Drupal\Core\Extension\Extension[] $base_themes + * An array of extension objects of base theme and its bases. It is ordered + * by 'oldest first', meaning the top level of the chain will be first. + * + * @throws \Exception + * todo + * @return \Drupal\Core\Theme\ActiveTheme + * The active theme instance for the passed in $theme. + */ + public function getActiveTheme(Extension $theme, array $base_themes) { + $theme_path = $theme->getPath(); + + $values['path'] = $theme_path; + $values['name'] = $theme->getName(); + + // Prepare stylesheets from this theme as well as all ancestor themes. + // We work it this way so that we can have child themes override parent + // theme stylesheets easily. + $values['stylesheets'] = array(); + // CSS file basenames to override, pointing to the final, overridden filepath. + $values['stylesheets_override'] = array(); + // CSS file basenames to remove. + $values['stylesheets_remove'] = array(); + + // Grab stylesheets from base theme. + $final_stylesheets = array(); + foreach ($base_themes as $base) { + if (!empty($base->stylesheets)) { + foreach ($base->stylesheets as $media => $stylesheets) { + foreach ($stylesheets as $name => $stylesheet) { + $final_stylesheets[$media][$name] = $stylesheet; + } + } + } + $base_theme_path = $base->getPath(); + if (!empty($base->info['stylesheets-remove'])) { + foreach ($base->info['stylesheets-remove'] as $basename) { + $values['stylesheets_remove'][$basename] = $base_theme_path . '/' . $basename; + } + } + if (!empty($base->info['stylesheets-override'])) { + foreach ($base->info['stylesheets-override'] as $name) { + $basename = drupal_basename($name); + $values['stylesheets_override'][$basename] = $base_theme_path . '/' . $name; + } + } + } + + // Add stylesheets used by this theme. + if (!empty($theme->stylesheets)) { + foreach ($theme->stylesheets as $media => $stylesheets) { + foreach ($stylesheets as $name => $stylesheet) { + $final_stylesheets[$media][$name] = $stylesheet; + } + } + } + if (!empty($theme->info['stylesheets-remove'])) { + foreach ($theme->info['stylesheets-remove'] as $basename) { + $values['stylesheets_remove'][$basename] = $theme_path . '/' . $basename; + + if (isset($values['stylesheets_override'][$basename])) { + unset($values['stylesheets_override'][$basename]); + } + } + } + if (!empty($theme->info['stylesheets-override'])) { + foreach ($theme->info['stylesheets-override'] as $name) { + $basename = drupal_basename($name); + $values['stylesheets_override'][$basename] = $theme_path . '/' . $name; + + if (isset($values['stylesheets_remove'][$basename])) { + unset($values['stylesheets_remove'][$basename]); + } + } + } + + // And now add the stylesheets properly. + $values['stylesheets'] = $final_stylesheets; + + // Do basically the same as the above for libraries + $values['libraries'] = array(); + + // Grab libraries from base theme + foreach ($base_themes as $base) { + if (!empty($base->libraries)) { + foreach ($base->libraries as $library) { + $values['libraries'][] = $library; + } + } + } + + // Add libraries used by this theme. + if (!empty($theme->libraries)) { + foreach ($theme->libraries as $library) { + $values['libraries'][] = $library; + } + } + + $values['engine'] = isset($theme->engine) ? $theme->engine : NULL; + $values['owner'] = isset($theme->owner) ? $theme->owner : NULL; + $values['extension'] = $theme; + + $base_active_themes = array(); + foreach ($base_themes as $base_theme) { + $base_active_themes[$base_theme->getName()] = $this->getActiveTheme($base_theme, array_slice($base_themes, 1)); + } + + $values['base_themes'] = $base_active_themes; + + return new ActiveTheme($values); + } + +} diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 402c9f7..a0be4c1 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -34,7 +34,7 @@ class Registry implements DestructableInterface { * * @var array */ - protected $baseThemes; + protected $baseThemes = array(); /** * The name of the theme engine of $theme. @@ -116,6 +116,20 @@ class Registry implements DestructableInterface { protected $runtimeRegistry; /** + * Stores whether the registry was already initialized. + * + * @var bool + */ + protected $initialized = FALSE; + + /** + * The name of the theme for which to construct the registry, if given. + * + * @var string|null + */ + protected $themeName; + + /** * Constructs a \Drupal\Core\\Theme\Registry object. * * @param \Drupal\Core\Cache\CacheBackendInterface $cache @@ -131,7 +145,7 @@ public function __construct(CacheBackendInterface $cache, LockBackendInterface $ $this->cache = $cache; $this->lock = $lock; $this->moduleHandler = $module_handler; - $this->init($theme_name); + $this->themeName = $theme_name; } /** @@ -144,19 +158,18 @@ public function __construct(CacheBackendInterface $cache, LockBackendInterface $ * (optional) The name of the theme for which to construct the registry.+ */ protected function init($theme_name = NULL) { + if ($this->initialized) { + return; + } // Unless instantiated for a specific theme, use globals. if (!isset($theme_name)) { - if (isset($GLOBALS['theme']) && isset($GLOBALS['theme_info'])) { - $this->theme = $GLOBALS['theme_info']; - $this->baseThemes = $GLOBALS['base_theme_info']; - $this->engine = $GLOBALS['theme_engine']; - } - else { - // @see drupal_theme_initialize() - $this->theme = new Extension('theme', 'core/core.info.yml'); - $this->baseThemes = array(); - $this->engine = 'twig'; + $active_theme = \Drupal::theme()->getActiveTheme(); + $this->theme = $active_theme; + // @todo use $active_theme->getBaseThemes(); + if ($parent = $active_theme->getParent()) { + $this->baseThemes = array($parent); } + $this->engine = $active_theme->getEngine(); } // Instead of the global theme, a specific theme was requested. else { @@ -176,7 +189,6 @@ protected function init($theme_name = NULL) { } $this->baseThemes = array_reverse($this->baseThemes); - // @see _drupal_theme_initialize() if (isset($this->theme->engine)) { $this->engine = $this->theme->engine; include_once DRUPAL_ROOT . '/' . $this->theme->owner; @@ -199,6 +211,7 @@ protected function init($theme_name = NULL) { * @see Registry::$registry */ public function get() { + $this->init($this->themeName); if (isset($this->registry)) { return $this->registry; } @@ -224,6 +237,7 @@ public function get() { * lightweight than the full registry. */ public function getRuntime() { + $this->init($this->themeName); if (!isset($this->runtimeRegistry)) { $this->runtimeRegistry = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->cache, $this->lock, array('theme_registry' => TRUE), $this->moduleHandler->isLoaded()); } @@ -247,6 +261,7 @@ protected function setCache() { * The name of the base hook or FALSE. */ public function getBaseHook($hook) { + $this->init($this->themeName); $base_hook = $hook; // Iteratively strip everything after the last '__' delimiter, until a // base hook definition is found. Recursive base hooks of base hooks are @@ -331,6 +346,7 @@ protected function build() { // Let modules alter the registry. $this->moduleHandler->alter('theme_registry', $cache); + // @todo Do we want to allow themes to take part? // @todo Implement more reduction of the theme registry entry. // Optimize the registry to not have empty arrays for functions. @@ -538,7 +554,6 @@ protected function processExtension(&$cache, $name, $type, $theme, $path) { * To be called when the list of enabled extensions is changed. */ public function reset() { - // Reset the runtime registry. if (isset($this->runtimeRegistry) && $this->runtimeRegistry instanceof ThemeRegistry) { $this->runtimeRegistry->clear(); @@ -580,11 +595,4 @@ protected function listThemes() { return list_themes(); } - /** - * Wraps drupal_theme_initialize(). - */ - protected function initializeTheme() { - drupal_theme_initialize(); - } - } diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php new file mode 100644 index 0000000..de41361 --- /dev/null +++ b/core/lib/Drupal/Core/Theme/ThemeManager.php @@ -0,0 +1,159 @@ +themeHandler = $theme_handler; + $this->themeNegotiator = $theme_negotiator; + $this->themeRegistry = $theme_registry; + $this->themeInitialization = $theme_initialization; + $this->requestStack = $request_stack; + } + + /** + * {@inheritdoc} + */ + public function render($hook, $variables) { + return _theme($hook, $variables); + } + + /** + * {@inheritdoc} + */ + public function getActiveTheme(RouteMatchInterface $route_match = NULL) { + if (!isset($this->activeTheme)) { + $this->initTheme($route_match); + } + return $this->activeTheme; + } + + /** + * {@inheritdoc} + */ + public function hasActiveTheme() { + return isset($this->activeTheme); + } + + /** + * @TODO + */ + public function resetActiveTheme() { + $this->activeTheme = NULL; + } + + /** + * {@inheritdoc} + */ + public function setActiveTheme(ActiveTheme $active_theme) { + $this->activeTheme = $active_theme; + if ($active_theme) { + $this->themeInitialization->loadActiveTheme($active_theme); + } + } + + protected function initTheme(RouteMatchInterface $route_match = NULL) { + // 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. + if (!$route_match) { + $route_match = \Drupal::routeMatch(); + } + $theme = $this->themeNegotiator->determineActiveTheme($route_match) ?: 'stark'; + $this->activeTheme = $this->themeInitialization->initTheme($theme); + } + + /** + * {@inheritdoc} + * + * @todo Should we cache some of these information? + */ + public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { + // Most of the time, $type is passed as a string, so for performance, + // normalize it to that. When passed as an array, usually the first item in + // the array is a generic type, and additional items in the array are more + // specific variants of it, as in the case of array('form', 'form_FORM_ID'). + if (is_array($type)) { + $extra_types = $type; + $type = array_shift($extra_types); + // Allow if statements in this function to use the faster isset() rather + // than !empty() both when $type is passed as a string, or as an array with + // one item. + if (empty($extra_types)) { + unset($extra_types); + } + } + + $theme_keys = array(); + $theme = $this->getActiveTheme(); + while ($base = $theme->getParent()) { + $theme_keys[] = $base->getName(); + } + + $theme_keys[] = $theme->getName(); + $functions = array(); + foreach ($theme_keys as $theme_key) { + $function = $theme_key . '_' . $type . '_alter'; + if (function_exists($function)) { + $functions[] = $function; + } + if (isset($extra_types)) { + foreach ($extra_types as $extra_type) { + $function = $theme_key . '_' . $extra_type . '_alter'; + if (function_exists($function)) { + $functions[] = $function; + } + } + } + } + + foreach ($functions as $function) { + $function($data, $context1, $context2); + } + } + +} diff --git a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php new file mode 100644 index 0000000..0d11a39 --- /dev/null +++ b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php @@ -0,0 +1,53 @@ +getActiveTheme()->getName(); // Fetch a list of regions for the current theme. $all_regions = system_region_list($theme); diff --git a/core/modules/block/src/BlockListBuilder.php b/core/modules/block/src/BlockListBuilder.php index 1b26b9f..dd171f9 100644 --- a/core/modules/block/src/BlockListBuilder.php +++ b/core/modules/block/src/BlockListBuilder.php @@ -87,7 +87,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI public function load() { // If no theme was specified, use the current theme. if (!$this->theme) { - $this->theme = $GLOBALS['theme']; + $this->theme = \Drupal::theme()->getActiveTheme()->getName(); } // Store the region list. @@ -117,7 +117,7 @@ public function load() { public function render($theme = NULL, Request $request = NULL) { $this->request = $request; // If no theme was specified, use the current theme. - $this->theme = $theme ?: $GLOBALS['theme_key']; + $this->theme = $theme ?: \Drupal::theme()->getActiveTheme()->getName(); return \Drupal::formBuilder()->getForm($this); } diff --git a/core/modules/color/color.module b/core/modules/color/color.module index 1f03e18..663a92c 100644 --- a/core/modules/color/color.module +++ b/core/modules/color/color.module @@ -67,7 +67,7 @@ function color_form_system_theme_settings_alter(&$form, FormStateInterface $form * Replaces style sheets with color-altered style sheets. */ function color_css_alter(&$css) { - global $theme_key; + $theme_key = \Drupal::theme()->getActiveTheme()->getName(); $themes = list_themes(); // Override stylesheets. @@ -97,7 +97,7 @@ function color_css_alter(&$css) { * Replace the logo with the colored version if available. */ function color_preprocess_page(&$variables) { - global $theme_key; + $theme_key = \Drupal::theme()->getActiveTheme()->getName(); // Override logo. $logo = \Drupal::config('color.theme.' . $theme_key)->get('logo'); diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index 616d064..afea590 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -176,7 +176,7 @@ protected function setUp() { // \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work. // Write directly to active storage to avoid early instantiation of // the event dispatcher which can prevent modules from registering events. - \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array())); + \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array('seven' => 1, 'stark' => 1))); // Collect and set a fixed module list. $class = get_class($this); diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index afe1feb..4e89fd6 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -1080,12 +1080,6 @@ private function prepareEnvironment() { unset($GLOBALS['config_directories']); unset($GLOBALS['config']); unset($GLOBALS['conf']); - unset($GLOBALS['theme_key']); - unset($GLOBALS['theme']); - unset($GLOBALS['theme_info']); - unset($GLOBALS['base_theme_info']); - unset($GLOBALS['theme_engine']); - unset($GLOBALS['theme_path']); // Log fatal errors. ini_set('log_errors', 1); @@ -1186,14 +1180,6 @@ private function restoreEnvironment() { // this second reset is guaranteed to reset everything to nothing. drupal_static_reset(); - // Reset global theme variables. - unset($GLOBALS['theme_key']); - unset($GLOBALS['theme']); - unset($GLOBALS['theme_info']); - unset($GLOBALS['base_theme_info']); - unset($GLOBALS['theme_engine']); - unset($GLOBALS['theme_path']); - // Restore original in-memory configuration. $GLOBALS['config'] = $this->originalConfig; $GLOBALS['conf'] = $this->originalConf; diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php index 9d398d9..ceaf612 100644 --- a/core/modules/system/src/Form/ThemeSettingsForm.php +++ b/core/modules/system/src/Form/ThemeSettingsForm.php @@ -248,7 +248,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = $local_file = drupal_get_path('theme', $theme) . '/' . $default; } else { - $local_file = path_to_theme() . '/' . $default; + $local_file = \Drupal::theme()->getActiveTheme()->getPath() . '/' . $default; } $element['#description'] = t('Examples: @implicit-public-file (for a file in the public filesystem), @explicit-file, or @local-file.', array( @@ -284,8 +284,11 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = // Save the name of the current theme (if any), so that we can temporarily // override the current theme and allow theme_get_setting() to work // without having to pass the theme name to it. - $default_theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : NULL; - $GLOBALS['theme_key'] = $theme; + $default_active_theme = \Drupal::theme()->getActiveTheme(); + $default_theme = $default_active_theme->getName(); + /** @var \Drupal\Core\Theme\Initialization $theme_initialization */ + $theme_initialization = \Drupal::service('theme.initialization'); + \Drupal::theme()->setActiveTheme($theme_initialization->getActiveThemeByName($theme)); // Process the theme and all its base themes. foreach ($theme_keys as $theme) { @@ -304,10 +307,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme = // Restore the original current theme. if (isset($default_theme)) { - $GLOBALS['theme_key'] = $default_theme; + \Drupal::theme()->setActiveTheme($default_theme); } else { - unset($GLOBALS['theme_key']); + \Drupal::theme()->setActiveTheme(NULL); } } diff --git a/core/modules/system/src/Tests/Common/AlterTest.php b/core/modules/system/src/Tests/Common/AlterTest.php index d291275..40b3193 100644 --- a/core/modules/system/src/Tests/Common/AlterTest.php +++ b/core/modules/system/src/Tests/Common/AlterTest.php @@ -29,9 +29,8 @@ class AlterTest extends WebTestBase { function testDrupalAlter() { // This test depends on Bartik, so make sure that it is always the current // active theme. - global $theme, $base_theme_info; - $theme = 'bartik'; - $base_theme_info = array(); + \Drupal::service('theme_handler')->enable(array('bartik')); + \Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('bartik')); $array = array('foo' => 'bar'); $entity = new \stdClass(); @@ -41,12 +40,14 @@ function testDrupalAlter() { $array_copy = $array; $array_expected = array('foo' => 'Drupal theme'); \Drupal::moduleHandler()->alter('drupal_alter', $array_copy); + \Drupal::theme()->alter('drupal_alter', $array_copy); $this->assertEqual($array_copy, $array_expected, 'Single array was altered.'); $entity_copy = clone $entity; $entity_expected = clone $entity; $entity_expected->foo = 'Drupal theme'; \Drupal::moduleHandler()->alter('drupal_alter', $entity_copy); + \Drupal::theme()->alter('drupal_alter', $entity_copy); $this->assertEqual($entity_copy, $entity_expected, 'Single object was altered.'); // Verify alteration of multiple arguments. @@ -58,6 +59,7 @@ function testDrupalAlter() { $array2_copy = $array; $array2_expected = array('foo' => 'Drupal theme'); \Drupal::moduleHandler()->alter('drupal_alter', $array_copy, $entity_copy, $array2_copy); + \Drupal::theme()->alter('drupal_alter', $array_copy, $entity_copy, $array2_copy); $this->assertEqual($array_copy, $array_expected, 'First argument to \Drupal::moduleHandler->alter() was altered.'); $this->assertEqual($entity_copy, $entity_expected, 'Second argument to \Drupal::moduleHandler->alter() was altered.'); $this->assertEqual($array2_copy, $array2_expected, 'Third argument to \Drupal::moduleHandler->alter() was altered.'); @@ -68,6 +70,7 @@ function testDrupalAlter() { $array_copy = $array; $array_expected = array('foo' => 'Drupal block theme'); \Drupal::moduleHandler()->alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy); + \Drupal::theme()->alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy); $this->assertEqual($array_copy, $array_expected, 'hook_TYPE_alter() implementations ran in correct order.'); } } diff --git a/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php b/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php index d2b3614..4fc770d 100644 --- a/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php +++ b/core/modules/system/src/Tests/Common/CascadingStylesheetsTest.php @@ -69,7 +69,7 @@ function testReset() { function testRenderFile() { $css = drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css'; _drupal_add_css($css); - $styles = drupal_get_css(); + $styles = drupal_get_css(NULL, FALSE, FALSE); $this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.'); // Verify that newlines are properly added inside style tags. $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0'; @@ -100,7 +100,7 @@ function testRenderInlinePreprocess() { $css = 'body { padding: 0px; }'; $css_preprocessed = ''; _drupal_add_css($css, array('type' => 'inline')); - $styles = drupal_get_css(); + $styles = drupal_get_css(NULL, NULL, FALSE); $this->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.'); } @@ -132,7 +132,7 @@ function testRenderOrder() { drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css', ); - $styles = drupal_get_css(); + $styles = drupal_get_css(NULL, NULL, FALSE); // Stylesheet URL may be the href of a LINK tag or in an @import statement // of a STYLE tag. if (preg_match_all('/(href="|url\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) { diff --git a/core/modules/system/src/Tests/Common/JavaScriptTest.php b/core/modules/system/src/Tests/Common/JavaScriptTest.php index 2f5dcae..58fd249 100644 --- a/core/modules/system/src/Tests/Common/JavaScriptTest.php +++ b/core/modules/system/src/Tests/Common/JavaScriptTest.php @@ -32,9 +32,6 @@ class JavaScriptTest extends DrupalUnitTestBase { function setUp() { parent::setUp(); - // There are dependencies in drupal_get_js() on the theme layer so we need - // to initialize it. - drupal_theme_initialize(); // Disable preprocessing $config = \Drupal::config('system.performance'); diff --git a/core/modules/system/src/Tests/Common/RegionContentTest.php b/core/modules/system/src/Tests/Common/RegionContentTest.php index 6b2d3a4..51ea973 100644 --- a/core/modules/system/src/Tests/Common/RegionContentTest.php +++ b/core/modules/system/src/Tests/Common/RegionContentTest.php @@ -19,7 +19,7 @@ class RegionContentTest extends WebTestBase { * Tests setting and retrieving content for theme regions. */ function testRegions() { - global $theme_key; + $theme_key = \Drupal::theme()->getActiveTheme()->getName(); $block_regions = array_keys(system_region_list($theme_key)); $delimiter = $this->randomName(32); diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php index 8ca6acc..6d24b5d 100644 --- a/core/modules/system/src/Tests/Common/RenderTest.php +++ b/core/modules/system/src/Tests/Common/RenderTest.php @@ -26,13 +26,6 @@ class RenderTest extends DrupalUnitTestBase { */ public static $modules = array('system', 'common_test'); - function setUp() { - parent::setUp(); - // There are dependencies in drupal_get_js() on the theme layer so we need - // to initialize it. - drupal_theme_initialize(); - } - /** * Tests the output drupal_render() for some elementary input values. */ diff --git a/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php b/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php index 03a94cf..23e8cc1 100644 --- a/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php +++ b/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php @@ -36,6 +36,10 @@ public function containerBuild(ContainerBuilder $container) { function setUp() { parent::setUp(); $this->installConfig(array('system')); + + // Reset the available themes to test the case of no themes. + $this->extensionConfig()->set('theme', array()); + $this->extensionConfig()->set('disabled.theme', array()); } /** @@ -51,8 +55,17 @@ function testEmpty() { // 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); + try { + // theme_get_setting() should fail, as no theme is available. + $this->assertIdentical(theme_get_setting('features.favicon'), TRUE); + $this->fail('No theme should lead to an exception'); + } + catch (\RuntimeException $e) { + $this->assertEqual($e->getMessage(), 'No theme is enabled.'); + } + catch (\Exception $e) { + $this->fail('The runtime exception should be thrown.'); + } } /** diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php index d41ec8d..685e7c4 100644 --- a/core/modules/system/src/Tests/Theme/ThemeTest.php +++ b/core/modules/system/src/Tests/Theme/ThemeTest.php @@ -139,14 +139,6 @@ function testFrontPageThemeSuggestion() { } /** - * Ensures theme hook_*_alter() implementations can run before anything is rendered. - */ - function testAlter() { - $this->drupalGet('theme-test/alter'); - $this->assertText('The altered data is test_theme_theme_test_alter_alter was invoked.', 'The theme was able to implement an alter hook during page building before anything was rendered.'); - } - - /** * Ensures a theme's .info.yml file is able to override a module CSS file from being added to the page. * * @see test_theme.info.yml @@ -220,7 +212,7 @@ function testListThemes() { */ function testThemeGetSetting() { $this->container->get('theme_handler')->enable(array('test_subtheme')); - $GLOBALS['theme_key'] = 'test_theme'; + \Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->initTheme('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.'); $this->assertIdentical(theme_get_setting('basetheme_only', 'test_subtheme'), 'base theme value', 'Base theme\'s default settings values are inherited by subtheme.'); diff --git a/core/modules/system/src/Tests/Theme/TwigSettingsTest.php b/core/modules/system/src/Tests/Theme/TwigSettingsTest.php index 630fc14..277eef5 100644 --- a/core/modules/system/src/Tests/Theme/TwigSettingsTest.php +++ b/core/modules/system/src/Tests/Theme/TwigSettingsTest.php @@ -76,8 +76,7 @@ function testTwigCacheOverride() { $theme_handler->setDefault('test_theme'); // The registry still works on theme globals, so set them here. - $GLOBALS['theme'] = 'test_theme'; - $GLOBALS['theme_info'] = $theme_handler->listInfo()['test_theme']; + \Drupal::theme()->setActiveTheme(\Drupal::service('theme.initialization')->getActiveThemeByName('test_theme')); // Reset the theme registry, so that the new theme is used. $this->container->set('theme.registry', NULL); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index f421e6b..5d3b98f 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1657,7 +1657,7 @@ function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $repl function system_page_alter(&$page) { // Find all non-empty page regions, and add a theme wrapper function that // allows them to be consistently themed. - $regions = system_region_list($GLOBALS['theme']); + $regions = system_region_list(\Drupal::theme()->getActiveTheme()->getName()); foreach (array_keys($regions) as $region) { if (!empty($page[$region])) { $page[$region]['#theme_wrappers'][] = 'region'; diff --git a/core/modules/system/tests/modules/batch_test/batch_test.module b/core/modules/system/tests/modules/batch_test/batch_test.module index 9290ef8..bc22b92 100644 --- a/core/modules/system/tests/modules/batch_test/batch_test.module +++ b/core/modules/system/tests/modules/batch_test/batch_test.module @@ -162,7 +162,7 @@ function _batch_test_theme_callback() { // theme is being used on the batch processing page by viewing that page // directly. Instead, we save the theme being used in a variable here, so // that it can be loaded and inspected in the thread running the test. - global $theme; + $theme = \Drupal::theme()->getActiveTheme()->getName(); batch_test_stack($theme); } diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index b4f76a2..a5ce74e 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -110,9 +110,7 @@ function menu_test_callback() { * @deprecated Use \Drupal\menu_test\Controller\MenuTestController::themePage() */ function menu_test_theme_page_callback($inherited = FALSE) { - global $theme_key; - // Initialize the theme system so that $theme_key will be populated. - drupal_theme_initialize(); + $theme_key = \Drupal::theme()->getActiveTheme()->getName(); // Now we check what the theme negotiator service returns. $active_theme = \Drupal::service('theme.negotiator')->determineActiveTheme(\Drupal::routeMatch()); $output = "Active theme: $active_theme. Actual theme: $theme_key."; diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php index 08c07b7..ff02186 100644 --- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php @@ -328,7 +328,7 @@ public function generateOutputKey() { 'result' => $this->view->result, 'roles' => $user->getRoles(), 'super-user' => $user->id() == 1, // special caching for super user. - 'theme' => $GLOBALS['theme'], + 'theme' => \Drupal::theme()->getActiveTheme()->getName(), 'langcode' => \Drupal::languageManager()->getCurrentLanguage()->id, 'base_url' => $GLOBALS['base_url'], ); diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index c7e810b..e244c1c 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -1299,6 +1299,12 @@ public function render($display_id = NULL) { $module_handler = \Drupal::moduleHandler(); + // @TODO on the longrun it would be great to execute a view without + // the theme system at all. + $active_theme = \Drupal::theme()->getActiveTheme(); + $themes = array_keys($active_theme->getBaseThemes()); + $themes[] = $active_theme->getName(); + // Check for already-cached output. if (!empty($this->live_preview)) { $cache = FALSE; @@ -1355,12 +1361,11 @@ public function render($display_id = NULL) { $module_handler->invokeAll('views_pre_render', array($this)); // Let the themes play too, because pre render is a very themey thing. - if (isset($GLOBALS['base_theme_info']) && isset($GLOBALS['theme'])) { - foreach ($GLOBALS['base_theme_info'] as $base) { - $module_handler->invoke($base->getName(), 'views_pre_render', array($this)); + foreach ($themes as $theme_name) { + $function = $theme_name . '_views_pre_render'; + if (function_exists($function)) { + $function($this); } - - $module_handler->invoke($GLOBALS['theme'], 'views_pre_render', array($this)); } $this->display_handler->output = $this->display_handler->render(); @@ -1379,12 +1384,11 @@ public function render($display_id = NULL) { $module_handler->invokeAll('views_post_render', array($this, &$this->display_handler->output, $cache)); // Let the themes play too, because post render is a very themey thing. - if (isset($GLOBALS['base_theme_info']) && isset($GLOBALS['theme'])) { - foreach ($GLOBALS['base_theme_info'] as $base) { - $module_handler->invoke($base->getName(), 'views_post_render', array($this)); + foreach ($themes as $theme_name) { + $function = $theme_name . '_views_post_render'; + if (function_exists($function)) { + $function($this); } - - $module_handler->invoke($GLOBALS['theme'], 'views_post_render', array($this)); } return $this->display_handler->output; diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index 08eaa31..80e5a6d 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -134,6 +134,13 @@ */ protected $httpKernel; + /** + * The mocked theme manager. + * + * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $themeManager; + public function setUp() { $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); @@ -158,6 +165,7 @@ public function setUp() { ->disableOriginalConstructor() ->getMock(); $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); + $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface'); $this->request = new Request(); $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->requestStack = new RequestStack(); @@ -171,7 +179,7 @@ public function setUp() { ->setMethods(array('batchGet', 'drupalInstallationAttempted')) ->getMock(); - $this->formBuilder = new TestFormBuilder($this->formValidator, $this->formSubmitter, $this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->requestStack, $this->classResolver, $this->csrfToken, $this->httpKernel); + $this->formBuilder = new TestFormBuilder($this->formValidator, $this->formSubmitter, $this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->requestStack, $this->classResolver, $this->themeManager, $this->csrfToken, $this->httpKernel); $this->formBuilder->setCurrentUser($this->account); }