Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,201 @@
<?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 stylesheets which are set to be removed by the theme.
*
* @var array
*/
protected $styleSheetsRemove;
/**
* The libraries provided by the theme.
*
* @var array
*/
protected $libraries;
/**
* The regions provided by the theme.
*
* @var array
*/
protected $regions;
/**
* Constructs an ActiveTheme object.
*
* @param array $values
* The properties of the object, keyed by the names.
*/
public function __construct(array $values) {
$values += [
'path' => '',
'engine' => 'twig',
'owner' => 'twig',
'stylesheets_remove' => [],
'libraries' => [],
'extension' => 'html.twig',
'base_themes' => [],
'regions' => [],
];
$this->name = $values['name'];
$this->path = $values['path'];
$this->engine = $values['engine'];
$this->owner = $values['owner'];
$this->styleSheetsRemove = $values['stylesheets_remove'];
$this->libraries = $values['libraries'];
$this->extension = $values['extension'];
$this->baseThemes = $values['base_themes'];
$this->regions = $values['regions'];
}
/**
* 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 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.
*
* The order starts with the base theme of $this and ends with the root of
* the dependency chain.
*
* @return static[]
*/
public function getBaseThemes() {
return $this->baseThemes;
}
/**
* The regions used by the theme.
*
* @return string[]
* The list of region machine names supported by the theme.
*
* @see system_region_list()
*/
public function getRegions() {
return array_keys($this->regions);
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\AjaxBasePageNegotiator.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Defines a theme negotiator that deals with the active theme on ajax requests.
*
* Many different pages can invoke an Ajax request to system/ajax or another
* generic Ajax path. It is almost always desired for an Ajax response to be
* rendered using the same theme as the base page, because most themes are built
* with the assumption that they control the entire page, so if the CSS for two
* themes are both loaded for a given page, they may conflict with each other.
* For example, Bartik is Drupal's default theme, and Seven is Drupal's default
* administration theme. Depending on whether the "Use the administration theme
* when editing or creating content" checkbox is checked, the node edit form may
* be displayed in either theme, but the Ajax response to the Field module's
* "Add another item" button should be rendered using the same theme as the rest
* of the page.
*
* Therefore specify '_theme: ajax_base_page' as part of the router options.
*/
class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
/**
* The CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfGenerator;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a new AjaxBasePageNegotiator.
*
* @param \Drupal\Core\Access\CsrfTokenGenerator $token_generator
* The CSRF token generator.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack used to retrieve the current request.
*/
public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryInterface $config_factory, RequestStack $request_stack) {
$this->csrfGenerator = $token_generator;
$this->configFactory = $config_factory;
$this->requestStack = $request_stack;
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
// Check whether the route was configured to use the base page theme.
return ($route = $route_match->getRouteObject())
&& $route->hasOption('_theme')
&& $route->getOption('_theme') == 'ajax_base_page';
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
if (($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state')) && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
$theme = $ajax_page_state['theme'];
$token = $ajax_page_state['theme_token'];
// Prevent a request forgery from giving a person access to a theme they
// shouldn't be otherwise allowed to see. However, since everyone is
// allowed to see the default theme, token validation isn't required for
// that, and bypassing it allows most use-cases to work even when accessed
// from the page cache.
if ($theme === $this->configFactory->get('system.theme')->get('default') || $this->csrfGenerator->validate($token, $theme)) {
return $theme;
}
}
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\DefaultNegotiator.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Determines the default theme of the site.
*/
class DefaultNegotiator implements ThemeNegotiatorInterface {
/**
* The system theme config object.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs a DefaultNegotiator object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
return $this->configFactory->get('system.theme')->get('default');
}
}

View file

@ -0,0 +1,603 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\Registry.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Utility\ThemeRegistry;
/**
* Defines the theme registry service.
*
* @todo Replace local $registry variables in methods with $this->registry.
*/
class Registry implements DestructableInterface {
/**
* The theme object representing the active theme for this registry.
*
* @var \Drupal\Core\Theme\ActiveTheme
*/
protected $theme;
/**
* The lock backend that should be used.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The complete theme registry.
*
* @var array
* An associative array keyed by theme hook names, whose values are
* associative arrays containing the aggregated hook definition:
* - type: The type of the extension the original theme hook originates
* 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 \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
* the value of 'path', or the path of the extension implementing
* hook_theme().
* In case of a theme base hook, one of the following:
* - variables: An associative array whose keys are variable names and whose
* values are default values of the variables to use for this theme hook.
* - render element: A string denoting the name of the variable name, in
* which the render element for this theme hook is provided.
* In case of a theme template file:
* - path: The path to the template file to use. Defaults to the
* subdirectory 'templates' of the path of the extension implementing
* hook_theme(); e.g., 'core/modules/node/templates' for Node module.
* - template: The basename of the template file to use, without extension
* (as the extension is specific to the theme engine). The template file
* is in the directory defined by 'path'.
* - template_file: A full path and file name to a template file to use.
* Allows any extension to override the effective template file.
* - engine: The theme engine to use for the template file.
* In case of a theme function:
* - function: The function name to call to generate the output.
* For any registered theme hook, including theme hook suggestions:
* - preprocess: An array of theme variable preprocess callbacks to invoke
* before invoking final theme variable processors.
* - process: An array of theme variable process callbacks to invoke
* before invoking the actual theme function or template.
*/
protected $registry;
/**
* The cache backend to use for the complete theme registry data.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The module handler to use to load modules.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The incomplete, runtime theme registry.
*
* @var \Drupal\Core\Utility\ThemeRegistry
*/
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;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* Constructs a \Drupal\Core\Theme\Registry object.
*
* @param string $root
* The app root.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend interface to use for the complete theme registry data.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to use to load modules.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
* The theme initialization.
* @param string $theme_name
* (optional) The name of the theme for which to construct the registry.
*/
public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, $theme_name = NULL) {
$this->root = $root;
$this->cache = $cache;
$this->lock = $lock;
$this->moduleHandler = $module_handler;
$this->themeName = $theme_name;
$this->themeHandler = $theme_handler;
$this->themeInitialization = $theme_initialization;
}
/**
* Sets the theme manager.
*
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
*/
public function setThemeManager(ThemeManagerInterface $theme_manager) {
$this->themeManager = $theme_manager;
}
/**
* Initializes a theme with a certain name.
*
* This function does to much magic, so it should be replaced by another
* services which holds the current active theme information.
*
* @param string $theme_name
* (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)) {
$this->theme = $this->themeManager->getActiveTheme();
}
// Instead of the active theme, a specific theme was requested.
else {
$this->theme = $this->themeInitialization->getActiveThemeByName($theme_name);
$this->themeInitialization->loadActiveTheme($this->theme);
}
}
/**
* Returns the complete theme registry from cache or rebuilds it.
*
* @return array
* The complete theme registry data array.
*
* @see Registry::$registry
*/
public function get() {
$this->init($this->themeName);
if (isset($this->registry)) {
return $this->registry;
}
if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) {
$this->registry = $cache->data;
}
else {
$this->registry = $this->build();
// Only persist it if all modules are loaded to ensure it is complete.
if ($this->moduleHandler->isLoaded()) {
$this->setCache();
}
}
return $this->registry;
}
/**
* Returns the incomplete, runtime theme registry.
*
* @return \Drupal\Core\Utility\ThemeRegistry
* A shared instance of the ThemeRegistry class, provides an ArrayObject
* that allows it to be accessed with array syntax and isset(), and is more
* 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'), $this->moduleHandler->isLoaded());
}
return $this->runtimeRegistry;
}
/**
* Persists the theme registry in the cache backend.
*/
protected function setCache() {
$this->cache->set('theme_registry:' . $this->theme->getName(), $this->registry, Cache::PERMANENT, array('theme_registry'));
}
/**
* Returns the base hook for a given hook suggestion.
*
* @param string $hook
* The name of a theme hook whose base hook to find.
*
* @return string|false
* 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
// not supported, so the base hook must be an original implementation that
// points to a theme function or template.
while ($pos = strrpos($base_hook, '__')) {
$base_hook = substr($base_hook, 0, $pos);
if (isset($this->registry[$base_hook]['exists'])) {
break;
}
}
if ($pos !== FALSE && $base_hook !== $hook) {
return $base_hook;
}
return FALSE;
}
/**
* Builds the theme registry cache.
*
* Theme hook definitions are collected in the following order:
* - Modules
* - Base theme engines
* - Base themes
* - Theme engine
* - Theme
*
* All theme hook definitions are essentially just collated and merged in the
* above order. However, various extension-specific default values and
* customizations are required; e.g., to record the effective file path for
* theme template. Therefore, this method first collects all extensions per
* type, and then dispatches the processing for each extension to
* processExtension().
*
* After completing the collection, modules are allowed to alter it. Lastly,
* any derived and incomplete theme hook definitions that are hook suggestions
* for base hooks (e.g., 'block__node' for the base hook 'block') need to be
* determined based on the full registry and classified as 'base hook'.
*
* See the @link themeable Default theme implementations topic @endlink for
* details.
*
* @return \Drupal\Core\Utility\ThemeRegistry
* The build theme registry.
*
* @see hook_theme_registry_alter()
*/
protected function build() {
$cache = array();
// First, preprocess the theme hooks advertised by modules. This will
// serve as the basic registry. Since the list of enabled modules is the
// same regardless of the theme used, this is cached in its own entry to
// save building it for every theme.
if ($cached = $this->cache->get('theme_registry:build:modules')) {
$cache = $cached->data;
}
else {
foreach ($this->moduleHandler->getImplementations('theme') as $module) {
$this->processExtension($cache, $module, 'module', $module, $this->getPath($module));
}
// Only cache this registry if all modules are loaded.
if ($this->moduleHandler->isLoaded()) {
$this->cache->set("theme_registry:build:modules", $cache, Cache::PERMANENT, array('theme_registry'));
}
}
// Process each base theme.
// Ensure that we start with the root of the parents, so that both CSS files
// and preprocess functions comes first.
foreach (array_reverse($this->theme->getBaseThemes()) as $base) {
// If the base theme uses a theme engine, process its hooks.
$base_path = $base->getPath();
if ($this->theme->getEngine()) {
$this->processExtension($cache, $this->theme->getEngine(), 'base_theme_engine', $base->getName(), $base_path);
}
$this->processExtension($cache, $base->getName(), 'base_theme', $base->getName(), $base_path);
}
// And then the same thing, but for the theme.
if ($this->theme->getEngine()) {
$this->processExtension($cache, $this->theme->getEngine(), 'theme_engine', $this->theme->getName(), $this->theme->getPath());
}
// Finally, hooks provided by the theme itself.
$this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath());
// Let modules and themes alter the registry.
$this->moduleHandler->alter('theme_registry', $cache);
$this->themeManager->alterForTheme($this->theme, 'theme_registry', $cache);
// @todo Implement more reduction of the theme registry entry.
// Optimize the registry to not have empty arrays for functions.
foreach ($cache as $hook => $info) {
if (empty($info['preprocess functions'])) {
unset($cache[$hook]['preprocess functions']);
}
}
$this->registry = $cache;
return $this->registry;
}
/**
* Process a single implementation of hook_theme().
*
* @param array $cache
* The theme registry that will eventually be cached; It is an associative
* array keyed by theme hooks, whose values are associative arrays
* describing the hook:
* - 'type': The passed-in $type.
* - 'theme path': The passed-in $path.
* - 'function': The name of the function generating output for this theme
* hook. Either defined explicitly in hook_theme() or, if neither
* 'function' nor 'template' is defined, then the default theme function
* name is used. The default theme function name is the theme hook
* prefixed by either 'theme_' for modules or '$name_' for everything
* else. If 'function' is defined, 'template' is not used.
* - 'template': The filename of the template generating output for this
* theme hook. The template is in the directory defined by the 'path' key
* of hook_theme() or defaults to "$path/templates".
* - 'variables': The variables for this theme hook as defined in
* hook_theme(). If there is more than one implementation and 'variables'
* is not specified in a later one, then the previous definition is kept.
* - 'render element': The renderable element for this theme hook as defined
* in hook_theme(). If there is more than one implementation and
* 'render element' is not specified in a later one, then the previous
* definition is kept.
* - 'preprocess functions': See _theme() for detailed documentation.
* @param string $name
* The name of the module, theme engine, base theme engine, theme or base
* theme implementing hook_theme().
* @param string $type
* One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or
* 'base_theme'. Unlike regular hooks that can only be implemented by
* modules, each of these can implement hook_theme(). This function is
* called in aforementioned order and new entries override older ones. For
* example, if a theme hook is both defined by a module and a theme, then
* the definition in the theme will be used.
* @param string $theme
* The actual name of theme, module, etc. that is being processed.
* @param string $path
* The directory where $name is. For example, modules/system or
* themes/bartik.
*
* @see \Drupal\Core\Theme\ThemeManagerInterface::render()
* @see hook_theme()
* @see \Drupal\Core\Extension\ThemeHandler::listInfo()
* @see twig_render_template()
*
* @throws \BadFunctionCallException
*/
protected function processExtension(array &$cache, $name, $type, $theme, $path) {
$result = array();
$hook_defaults = array(
'variables' => TRUE,
'render element' => TRUE,
'pattern' => TRUE,
'base hook' => TRUE,
);
$module_list = array_keys((array) $this->moduleHandler->getModuleList());
// Invoke the hook_theme() implementation, preprocess what is returned, and
// merge it into $cache.
$function = $name . '_theme';
if (function_exists($function)) {
$result = $function($cache, $type, $theme, $path);
foreach ($result as $hook => $info) {
// When a theme or engine overrides a module's theme function
// $result[$hook] will only contain key/value pairs for information being
// overridden. Pull the rest of the information from what was defined by
// an earlier hook.
// Fill in the type and path of the module, theme, or engine that
// implements this theme function.
$result[$hook]['type'] = $type;
$result[$hook]['theme path'] = $path;
if (isset($cache[$hook]['includes'])) {
$result[$hook]['includes'] = $cache[$hook]['includes'];
}
// If the theme implementation defines a file, then also use the path
// that it defined. Otherwise use the default path. This allows
// system.module to declare theme functions on behalf of core .include
// files.
if (isset($info['file'])) {
$include_file = isset($info['path']) ? $info['path'] : $path;
$include_file .= '/' . $info['file'];
include_once $this->root . '/' . $include_file;
$result[$hook]['includes'][] = $include_file;
}
// A template file is the default implementation for a theme hook, but
// if the theme hook specifies a function callback instead, check to
// ensure the function actually exists.
if (isset($info['function']) && !function_exists($info['function'])) {
throw new \BadFunctionCallException(sprintf(
'Theme hook "%s" refers to a theme function callback that does not exist: "%s"',
$hook,
$info['function']
));
}
// Provide a default naming convention for 'template' based on the
// hook used. If the template does not exist, the theme engine used
// should throw an exception at runtime when attempting to include
// the template file.
elseif (!isset($info['template'])) {
$info['template'] = strtr($hook, '_', '-');
$result[$hook]['template'] = $info['template'];
}
// Prepend the current theming path when none is set. This is required
// for the default theme engine to know where the template lives.
if (isset($result[$hook]['template']) && !isset($info['path'])) {
$result[$hook]['path'] = $path . '/templates';
}
// If the default keys are not set, use the default values registered
// by the module.
if (isset($cache[$hook])) {
$result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
}
// Preprocess variables for all theming hooks, whether the hook is
// implemented as a template or as a function. Ensure they are arrays.
if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
$info['preprocess functions'] = array();
$prefixes = array();
if ($type == 'module') {
// Default variable preprocessor prefix.
$prefixes[] = 'template';
// Add all modules so they can intervene with their own variable
// preprocessors. This allows them to provide variable preprocessors
// even if they are not the owner of the current hook.
$prefixes = array_merge($prefixes, $module_list);
}
elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
// Theme engines get an extra set that come before the normally
// named variable preprocessors.
$prefixes[] = $name . '_engine';
// The theme engine registers on behalf of the theme using the
// theme's name.
$prefixes[] = $theme;
}
else {
// This applies when the theme manually registers their own variable
// preprocessors.
$prefixes[] = $name;
}
foreach ($prefixes as $prefix) {
// Only use non-hook-specific variable preprocessors for theming
// hooks implemented as templates. See _theme().
if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
$info['preprocess functions'][] = $prefix . '_preprocess';
}
if (function_exists($prefix . '_preprocess_' . $hook)) {
$info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
}
}
}
// Check for the override flag and prevent the cached variable
// preprocessors from being used. This allows themes or theme engines
// to remove variable preprocessors set earlier in the registry build.
if (!empty($info['override preprocess functions'])) {
// Flag not needed inside the registry.
unset($result[$hook]['override preprocess functions']);
}
elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
$info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
}
$result[$hook]['preprocess functions'] = $info['preprocess functions'];
}
// Merge the newly created theme hooks into the existing cache.
$cache = $result + $cache;
}
// Let themes have variable preprocessors even if they didn't register a
// template.
if ($type == 'theme' || $type == 'base_theme') {
foreach ($cache as $hook => $info) {
// Check only if not registered by the theme or engine.
if (empty($result[$hook])) {
if (!isset($info['preprocess functions'])) {
$cache[$hook]['preprocess functions'] = array();
}
// Only use non-hook-specific variable preprocessors for theme hooks
// implemented as templates. See _theme().
if (isset($info['template']) && function_exists($name . '_preprocess')) {
$cache[$hook]['preprocess functions'][] = $name . '_preprocess';
}
if (function_exists($name . '_preprocess_' . $hook)) {
$cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
$cache[$hook]['theme path'] = $path;
}
// Ensure uniqueness.
$cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
}
}
}
}
/**
* Invalidates theme registry caches.
*
* 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();
}
$this->runtimeRegistry = NULL;
$this->registry = NULL;
Cache::invalidateTags(array('theme_registry'));
return $this;
}
/**
* {@inheritdoc}
*/
public function destruct() {
if (isset($this->runtimeRegistry)) {
$this->runtimeRegistry->destruct();
}
}
/**
* Wraps drupal_get_path().
*
* @param string $module
* The name of the item for which the path is requested.
*
* @return string
*/
protected function getPath($module) {
return drupal_get_path('module', $module);
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeAccessCheck.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
/**
* Provides access checking for themes for routing and theme negotiation.
*/
class ThemeAccessCheck implements AccessInterface {
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a \Drupal\Core\Theme\Registry object.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(ThemeHandlerInterface $theme_handler) {
$this->themeHandler = $theme_handler;
}
/**
* Checks access to the theme for routing.
*
* @param string $theme
* The name of a theme.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access($theme) {
// Cacheable until the theme settings are modified.
return AccessResult::allowedIf($this->checkAccess($theme))->addCacheTags(['config:' . $theme . '.settings']);
}
/**
* Indicates whether the theme is accessible based on whether it is installed.
*
* @param string $theme
* The name of a theme.
*
* @return bool
* TRUE if the theme is installed, FALSE otherwise.
*/
public function checkAccess($theme) {
$themes = $this->themeHandler->listInfo();
return !empty($themes[$theme]->status);
}
}

View file

@ -0,0 +1,259 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeInitialization.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
/**
* Provides the theme initialization logic.
*/
class ThemeInitialization implements ThemeInitializationInterface {
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The cache backend to use for the active theme.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* The extensions that might be attaching assets.
*
* @var array
*/
protected $extensions;
/**
* Constructs a new ThemeInitialization object.
*
* @param string $root
* The app root.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to use to load modules.
*/
public function __construct($root, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
$this->root = $root;
$this->themeHandler = $theme_handler;
$this->cache = $cache;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function initTheme($theme_name) {
$active_theme = $this->getActiveThemeByName($theme_name);
$this->loadActiveTheme($active_theme);
return $active_theme;
}
/**
* {@inheritdoc}
*/
public function getActiveThemeByName($theme_name) {
if ($cached = $this->cache->get('theme.active_theme.' . $theme_name)) {
return $cached->data;
}
$themes = $this->themeHandler->listInfo();
// If no theme could be negotiated, or if the negotiated theme is not within
// the list of installed themes, fall back to the default theme output of
// core and modules (like Stark, but without a theme extension at all). This
// is possible, because loadActiveTheme() always loads the Twig theme
// engine. This is desired, because missing or malformed theme configuration
// should not leave the application in a broken state. By falling back to
// default output, the user is able to reconfigure the theme through the UI.
// Lastly, tests are expected to operate with no theme by default, so as to
// only assert the original theme output of modules (unless a test manually
// installs a specific theme).
if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) {
$theme_name = 'core';
// /core/core.info.yml does not actually exist, but is required because
// Extension expects a pathname.
$active_theme = $this->getActiveTheme(new Extension($this->root, 'theme', 'core/core.info.yml'));
// Early-return and do not set state, because the initialized $theme_name
// differs from the original $theme_name.
return $active_theme;
}
// 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->cache->set('theme.active_theme.' . $theme_name, $active_theme);
return $active_theme;
}
/**
* {@inheritdoc}
*/
public function loadActiveTheme(ActiveTheme $active_theme) {
// Initialize the theme.
if ($theme_engine = $active_theme->getEngine()) {
// Include the engine.
include_once $this->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 $this->root . '/' . $base->getOwner();
}
}
// and our theme gets one too.
if ($active_theme->getOwner()) {
include_once $this->root . '/' . $active_theme->getOwner();
}
}
// Always include Twig as the default theme engine.
include_once $this->root . '/core/themes/engines/twig/twig.engine';
}
/**
* {@inheritdoc}
*/
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 remove CSS files
// easily from parent.
$values['stylesheets_remove'] = array();
// Grab stylesheets from base theme.
foreach ($base_themes as $base) {
$base_theme_path = $base->getPath();
if (!empty($base->info['stylesheets-remove'])) {
foreach ($base->info['stylesheets-remove'] as $css_file) {
$css_file = $this->resolveStyleSheetPlaceholders($css_file);
$values['stylesheets_remove'][$css_file] = $css_file;
}
}
}
// Add stylesheets used by this theme.
if (!empty($theme->info['stylesheets-remove'])) {
foreach ($theme->info['stylesheets-remove'] as $css_file) {
$css_file = $this->resolveStyleSheetPlaceholders($css_file);
$values['stylesheets_remove'][$css_file] = $css_file;
}
}
// 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;
if (!empty($theme->info['regions'])) {
$values['regions'] = $theme->info['regions'];
}
return new ActiveTheme($values);
}
/**
* Gets all extensions.
*
* @return array
*/
protected function getExtensions() {
if (!isset($this->extensions)) {
$this->extensions = array_merge($this->moduleHandler->getModuleList(), $this->themeHandler->listInfo());
}
return $this->extensions;
}
/**
* Gets CSS file where tokens have been resolved.
*
* @param string $css_file
* CSS file which may contain tokens.
*
* @return string
* CSS file where placeholders are replaced.
*/
protected function resolveStyleSheetPlaceholders($css_file) {
$token_candidate = explode('/', $css_file)[0];
if (!preg_match('/@[A-z0-9_-]+/', $token_candidate)) {
return $css_file;
}
$token = substr($token_candidate, 1);
// Prime extensions.
$extensions = $this->getExtensions();
if (isset($extensions[$token])) {
return str_replace($token_candidate, $extensions[$token]->getPath(), $css_file);
}
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeInitializationInterface.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Extension\Extension;
/**
* Defines an interface which contain theme initialization logic.
*/
interface ThemeInitializationInterface {
/**
* 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);
/**
* 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);
/**
* 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 \Drupal\Core\Theme\ActiveTheme $active_theme
* The theme to load.
*/
public function loadActiveTheme(ActiveTheme $active_theme);
/**
* Builds up the active theme object from extensions.
*
* @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.
*
* @return \Drupal\Core\Theme\ActiveTheme
* The active theme instance for the passed in $theme.
*/
public function getActiveTheme(Extension $theme, array $base_themes = []);
}

View file

@ -0,0 +1,468 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeManager.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\StackedRouteMatchInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Component\Utility\SafeMarkup;
/**
* Provides the default implementation of a theme manager.
*/
class ThemeManager implements ThemeManagerInterface {
/**
* The theme negotiator.
*
* @var \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
protected $themeNegotiator;
/**
* The theme registry used to render an output.
*
* @var \Drupal\Core\Theme\Registry
*/
protected $themeRegistry;
/**
* Contains the current active theme.
*
* @var \Drupal\Core\Theme\ActiveTheme
*/
protected $activeTheme;
/**
* The theme initialization.
*
* @var \Drupal\Core\Theme\ThemeInitializationInterface
*/
protected $themeInitialization;
/**
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* Constructs a new ThemeManager object.
*
* @param string $root
* The app root.
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
* The theme negotiator.
* @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
* The theme initialization.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
*/
public function __construct($root, ThemeNegotiatorInterface $theme_negotiator, ThemeInitializationInterface $theme_initialization, RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
$this->root = $root;
$this->themeNegotiator = $theme_negotiator;
$this->themeInitialization = $theme_initialization;
$this->requestStack = $request_stack;
$this->moduleHandler = $module_handler;
}
/**
* Sets the theme registry.
*
* @param \Drupal\Core\Theme\Registry $theme_registry
* The theme registry.
*
* @return $this
*/
public function setThemeRegistry(Registry $theme_registry) {
$this->themeRegistry = $theme_registry;
return $this;
}
/**
* {@inheritdoc}
*/
public function render($hook, array $variables) {
return $this->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);
}
/**
* {@inheritdoc}
*/
public function resetActiveTheme() {
$this->activeTheme = NULL;
return $this;
}
/**
* {@inheritdoc}
*/
public function setActiveTheme(ActiveTheme $active_theme) {
$this->activeTheme = $active_theme;
if ($active_theme) {
$this->themeInitialization->loadActiveTheme($active_theme);
}
return $this;
}
/**
* Generates themed output (internal use only).
*
* @see \Drupal\Core\Render\RendererInterface::render();
*/
protected function theme($hook, $variables = array()) {
static $default_attributes;
$active_theme = $this->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
// request properly. See also \Drupal\Core\Theme\Registry::get().
if (!$this->moduleHandler->isLoaded() && !defined('MAINTENANCE_MODE')) {
throw new \Exception(t('_theme() may not be called until all modules are loaded.'));
}
$theme_registry = $this->themeRegistry->getRuntime();
// If an array of hook candidates were passed, use the first one that has an
// implementation.
if (is_array($hook)) {
foreach ($hook as $candidate) {
if ($theme_registry->has($candidate)) {
break;
}
}
$hook = $candidate;
}
// Save the original theme hook, so it can be supplied to theme variable
// preprocess callbacks.
$original_hook = $hook;
// If there's no implementation, check for more generic fallbacks.
// If there's still no implementation, log an error and return an empty
// string.
if (!$theme_registry->has($hook)) {
// Iteratively strip everything after the last '__' delimiter, until an
// implementation is found.
while ($pos = strrpos($hook, '__')) {
$hook = substr($hook, 0, $pos);
if ($theme_registry->has($hook)) {
break;
}
}
if (!$theme_registry->has($hook)) {
// Only log a message when not trying theme suggestions ($hook being an
// array).
if (!isset($candidate)) {
\Drupal::logger('theme')->warning('Theme hook %hook not found.', array('%hook' => $hook));
}
// There is no theme implementation for the hook passed. Return FALSE so
// the function calling _theme() can differentiate between a hook that
// exists and renders an empty string and a hook that is not
// implemented.
return FALSE;
}
}
$info = $theme_registry->get($hook);
// If a renderable array is passed as $variables, then set $variables to
// the arguments expected by the theme function.
if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) {
$element = $variables;
$variables = array();
if (isset($info['variables'])) {
foreach (array_keys($info['variables']) as $name) {
if (isset($element["#$name"]) || array_key_exists("#$name", $element)) {
$variables[$name] = $element["#$name"];
}
}
}
else {
$variables[$info['render element']] = $element;
// Give a hint to render engines to prevent infinite recursion.
$variables[$info['render element']]['#render_children'] = TRUE;
}
}
// Merge in argument defaults.
if (!empty($info['variables'])) {
$variables += $info['variables'];
}
elseif (!empty($info['render element'])) {
$variables += array($info['render element'] => array());
}
// Supply original caller info.
$variables += array(
'theme_hook_original' => $original_hook,
);
// Set base hook for later use. For example if '#theme' => 'node__article'
// is called, we run hook_theme_suggestions_node_alter() rather than
// hook_theme_suggestions_node__article_alter(), and also pass in the base
// hook as the last parameter to the suggestions alter hooks.
if (isset($info['base hook'])) {
$base_theme_hook = $info['base hook'];
}
else {
$base_theme_hook = $hook;
}
// Invoke hook_theme_suggestions_HOOK().
$suggestions = $this->moduleHandler->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables));
// If _theme() was invoked with a direct theme suggestion like
// '#theme' => 'node__article', add it to the suggestions array before
// invoking suggestion alter hooks.
if (isset($info['base hook'])) {
$suggestions[] = $hook;
}
// Invoke hook_theme_suggestions_alter() and
// hook_theme_suggestions_HOOK_alter().
$hooks = array(
'theme_suggestions',
'theme_suggestions_' . $base_theme_hook,
);
$this->moduleHandler->alter($hooks, $suggestions, $variables, $base_theme_hook);
$this->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
// function may call _theme('node', ...), but a module can add
// 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
// enabling a theme to have an alternate template file for article nodes.
foreach (array_reverse($suggestions) as $suggestion) {
if ($theme_registry->has($suggestion)) {
$info = $theme_registry->get($suggestion);
break;
}
}
// Include a file if the theme function or variable preprocessor is held
// elsewhere.
if (!empty($info['includes'])) {
foreach ($info['includes'] as $include_file) {
include_once $this->root . '/' . $include_file;
}
}
// Invoke the variable preprocessors, if any.
if (isset($info['base hook'])) {
$base_hook = $info['base hook'];
$base_hook_info = $theme_registry->get($base_hook);
// Include files required by the base hook, since its variable
// preprocessors might reside there.
if (!empty($base_hook_info['includes'])) {
foreach ($base_hook_info['includes'] as $include_file) {
include_once $this->root . '/' . $include_file;
}
}
// Replace the preprocess functions with those from the base hook.
if (isset($base_hook_info['preprocess functions'])) {
// Set a variable for the 'theme_hook_suggestion'. This is used to
// maintain backwards compatibility with template engines.
$theme_hook_suggestion = $hook;
$info['preprocess functions'] = $base_hook_info['preprocess functions'];
}
}
if (isset($info['preprocess functions'])) {
foreach ($info['preprocess functions'] as $preprocessor_function) {
if (function_exists($preprocessor_function)) {
$preprocessor_function($variables, $hook, $info);
}
}
// Allow theme preprocess functions to set $variables['#attached'] and use
// it like the #attached property on render arrays. In Drupal 8, this is
// the (only) officially supported method of attaching assets from
// preprocess functions. Assets attached here should be associated with
// the template that we're preprocessing variables for.
if (isset($variables['#attached'])) {
$preprocess_attached = ['#attached' => $variables['#attached']];
drupal_render($preprocess_attached);
}
}
// Generate the output using either a function or a template.
$output = '';
if (isset($info['function'])) {
if (function_exists($info['function'])) {
$output = SafeMarkup::set($info['function']($variables));
}
}
else {
$render_function = 'twig_render_template';
$extension = '.html.twig';
// The theme engine may use a different extension and a different
// renderer.
$theme_engine = $active_theme->getEngine();
if (isset($theme_engine)) {
if ($info['type'] != 'module') {
if (function_exists($theme_engine . '_render_template')) {
$render_function = $theme_engine . '_render_template';
}
$extension_function = $theme_engine . '_extension';
if (function_exists($extension_function)) {
$extension = $extension_function();
}
}
}
// In some cases, a template implementation may not have had
// template_preprocess() run (for example, if the default implementation
// is a function, but a template overrides that default implementation).
// In these cases, a template should still be able to expect to have
// access to the variables provided by template_preprocess(), so we add
// them here if they don't already exist. We don't want the overhead of
// running template_preprocess() twice, so we use the 'directory' variable
// to determine if it has already run, which while not completely
// intuitive, is reasonably safe, and allows us to save on the overhead of
// adding some new variable to track that.
if (!isset($variables['directory'])) {
$default_template_variables = array();
template_preprocess($default_template_variables, $hook, $info);
$variables += $default_template_variables;
}
if (!isset($default_attributes)) {
$default_attributes = new Attribute();
}
foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) {
if (isset($variables[$key]) && !($variables[$key] instanceof Attribute)) {
if ($variables[$key]) {
$variables[$key] = new Attribute($variables[$key]);
}
else {
// Create empty attributes.
$variables[$key] = clone $default_attributes;
}
}
}
// Render the output using the template file.
$template_file = $info['template'] . $extension;
if (isset($info['path'])) {
$template_file = $info['path'] . '/' . $template_file;
}
// Add the theme suggestions to the variables array just before rendering
// the template for backwards compatibility with template engines.
$variables['theme_hook_suggestions'] = $suggestions;
// For backwards compatibility, pass 'theme_hook_suggestion' on to the
// template engine. This is only set when calling a direct suggestion like
// '#theme' => 'menu__shortcut_default' when the template exists in the
// current theme.
if (isset($theme_hook_suggestion)) {
$variables['theme_hook_suggestion'] = $theme_hook_suggestion;
}
$output = $render_function($template_file, $variables);
}
return (string) $output;
}
/**
* Initializes the active theme for a given route match.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
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();
}
if ($route_match instanceof StackedRouteMatchInterface) {
$route_match = $route_match->getMasterRouteMatch();
}
$theme = $this->themeNegotiator->determineActiveTheme($route_match);
$this->activeTheme = $this->themeInitialization->initTheme($theme);
}
/**
* {@inheritdoc}
*
* @todo Should we cache some of these information?
*/
public function alterForTheme(ActiveTheme $theme, $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();
foreach ($theme->getBaseThemes() as $base) {
$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);
}
}
/**
* {@inheritdoc}
*/
public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
$theme = $this->getActiveTheme();
$this->alterForTheme($theme, $type, $data, $context1, $context2);
}
}

