Commit a4125e6a authored by alexpott's avatar alexpott

Issue #2228093 by dawehner | sun: Modernize theme initialization.

parent 450cc989
module: {}
theme: {}
theme:
stark: 0
disabled:
theme: {}
......@@ -147,7 +147,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.manager', '@?csrf_token', '@?http_kernel']
form_validator:
class: Drupal\Core\Form\FormValidator
arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form']
......@@ -235,10 +235,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']
......@@ -858,6 +854,12 @@ services:
class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry
feed.writer.wellformedwebrendererentry:
class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
theme.manager:
class: Drupal\Core\Theme\ThemeManager
arguments: ['@theme_handler', '@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack']
theme.initialization:
class: Drupal\Core\Theme\ThemeInitialization
arguments: ['@theme_handler', '@state']
theme.registry:
class: Drupal\Core\Theme\Registry
arguments: ['@cache.default', '@lock', '@module_handler']
......
......@@ -1164,8 +1164,22 @@ 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 is probably a better place to add the CSS from themes,
// see https://www.drupal.org/node/2322617.
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();
......@@ -1174,24 +1188,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']];
}
}
}
......@@ -1812,7 +1827,17 @@ 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) {
// @todo There is probably a better place to add the JS from themes,
// see https://www.drupal.org/node/2322617.
$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();
}
......@@ -1823,6 +1848,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.
......@@ -1839,7 +1865,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
......@@ -2832,6 +2858,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
......@@ -3945,7 +3972,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();
}
}
......
......@@ -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();
}
......
......@@ -2915,8 +2915,6 @@ function batch_set($batch_definition) {
function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) {
$batch =& batch_get();
drupal_theme_initialize();
if (isset($batch)) {
// Add process information
$process_info = array(
......@@ -2926,7 +2924,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NU
'url_options' => array(),
'source_url' => current_path(),
'batch_redirect' => $redirect,
'theme' => $GLOBALS['theme_key'],
'theme' => \Drupal::theme()->getActiveTheme()->getName(),
'redirect_callback' => $redirect_callback,
);
$batch += $process_info;
......
......@@ -20,7 +20,6 @@
use Drupal\Core\Page\FeedLinkElement;
use Drupal\Core\Page\LinkElement;
use Drupal\Core\Page\MetaElement;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\ThemeSettings;
use Drupal\Component\Utility\NestedArray;
......@@ -93,210 +92,6 @@ function drupal_theme_access($theme) {
return \Drupal::service('access_check.theme')->checkAccess($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.
*
......@@ -420,6 +215,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
......@@ -427,8 +223,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();
......@@ -472,11 +266,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.
......@@ -536,6 +325,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
......@@ -596,7 +386,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')) {
......@@ -657,31 +447,9 @@ 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.
*
......@@ -768,7 +536,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.
......@@ -870,7 +638,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])) {
......@@ -1901,7 +1669,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.
......@@ -1991,8 +1759,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;
}
......@@ -2113,7 +1880,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();
}
......
......@@ -18,10 +18,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,21 +74,21 @@ 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\ThemeInitialization $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
// maintenance mode, but we can't let themes alter the .info.yml data until
// we know a theme's base themes. So don't set global $theme until after
// we know a theme's base themes. So don't set active theme until after
// 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,9 +96,10 @@ 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, see https://www.drupal.org/node/2322619.
\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');
}
......
......@@ -623,6 +623,15 @@ public static function formBuilder() {
return static::$container->get('form_builder');
}
/**
* Gets the theme service.
*
* @return \Drupal\Core\Theme\ThemeManagerInterface
*/
public static function theme() {
return static::$container->get('theme.manager');
}
/**
* Gets the syncing state.
*
......
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\ThemeNegotiatorRequestSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\RouteMatch;