Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
20.00% |
3 / 15 |
CRAP | |
58.67% |
115 / 196 |
Registry | |
0.00% |
0 / 1 |
|
20.00% |
3 / 15 |
775.86 | |
58.67% |
115 / 196 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
8 / 8 |
|||
setThemeManager | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
init | |
0.00% |
0 / 1 |
3.71 | |
57.14% |
4 / 7 |
|||
get | |
0.00% |
0 / 1 |
4.59 | |
66.67% |
6 / 9 |
|||
getRuntime | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
setCache | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getBaseHook | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 9 |
|||
build | |
0.00% |
0 / 1 |
10.44 | |
73.91% |
17 / 23 |
|||
processExtension | |
0.00% |
0 / 1 |
53.27 | |
75.38% |
49 / 65 |
|||
completeSuggestion | |
0.00% |
0 / 1 |
132 | |
0.00% |
0 / 16 |
|||
postProcessExtension | |
0.00% |
0 / 1 |
33.55 | |
65.71% |
23 / 35 |
|||
reset | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 6 |
|||
destruct | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
getPrefixGroupedUserFunctions | |
100.00% |
1 / 1 |
2 | |
100.00% |
6 / 6 |
|||
getPath | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
<?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 array of theme registries, keyed by the theme name. Each registry is | |
* 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 \Drupal\Core\Theme\ThemeManagerInterface::render(), 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 \Drupal\Core\Theme\ThemeManagerInterface::render(). | |
* - 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; | |
/** | |
* An array of incomplete, runtime theme registries, keyed by theme name. | |
* | |
* @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[$this->theme->getName()])) { | |
return $this->registry[$this->theme->getName()]; | |
} | |
if ($cache = $this->cache->get('theme_registry:' . $this->theme->getName())) { | |
$this->registry[$this->theme->getName()] = $cache->data; | |
} | |
else { | |
$this->build(); | |
// Only persist it if all modules are loaded to ensure it is complete. | |
if ($this->moduleHandler->isLoaded()) { | |
$this->setCache(); | |
} | |
} | |
return $this->registry[$this->theme->getName()]; | |
} | |
/** | |
* 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->theme->getName()])) { | |
$this->runtimeRegistry[$this->theme->getName()] = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->cache, $this->lock, array('theme_registry'), $this->moduleHandler->isLoaded()); | |
} | |
return $this->runtimeRegistry[$this->theme->getName()]; | |
} | |
/** | |
* Persists the theme registry in the cache backend. | |
*/ | |
protected function setCache() { | |
$this->cache->set('theme_registry:' . $this->theme->getName(), $this->registry[$this->theme->getName()], 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()); | |
} | |
// Hooks provided by the theme itself. | |
$this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath()); | |
// Discover and add all preprocess functions for theme hook suggestions. | |
$this->postProcessExtension($cache, $this->theme); | |
// 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[$this->theme->getName()] = $cache; | |
return $this->registry[$this->theme->getName()]; | |
} | |
/** | |
* 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. | |
* - See the @link themeable Theme system overview topic @endlink 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($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 a theme hook has a base hook, mark its preprocess functions always | |
// incomplete in order to inherit the base hook's preprocess functions. | |
if (!empty($result[$hook]['base hook'])) { | |
$result[$hook]['incomplete preprocess functions'] = TRUE; | |
} | |
if (isset($cache[$hook]['includes'])) { | |
$result[$hook]['includes'] = $cache[$hook]['includes']; | |
} | |
// Load the includes, as they may contain preprocess functions. | |
if (isset($info['includes'])) { | |
foreach ($info['includes'] as $include_file) { | |
include_once $this->root . '/' . $include_file; | |
} | |
} | |
// 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'])) { | |
if (!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 the @defgroup themeable | |
// topic. | |
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 the @defgroup themeable topic. | |
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; | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Completes the definition of the requested suggestion hook. | |
* | |
* @param string $hook | |
* The name of the suggestion hook to complete. | |
* @param array $cache | |
* The theme registry, as documented in | |
* \Drupal\Core\Theme\Registry::processExtension(). | |
*/ | |
protected function completeSuggestion($hook, array &$cache) { | |
$previous_hook = $hook; | |
$incomplete_previous_hook = array(); | |
while ((!isset($cache[$previous_hook]) || isset($cache[$previous_hook]['incomplete preprocess functions'])) | |
&& $pos = strrpos($previous_hook, '__')) { | |
if (isset($cache[$previous_hook]) && !$incomplete_previous_hook && isset($cache[$previous_hook]['incomplete preprocess functions'])) { | |
$incomplete_previous_hook = $cache[$previous_hook]; | |
unset($incomplete_previous_hook['incomplete preprocess functions']); | |
} | |
$previous_hook = substr($previous_hook, 0, $pos); | |
// If base hook exists clone of it for the preprocess function | |
// without a template. | |
// @see https://www.drupal.org/node/2457295 | |
if (isset($cache[$previous_hook]) && !isset($cache[$previous_hook]['incomplete preprocess functions'])) { | |
$cache[$hook] = $incomplete_previous_hook + $cache[$previous_hook]; | |
if (isset($incomplete_previous_hook['preprocess functions'])) { | |
$diff = array_diff($incomplete_previous_hook['preprocess functions'], $cache[$previous_hook]['preprocess functions']); | |
$cache[$hook]['preprocess functions'] = array_merge($cache[$previous_hook]['preprocess functions'], $diff); | |
} | |
// If a base hook isn't set, this is the actual base hook. | |
if (!isset($cache[$previous_hook]['base hook'])) { | |
$cache[$hook]['base hook'] = $previous_hook; | |
} | |
} | |
} | |
} | |
/** | |
* Completes the theme registry adding discovered functions and hooks. | |
* | |
* @param array $cache | |
* The theme registry as documented in | |
* \Drupal\Core\Theme\Registry::processExtension(). | |
* @param \Drupal\Core\Theme\ActiveTheme $theme | |
* Current active theme. | |
* | |
* @see ::processExtension() | |
*/ | |
protected function postProcessExtension(array &$cache, ActiveTheme $theme) { | |
$grouped_functions = $this->getPrefixGroupedUserFunctions(); | |
// Gather prefixes. This will be used to limit the found functions to the | |
// expected naming conventions. | |
$prefixes = array_keys((array) $this->moduleHandler->getModuleList()); | |
foreach (array_reverse($theme->getBaseThemes()) as $base) { | |
$prefixes[] = $base->getName(); | |
} | |
if ($theme->getEngine()) { | |
$prefixes[] = $theme->getEngine() . '_engine'; | |
} | |
$prefixes[] = $theme->getName(); | |
// Collect all variable preprocess functions in the correct order. | |
$suggestion_level = []; | |
$matches = []; | |
// Look for functions named according to the pattern and add them if they | |
// have matching hooks in the registry. | |
foreach ($prefixes as $prefix) { | |
// Grep only the functions which are within the prefix group. | |
list($first_prefix,) = explode('_', $prefix, 2); | |
if (!isset($grouped_functions[$first_prefix])) { | |
continue; | |
} | |
// Add the function and the name of the associated theme hook to the list | |
// of preprocess functions grouped by suggestion specificity if a matching | |
// base hook is found. | |
foreach ($grouped_functions[$first_prefix] as $candidate) { | |
if (preg_match("/^{$prefix}_preprocess_(((?:[^_]++|_(?!_))+)__.*)/", $candidate, $matches)) { | |
if (isset($cache[$matches[2]])) { | |
$level = substr_count($matches[1], '__'); | |
$suggestion_level[$level][$candidate] = $matches[1]; | |
} | |
} | |
} | |
} | |
// Add missing variable preprocessors. This is needed for modules that do | |
// not explicitly register the hook. For example, when a theme contains a | |
// variable preprocess function but it does not implement a template, it | |
// will go missing. This will add the expected function. It also allows | |
// modules or themes to have a variable process function based on a pattern | |
// even if the hook does not exist. | |
ksort($suggestion_level); | |
foreach ($suggestion_level as $level => $item) { | |
foreach ($item as $preprocessor => $hook) { | |
if (isset($cache[$hook]['preprocess functions']) && !in_array($hook, $cache[$hook]['preprocess functions'])) { | |
// Add missing preprocessor to existing hook. | |
$cache[$hook]['preprocess functions'][] = $preprocessor; | |
} | |
elseif (!isset($cache[$hook]) && strpos($hook, '__')) { | |
// Process non-existing hook and register it. | |
// Look for a previously defined hook that is either a less specific | |
// suggestion hook or the base hook. | |
$this->completeSuggestion($hook, $cache); | |
$cache[$hook]['preprocess functions'][] = $preprocessor; | |
} | |
} | |
} | |
// Inherit all base hook variable preprocess functions into suggestion | |
// hooks. This ensures that derivative hooks have a complete set of variable | |
// preprocess functions. | |
foreach ($cache as $hook => $info) { | |
// The 'base hook' is only applied to derivative hooks already registered | |
// from a pattern. This is typically set from | |
// drupal_find_theme_functions() and drupal_find_theme_templates(). | |
if (isset($info['incomplete preprocess functions'])) { | |
$this->completeSuggestion($hook, $cache); | |
unset($cache[$hook]['incomplete preprocess functions']); | |
} | |
// Optimize the registry. | |
if (isset($cache[$hook]['preprocess functions']) && empty($cache[$hook]['preprocess functions'])) { | |
unset($cache[$hook]['preprocess functions']); | |
} | |
// Ensure uniqueness. | |
if (isset($cache[$hook]['preprocess functions'])) { | |
$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. | |
foreach ($this->runtimeRegistry as $runtime_registry) { | |
$runtime_registry->clear(); | |
} | |
$this->runtimeRegistry = []; | |
$this->registry = []; | |
Cache::invalidateTags(array('theme_registry')); | |
return $this; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function destruct() { | |
foreach ($this->runtimeRegistry as $runtime_registry) { | |
$runtime_registry->destruct(); | |
} | |
} | |
/** | |
* Gets all user functions grouped by the word before the first underscore. | |
* | |
* @return array | |
* Functions grouped by the first prefix. | |
*/ | |
public function getPrefixGroupedUserFunctions() { | |
$functions = get_defined_functions(); | |
$grouped_functions = []; | |
// Splitting user defined functions into groups by the first prefix. | |
foreach ($functions['user'] as $function) { | |
list($first_prefix,) = explode('_', $function, 2); | |
$grouped_functions[$first_prefix][] = $function; | |
} | |
return $grouped_functions; | |
} | |
/** | |
* 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); | |
} | |
} |