View file

@ -0,0 +1,148 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeManagerInterface.
*/
namespace Drupal\Core\Theme;
/**
* Provides a high level access to the active theme and methods to use it.
*
* Beside the active theme it provides a wrapper around _theme as well as the
* alter functionality for themes.
*/
interface ThemeManagerInterface {
/**
* Generates themed output.
*
* See the @link themeable Default theme implementations topic @endlink for
* details.
*
* @param string $hook
* The name of the theme hook to call.
* @param array $variables
* An associative array of theme variables.
*
* @return string
* The rendered output.
*/
public function render($hook, array $variables);
/**
* Returns the active theme object.
*
* @return \Drupal\Core\Theme\ActiveTheme
*/
public function getActiveTheme();
/**
* Determines whether there is an active theme.
*
* @return bool
*/
public function hasActiveTheme();
/**
* Resets the current active theme.
*
* Note: This method should not be used in common cases, just in special cases
* like tests.
*
* @return $this
*/
public function resetActiveTheme();
/**
* Sets the current active theme manually.
*
* Note: This method should not be used in common cases, just in special cases
* like tests.
*
* @param \Drupal\Core\Theme\ActiveTheme $active_theme
* The new active theme.
* @return $this
*/
public function setActiveTheme(ActiveTheme $active_theme);
/**
* Passes alterable variables to specific $theme_TYPE_alter() implementations.
*
* It also invokes alter hooks for all base themes.
*
* $theme specifies the theme name of the active theme and all its base
* themes.
*
* This dispatch function hands off the passed-in variables to type-specific
* $theme_TYPE_alter() implementations in the active theme. It ensures a
* consistent interface for all altering operations.
*
* A maximum of 2 alterable arguments is supported. In case more arguments
* need to be passed and alterable, modules provide additional variables
* assigned by reference in the last $context argument:
* @code
* $context = array(
* 'alterable' => &$alterable,
* 'unalterable' => $unalterable,
* 'foo' => 'bar',
* );
* $this->alter('mymodule_data', $alterable1, $alterable2, $context);
* @endcode
*
* Note that objects are always passed by reference in PHP5. If it is
* absolutely required that no implementation alters a passed object in
* $context, then an object needs to be cloned:
* @code
* $context = array(
* 'unalterable_object' => clone $object,
* );
* $this->alter('mymodule_data', $data, $context);
* @endcode
*
* @param string|array $type
* A string describing the type of the alterable $data. 'form', 'links',
* 'node_content', and so on are several examples. Alternatively can be an
* array, in which case $theme_TYPE_alter() is invoked for each value in the
* array. When Form API is using $this->alter() to
* execute both $theme_form_alter() and $theme_form_FORM_ID_alter()
* implementations, it passes array('form', 'form_' . $form_id) for $type.
* @param mixed $data
* The variable that will be passed to $theme_TYPE_alter() implementations
* to be altered. The type of this variable depends on the value of the
* $type argument. For example, when altering a 'form', $data will be a
* structured array. When altering a 'profile', $data will be an object.
* @param mixed $context1
* (optional) An additional variable that is passed by reference.
* @param mixed $context2
* (optional) An additional variable that is passed by reference. If more
* context needs to be provided to implementations, then this should be an
* associative array as described above.
* Execute the alter hook on the current theme.
*
* @see \Drupal\Core\Extension\ModuleHandlerInterface
*/
public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL);
/**
* Provides an alter hook for a specific theme.
*
* Similar to ::alter, it also invokes the alter hooks for the base themes.
*
* @param \Drupal\Core\Theme\ActiveTheme $theme
* A manually specified theme.
* @param string|array $type
* A string describing the type of the alterable $data.
* @param mixed $data
* The variable that will be passed to $theme_TYPE_alter() implementations
* @param mixed $context1
* (optional) An additional variable that is passed by reference.
* @param mixed $context2
* (optional) An additional variable that is passed by reference.
*
* @see ::alter
*/
public function alterForTheme(ActiveTheme $theme, $type, &$data, &$context1 = NULL, &$context2 = NULL);
}

