diff --git a/core/includes/common.inc b/core/includes/common.inc index 10c8cf5..92e0355 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2240,7 +2240,7 @@ function l($text, $path, array $options = array()) { // rendering. if (config('system.performance')->get('theme_link')) { drupal_theme_initialize(); - $registry = theme_get_registry(FALSE); + $registry = drupal_container()->get('theme.registry')->getRuntime(); // We don't want to duplicate functionality that's in theme(), so any // hint of a module or theme doing anything at all special with the 'link' // theme hook should simply result in theme() being called. This includes diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 99b50ee..a766cf3 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -271,27 +271,6 @@ function _drupal_theme_initialize($theme, $base_theme = array()) { } /** - * Gets the theme registry. - * - * @param bool $complete - * Optional boolean to indicate whether to return the complete theme registry - * array or an instance of the Drupal\Core\Utility\ThemeRegistry class. - * If TRUE, the complete theme registry array will be returned. This is useful - * if you want to foreach over the whole registry, use array_* functions or - * inspect it in a debugger. If FALSE, an instance of the - * Drupal\Core\Utility\ThemeRegistry class will be returned, this provides an - * ArrayObject which allows it to be accessed with array syntax and isset(), - * and should be more lightweight than the full registry. Defaults to TRUE. - * - * @return - * The complete theme registry array, or an instance of the - * Drupal\Core\Utility\ThemeRegistry class. - */ -function theme_get_registry($complete = TRUE) { - return drupal_container()->get('theme.registry')->get($complete); -} - -/** * Forces the system to rebuild the theme registry. * * This function should be called when modules are added to the system, or when @@ -585,7 +564,7 @@ function theme($hook, $variables = array()) { throw new Exception(t('theme() may not be called until all modules are loaded.')); } - $hooks = theme_get_registry(FALSE); + $hooks = drupal_container()->get('theme.registry')->getRuntime(); // If an array of hook candidates were passed, use the first one that has an // implementation. @@ -718,6 +697,11 @@ function theme($hook, $variables = array()) { } foreach (array_reverse($suggestions) as $suggestion) { if (isset($hooks[$suggestion])) { + // A theme hook suggestion is essentially the reverse of a base hook + // definition; instead of finding the generic parent, we find a more + // specific child. The registry ensures to merge the base hook info into + // the suggestion's info. + // @see \Drupal\Core\Theme\Registry::build() $info = $hooks[$suggestion]; break; } diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index ead6d7c..46a76e8 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -119,6 +119,7 @@ public function build(ContainerBuilder $container) { ->addArgument(new Reference('database')) ->addArgument(new Reference('lock')); + // @todo Allow to use MemoryBackend as default in 'dev' environment. $container ->register('cache.theme', 'Drupal\Core\Cache\CacheBackendInterface') ->setFactoryClass('Drupal\Core\Cache\CacheFactory') diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 97c5f09..5ecc8a0 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -82,27 +82,40 @@ class Registry { protected $registry; /** + * The cache backend to use for the complete theme registry data. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cache; + + /** * The incomplete, runtime theme registry. * * @var \Drupal\Core\Utility\ThemeRegistry */ protected $runtimeRegistry; - protected $cache; - /** * Constructs a new theme registry instance. * + * @param \Drupal\Core\Cache\CacheBackendInterface $cache + * The cache backend interface to use for the complete theme registry data. + * @param string $theme_name + * (optional) The name of the theme for which to construct the registry. + * * @todo Inject new ModuleHandler. */ - public function __construct(CacheBackendInterface $cache) { + public function __construct(CacheBackendInterface $cache, $theme_name = NULL) { $this->cache = $cache; - $this->init(); + $this->init($theme_name); } /** * DIE. * + * @param string $theme_name + * (optional) The name of the theme for which to construct the registry. + * * @global object $theme_info * An object with (at least) the following information: * - uri: The path to the theme. @@ -126,62 +139,79 @@ public function __construct(CacheBackendInterface $cache) { * - $theme->requires == should contain the full stack of dependencies, * in the correct order. */ - public function init() { - // #1: The theme registry might get instantiated before the theme was - // initialized. Cope with that. - if (!isset($GLOBALS['theme_info'])) { - unset($this->runtimeRegistry); - unset($this->registry); - drupal_theme_initialize(); + public function init($theme_name = NULL) { + // Unless instantiated for a specific theme, use globals. + if (!isset($theme_name)) { + // #1: The theme registry might get instantiated before the theme was + // initialized. Cope with that. + if (!isset($GLOBALS['theme_info'])) { + unset($this->runtimeRegistry); + unset($this->registry); + drupal_theme_initialize(); + } + // #2: The testing framework only cares for the global $theme variable at + // this point. Cope with that. + if ($GLOBALS['theme'] != $GLOBALS['theme_info']->name) { + unset($this->runtimeRegistry); + unset($this->registry); + drupal_theme_initialize(); + } + $this->theme = $GLOBALS['theme_info']; + $this->base_themes = $GLOBALS['base_theme_info']; + $this->engine = $GLOBALS['theme_engine']; } - // #2: The testing framework only cares for the global $theme variable at - // this point. Cope with that. - if ($GLOBALS['theme'] != $GLOBALS['theme_info']->name) { - unset($this->runtimeRegistry); - unset($this->registry); - drupal_theme_initialize(); + // Instead of the global theme, a specific theme was requested. + else { + // @see drupal_theme_initialize() + $themes = list_themes(); + $this->theme = $themes[$theme_name]; + + // Find all base themes. + $this->base_themes = array(); + $ancestor = $theme; + while ($ancestor && isset($themes[$ancestor]->base_theme)) { + $ancestor = $themes[$ancestor]->base_theme; + $this->base_themes[] = $themes[$ancestor]; + if (!empty($themes[$ancestor]->owner)) { + include_once DRUPAL_ROOT . '/' . $themes[$ancestor]->owner; + } + } + $this->base_themes = array_reverse($base_themes); + + // @see _drupal_theme_initialize() + if (isset($theme->engine)) { + $this->engine = $theme->engine; + include_once DRUPAL_ROOT . '/' . $theme->owner; + if (function_exists($theme->engine . '_init')) { + foreach ($this->base_themes as $base) { + call_user_func($theme->engine . '_init', $base); + } + call_user_func($theme->engine . '_init', $theme); + } + } } - $this->theme = $GLOBALS['theme_info']; - $this->base_themes = $GLOBALS['base_theme_info']; - $this->engine = $GLOBALS['theme_engine']; } /** * Returns the complete theme registry. * - * @param bool $complete - * Optional boolean to indicate whether to return the complete theme registry - * array or an instance of the Drupal\Core\Utility\ThemeRegistry class. - * If TRUE, the complete theme registry array will be returned. This is useful - * if you want to foreach over the whole registry, use array_* functions or - * inspect it in a debugger. If FALSE, an instance of the - * Drupal\Core\Utility\ThemeRegistry class will be returned, this provides an - * ArrayObject which allows it to be accessed with array syntax and isset(), - * and should be more lightweight than the full registry. Defaults to TRUE. - * - * @return array|\Drupal\Core\Utility\ThemeRegistry - * The complete theme registry array, or an instance of the - * Drupal\Core\Utility\ThemeRegistry class. + * @return array + * The complete theme registry data array. * - * @todo Refactor calling code to remove $complete parameter. + * @see Registry::$registry */ - public function get($complete = TRUE) { - if ($complete) { - if (!isset($this->registry)) { - $this->registry = $this->load(); - } - return $this->registry; - } - else { - return $this->getRuntime(); + public function get() { + if (!isset($this->registry)) { + $this->registry = $this->load(); } + return $this->registry; } /** * Returns the incomplete, runtime theme registry. * * @return \Drupal\Core\Utility\ThemeRegistry - * An shared instance of the ThemeRegistry class, provides an ArrayObject + * 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. */ @@ -195,19 +225,10 @@ public function getRuntime() { /** * Loads the theme registry from cache or rebuilds it. * - * @param $complete - * Whether to load the complete theme registry or an instance of the - * Drupal\Core\Utility\ThemeRegistry class. - * * @return array - * The complete theme registry array. - * - * @todo Remove $complete argument. @see ::get() + * The complete theme registry data array. */ - public function load($complete = TRUE) { - if (!$complete) { - return $this->getRuntime(); - } + public function load() { // Check the theme registry cache; if it exists, use it. if ($cache = $this->cache->get('theme_registry:' . $this->theme->name)) { $registry = $cache->data; @@ -266,8 +287,8 @@ public function build() { $registry = array(); // hook_theme() implementations of modules are always the same. - if ($cached = $this->cache->get('theme_registry:build:modules')) { - $registry = $cached->data; + if ($cache = $this->cache->get('theme_registry:build:modules')) { + $registry = $cache->data; } else { foreach (module_implements('theme') as $module) { @@ -314,13 +335,21 @@ public function build() { } } if ($pos !== FALSE && $base_hook !== $hook) { + // If a base hook is found, use it as base. (Pre)processor functions + // of the hook suggestion are appended, since the hook suggestion is + // more specific, by design. Any other info of this hook overrides the + // base hook. + $info = NestedArray::mergeDeep($registry[$base_hook], $info); $info['base hook'] = $base_hook; - unset($info['exists']); } else { + // If no base hook was found, then this is a suggestion for a theme + // hook of another extension that is not enabled. unset($registry[$hook]); } } + // Clean up. + unset($info['exists']); } return $registry; } @@ -366,7 +395,9 @@ public function processExtension(&$registry, $type, $name, $theme_name, $theme_p unset($info['file']); } - // An actual theme hook must define either 'variables' or a 'render element'. + // An actual/original theme hook must define either 'variables' or a + // 'render element', in which case we need to assign default values for + // 'template' or 'function'. if (isset($info['variables']) || isset($info['render element'])) { $info['exists'] = TRUE; @@ -384,7 +415,8 @@ public function processExtension(&$registry, $type, $name, $theme_name, $theme_p $info['path'] = $theme_path . '/templates'; } // Find the preferred theme engine for this module template. - // @todo Remove and make Twig the default engine. + // @todo Remove this hack. Simply support multiple theme engines; + // which will simplify the entire processing in the first place. if ($type == 'module') { $render_engines = array( '.twig' => 'twig', @@ -415,42 +447,58 @@ public function processExtension(&$registry, $type, $name, $theme_name, $theme_p } // If no 'variables' or 'render element' was defined, then this hook // definition extends an existing, or defines data for a hook suggestion. - // Data for hook suggestions requires a full registry in order to check - // for base hooks; therefore it happens after per-extension processing as - // last step in Registry::build(). else { + // Data for hook suggestions requires a full registry in order to check + // for base hooks, since suggestions are extending hooks horizontally + // (instead of overriding vertically); therefore it happens after + // per-extension processing. + // @see Registry::build() + // Revert the above theme engine hack for Twig, if the actual theme // engine returns a template. - // @todo What an insane hack is this? Simply support multiple theme engines. + // @todo Remove the above hack. Simply support multiple theme engines; + // which will simplify the entire processing in the first place. if ($type == 'theme_engine' && isset($info['template'])) { unset($registry[$hook]['template_file']); } } - // If a template should be used, inject the default template_preprocess(). + // Inject special preprocess functions in their required order. + // @see theme() // Also applies if the default implementation is a function, but a theme // overrides it to a template. - if (isset($info['template']) && !isset($registry[$hook]['preprocess'])) { - $registry[$hook] += array('preprocess' => array()); - - // Prepend global hook_preprocess() of modules. - $registry[$hook]['preprocess'] = array_merge($this->getHookImplementations('preprocess'), $registry[$hook]['preprocess']); - - // Prepend the base template_preprocess(). - array_unshift($registry[$hook]['preprocess'], 'template_preprocess'); - - // Once more for hook_process(), but only add the key if any enabled - // modules implement it, since there is no base template_process(). - if ($implementations = $this->getHookImplementations('process')) { - $registry[$hook] += array('process' => array()); - $registry[$hook]['process'] = array_merge($implementations, $registry[$hook]['process']); + if (!empty($info['exists'])) { + foreach (array('preprocess', 'process') as $phase) { + // 1) The base template_(pre)process(). + $functions = array(); + if (function_exists("template_{$phase}")) { + $functions[] = "template_{$phase}"; + } + // 2) The original hook owners' template_(pre)process_HOOK(), if + // registered in $info. + if (isset($info[$phase]) && in_array("template_{$phase}_{$hook}", $info[$phase])) { + $functions[] = "template_{$phase}_{$hook}"; + // Remove it from $info, as the merge would duplicate it otherwise. + $info[$phase] = array_diff($info[$phase], array("template_{$phase}_{$hook}")); + } + // 3) Global hook_(pre)process() of all modules. + $functions = array_merge($functions, $this->getHookImplementations($phase)); + // 4) hook_(pre)process_HOOK() implementations and any other preprocess + // hooks, in the vertical processing order of this build mechanism, as + // defined in $info. These do not require manual processing. + + // Prepend the (pre)process functions, unless empty. + if (!empty($functions)) { + $registry[$hook] += array($phase => array()); + $registry[$hook][$phase] = array_merge($functions, $registry[$hook][$phase]); + } } } // Merge this extension's theme hook definition into the existing. $registry[$hook] = NestedArray::mergeDeep($registry[$hook], $info); - // Allow themes and theme engines to remove all (pre)process functions. + // Themes and theme engines can force-remove all preprocess functions. // @see hook_theme() if (!empty($info['no preprocess'])) { unset($registry[$hook]['preprocess']); @@ -477,21 +525,9 @@ protected function getHookImplementations($hook) { } /** - * Invalidates theme registry caches and rebuilds the theme registry. - * - * @return array - * The complete, rebuilt theme registry. - */ - public function rebuild() { - $this->reset(); - return $this->get(); - } - - /** - * Forces the system to rebuild the theme registry. + * Invalidates theme registry caches. * - * This function should be called when modules are added to the system, or when - * a dynamic system needs to add more theme hooks. + * To be called when the list of enabled extensions is changed. */ public function reset() { unset($this->runtimeRegistry); diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php index ce4de7f..7a2168d 100644 --- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php +++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php @@ -75,7 +75,7 @@ function __construct($cid, $bin, $tags) { */ function initializeRegistry() { // @todo DIC this. - $this->completeRegistry = theme_get_registry(); + $this->completeRegistry = drupal_container()->get('theme.registry')->get(); return array_fill_keys(array_keys($this->completeRegistry), NULL); } @@ -112,7 +112,7 @@ public function offsetGet($offset) { public function resolveCacheMiss($offset) { // @todo DIC this. if (!isset($this->completeRegistry)) { - $this->completeRegistry = theme_get_registry(); + $this->completeRegistry = drupal_container()->get('theme.registry')->get(); } $this->storage[$offset] = $this->completeRegistry[$offset]; if ($this->persistable) { diff --git a/core/modules/block/block.module b/core/modules/block/block.module index c75e0c0..bc8d72f 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -86,7 +86,7 @@ function block_theme() { 'block' => array( 'render element' => 'elements', 'preprocess' => array('template_preprocess_block'), - 'template' => 'block', + 'template' => 'block', ), 'block_admin_display_form' => array( 'template' => 'block-admin-display-form', diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module index bbb00e8..3049547 100644 --- a/core/modules/contextual/contextual.module +++ b/core/modules/contextual/contextual.module @@ -92,7 +92,7 @@ function contextual_preprocess(&$variables, $hook) { return; } - $hooks = theme_get_registry(FALSE); + $hooks = drupal_container()->get('theme.registry')->getRuntime(); // Determine the primary theme function argument. if (!empty($hooks[$hook]['variables'])) { diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php index 1ad6fa1..9bf4502 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php @@ -208,7 +208,7 @@ function testClassLoading() { * Tests drupal_find_theme_templates(). */ public function testFindThemeTemplates() { - $registry = theme_get_registry(); + $registry = $this->container->get('theme.registry')->get(); $templates = drupal_find_theme_templates($registry, '.tpl.php', drupal_get_path('theme', 'test_theme')); $this->assertEqual($templates['node__1']['template'], 'node--1', 'Template node--1.tpl.php was found in test_theme.'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTestTwig.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTestTwig.php index 5abb4bc..aa516bd 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTestTwig.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTestTwig.php @@ -47,7 +47,7 @@ function testTemplateOverride() { * Tests drupal_find_theme_templates */ function testFindThemeTemplates() { - $cache = theme_get_registry(); + $cache = $this->container->get('theme.registry')->get(); // Check for correct content // @todo Remove this tests once double engine code is removed diff --git a/core/modules/system/system.module b/core/modules/system/system.module index d778218..dcc21e5 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -188,6 +188,7 @@ function system_theme() { 'render element' => 'form', ), 'system_plugin_ui_form' => array( + 'preprocess' => array('template_preprocess_system_plugin_ui_form'), 'template' => 'system-plugin-ui-form', 'render element' => 'form', ), diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module index d5f8cc6..945308d 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -15,12 +15,15 @@ function theme_test_theme($existing, $type, $theme, $path) { $items['theme_test_preprocess'] = array( 'file' => 'theme_test.inc', 'variables' => array('foo' => ''), + 'preprocess' => array('template_preprocess_theme_test_preprocess'), ); $items['theme_test_template_test'] = array( 'template' => 'theme_test.template_test', + 'variables' => array(), ); $items['theme_test_template_test_2'] = array( 'template' => 'theme_test.template_test', + 'variables' => array(), ); $items['theme_test_foo'] = array( 'variables' => array('foo' => NULL), diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php index c5a64f9..2e88f02 100644 --- a/core/modules/system/theme.api.php +++ b/core/modules/system/theme.api.php @@ -118,7 +118,7 @@ function hook_preprocess(&$variables, $hook) { } if (!isset($hooks)) { - $hooks = theme_get_registry(FALSE); + $hooks = drupal_container()->get('theme.registry')->getRuntime(); } // Determine the primary theme function argument. diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php index 6851f70..4e0bf8e 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php @@ -9,7 +9,7 @@ use Drupal\Core\Theme\Registry; use Drupal\views\ViewExecutable; -use \Drupal\views\Plugin\views\PluginBase; +use Drupal\views\Plugin\views\PluginBase; /** * @defgroup views_display_plugins Views display plugins @@ -1719,54 +1719,19 @@ public function buildOptionsForm(&$form, &$form_state) { } if (isset($GLOBALS['theme']) && $GLOBALS['theme'] == $this->theme) { - $this->theme_registry = theme_get_registry(); + $this->theme_registry = drupal_container()->get('theme.registry')->get(); $theme_engine = $GLOBALS['theme_engine']; } else { $themes = list_themes(); $theme = $themes[$this->theme]; - - // Find all our ancestor themes and put them in an array. - $base_theme = array(); - $ancestor = $this->theme; - while ($ancestor && isset($themes[$ancestor]->base_theme)) { - $ancestor = $themes[$ancestor]->base_theme; - $base_theme[] = $themes[$ancestor]; - } - - // The base themes should be initialized in the right order. - $base_theme = array_reverse($base_theme); - - // This code is copied directly from _drupal_theme_initialize() + // @see _drupal_theme_initialize() $theme_engine = NULL; - - // Initialize the theme. if (isset($theme->engine)) { - // Include the engine. - include_once DRUPAL_ROOT . '/' . $theme->owner; - $theme_engine = $theme->engine; - if (function_exists($theme_engine . '_init')) { - foreach ($base_theme as $base) { - call_user_func($theme_engine . '_init', $base); - } - call_user_func($theme_engine . '_init', $theme); - } - } - else { - // include non-engine theme files - foreach ($base_theme as $base) { - // Include the theme file or the engine. - if (!empty($base->owner)) { - include_once DRUPAL_ROOT . '/' . $base->owner; - } - } - // and our theme gets one too. - if (!empty($theme->owner)) { - include_once DRUPAL_ROOT . '/' . $theme->owner; - } } - $this->theme_registry = new Registry($theme, $base_theme, $theme_engine); + $cache_theme = drupal_container()->get('cache.theme'); + $this->theme_registry = new Registry($cache_theme, $theme->name); } // If there's a theme engine involved, we also need to know its extension diff --git a/core/modules/views/views_ui/admin.inc b/core/modules/views/views_ui/admin.inc index 06881a9..ef9fc38 100644 --- a/core/modules/views/views_ui/admin.inc +++ b/core/modules/views/views_ui/admin.inc @@ -2037,16 +2037,9 @@ function views_ui_tools_clear_cache() { * a templates rescan). */ function views_ui_config_item_form_rescan($form, &$form_state) { - drupal_theme_rebuild(); - // The 'Theme: Information' page is about to be shown again. That page - // analyzes the output of theme_get_registry(). However, this latter - // function uses an internal cache (which was initialized before we - // called drupal_theme_rebuild()) so it won't reflect the - // current state of our theme registry. The only way to clear that cache - // is to re-initialize the theme system: - unset($GLOBALS['theme']); - drupal_theme_initialize(); + // analyzes the data of the theme registry. + drupal_container()->get('theme.registry')->reset(); $form_state['rerender'] = TRUE; $form_state['rebuild'] = TRUE;