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;
......
This diff is collapsed.
......@@ -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;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Initializes the theme for the current request.
*/
class ThemeNegotiatorRequestSubscriber implements EventSubscriberInterface {
/**
* Initializes the theme system after the routing system.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestThemeNegotiator(GetResponseEvent $event) {
if ($event->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;
}
}
......@@ -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) {
......
......@@ -358,6 +358,13 @@ public function listInfo() {
if (!isset($this->list)) {
$this->list = array();
$themes = $this->systemThemeList();
// @todo Ensure that systemThemeList() does not contain an empty list
// during the batch installer, see https://www.drupal.org/node/2322619.
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);
}
......@@ -393,7 +400,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.
......
......@@ -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;
......@@ -89,6 +90,13 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
*/
protected $currentUser;
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* @var \Drupal\Core\Form\FormValidatorInterface
*/
......@@ -116,12 +124,14 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
* The request stack.
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator.
* @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 +141,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 +659,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);
}
/**
......
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ActiveTheme.
*/
namespace Drupal\Core\Theme;
/**
* Defines a theme and its information needed at runtime.
*
* The theme manager will store the active theme object.
*
* @see \Drupal\Core\Theme\ThemeManager
* @see \Drupal\Core\Theme\ThemeInitialization
*/
class ActiveTheme {
/**
* The machine name of the active theme.
*
* @var string
*/
protected $name;
/**
* The path to the theme.
*
* @var string
*/
protected $path;
/**
* The engine of the theme.
*
* @var string
*/
protected $engine;
/**
* The path to the theme engine for root themes.
*
* @var string
*/
protected $owner;
/**
* An array of base theme active theme objects keyed by name.
*
* @var static[]
*/
protected $baseThemes;
/**
* The extension object.
*
* @var \Drupal\Core\Extension\Extension
*/
protected $extension;
/**
* The provided stylesheet of the theme.
*
* @var array
*/
protected $styleSheets;
/**
* The stylesheets which are set to be removed by the theme.
*
* @var array
*/
protected $styleSheetsRemove;
/**
* The stylesheets which are overridden by the theme.
*
* @var array
*/
protected $styleSheetsOverride;
/**
* The libraries provided by the theme.
*
* @var array
*/
protected $libraries;
/**
* Constructs an ActiveTheme object.
*
* @param array $values
* The properties of the object, keyed by the names.
*/
public function __construct(array $values) {
$this->name = $values['name'];
$this->path = $values['path'];
$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 theme engine.
*
* @return string
*/
public function getEngine() {
return $this->engine;
}
/**
* Returns the path to the theme engine for root themes.
*
* @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData
*
* @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;
}
}
......@@ -43,7 +43,10 @@ public function applies(RouteMatchInterface $route_match) {
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
return $this->config->get('default');
// @todo Find a proper way to work at the beginning of the installer when
// there is no configuration available yet. One proper way could be to
// provider a custom negotiator during the installer.
return $this->config->get('default') ?: 'stark';
}
}
......@@ -10,7 +10,6 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Utility\ThemeRegistry;
......@@ -34,7 +33,7 @@ class Registry implements DestructableInterface {
*
* @var array
*/
protected $baseThemes;
protected $baseThemes = array();
/**
* The name of the theme engine of $theme.
......@@ -60,10 +59,11 @@ class Registry implements DestructableInterface {
* from; e.g., 'module' for theme hook 'node' of Node module.
* - name: The name of the extension the original theme hook originates
* from; e.g., 'node' for theme hook 'node' of Node module.
* - theme path: The effective path_to_theme() during _theme(), available as
* 'directory' variable in templates.
* functions, it should point to the respective theme. For templates,
* it should point to the directory that contains the template.
* - theme path: The effective \Drupal\Core\Theme\ActiveTheme::getPath()
* during _theme(), available as
* 'directory' variable in templates. For functions, it should point to
* the respective theme.For templates, it should point to the directory
* that contains the template.
* - includes: (optional) An array of include files to load when the theme
* hook is executed by _theme().
* - file: (optional) A filename to add to 'includes', either prefixed with
......@@ -115,6 +115,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.
*
......@@ -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,23 +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;
$this->baseThemes = $active_theme->getBaseThemes();
$this->engine = $active_theme->getEngine();
}
// Instead of the global theme, a specific theme was requested.
// Instead of the active theme, a specific theme was requested.
else {
// @see drupal_theme_initialize()