View file

@ -0,0 +1,111 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeNegotiator.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Provides a class which determines the active theme of the page.
*
* It therefore uses ThemeNegotiatorInterface objects which are passed in
* using the 'theme_negotiator' tag.
*
* @see \Drupal\Core\Theme\ThemeNegotiatorPass
* @see \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
class ThemeNegotiator implements ThemeNegotiatorInterface {
/**
* Holds arrays of theme negotiators, keyed by priority.
*
* @var array
*/
protected $negotiators = array();
/**
* Holds the array of theme negotiators sorted by priority.
*
* Set to NULL if the array needs to be re-calculated.
*
* @var array|NULL
*/
protected $sortedNegotiators;
/**
* The access checker for themes.
*
* @var \Drupal\Core\Theme\ThemeAccessCheck
*/
protected $themeAccess;
/**
* Constructs a new ThemeNegotiator.
*
* @param \Drupal\Core\Theme\ThemeAccessCheck $theme_access
* The access checker for themes.
*/
public function __construct(ThemeAccessCheck $theme_access) {
$this->themeAccess = $theme_access;
}
/**
* Adds a active theme negotiation service.
*
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $negotiator
* The theme negotiator to add.
* @param int $priority
* Priority of the theme negotiator.
*/
public function addNegotiator(ThemeNegotiatorInterface $negotiator, $priority) {
$this->negotiators[$priority][] = $negotiator;
// Force the negotiators to be re-sorted.
$this->sortedNegotiators = NULL;
}
/**
* Returns the sorted array of theme negotiators.
*
* @return array|\Drupal\Core\Theme\ThemeNegotiatorInterface[]
* An array of theme negotiator objects.
*/
protected function getSortedNegotiators() {
if (!isset($this->sortedNegotiators)) {
// Sort the negotiators according to priority.
krsort($this->negotiators);
// Merge nested negotiators from $this->negotiators into
// $this->sortedNegotiators.
$this->sortedNegotiators = array();
foreach ($this->negotiators as $builders) {
$this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders);
}
}
return $this->sortedNegotiators;
}
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
foreach ($this->getSortedNegotiators() as $negotiator) {
if ($negotiator->applies($route_match)) {
$theme = $negotiator->determineActiveTheme($route_match);
if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
return $theme;
}
}
}
}
}

View file

@ -0,0 +1,53 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeNegotiatorInterface.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Defines an interface for classes which determine the active theme.
*
* To set the active theme, create a new service tagged with 'theme_negotiator'
* (see user.services.yml for an example). The only method this service needs
* to implement is determineActiveTheme. Return the name of the theme, or NULL
* if other negotiators like the configured default one should kick in instead.
*
* If you are setting a theme which is closely tied to the functionality of a
* particular page or set of pages (such that the page might not function
* correctly if a different theme is used), make sure to set the priority on
* the service to a high number so that it is not accidentally overridden by
* other theme negotiators. By convention, a priority of "1000" is used in
* these cases; see \Drupal\Core\Theme\AjaxBasePageNegotiator and
* core.services.yml for an example.
*/
interface ThemeNegotiatorInterface {
/**
* Whether this theme negotiator should be used to set the theme.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match object.
*
* @return bool
* TRUE if this negotiator should be used or FALSE to let other negotiators
* decide.
*/
public function applies(RouteMatchInterface $route_match);
/**
* Determine the active theme for the request.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match object.
*
* @return string|null
* Returns the active theme name, else return NULL.
*/
public function determineActiveTheme(RouteMatchInterface $route_match);
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeSettings.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Config\ConfigBase;
/**
* Provides a configuration API wrapper for runtime merged theme settings.
*
* Theme settings use configuration for base values but the runtime theme
* settings are calculated based on various site settings and are therefore
* not persisted.
*
* @see theme_get_setting()
*/
class ThemeSettings extends ConfigBase {
/**
* The theme of the theme settings object.
*
* @var string
*/
protected $theme;
/**
* Constructs a theme settings object.
*
* @param string $theme
* The name of the theme settings object being constructed.
*/
public function __construct($theme) {
$this->theme = $theme;
}
/**
* Returns the theme of this theme settings object.
*
* @return string
* The theme of this theme settings object.
*/
public function getTheme() {
return $this->theme;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return ['rendered'];
}
}