diff --git a/core/includes/theme.inc b/core/includes/theme.inc index bab2af5..e16c8d2 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -10,6 +10,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Template\Attribute; +use Drupal\Core\Theme\Registry; use Drupal\Core\Utility\ThemeRegistry; /** @@ -136,10 +137,8 @@ function drupal_theme_initialize() { * the same information as the $theme object. It should be in * 'oldest first' order, meaning the top level of the chain will * be first. - * @param $registry_callback - * The callback to invoke to set the theme registry. */ -function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') { +function _drupal_theme_initialize($theme, $base_theme = array()) { global $theme_info, $base_theme_info, $theme_engine, $theme_path; $theme_info = $theme; $base_theme_info = $base_theme; @@ -266,15 +265,9 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb } } // Load twig as secondary always available engine. - // @todo Make twig the default engine and remove this. This is required - // because (by design) the theme system doesn't allow modules to register more - // than one type of extension. We need a temporary backwards compatibility - // layer to allow us to perform core-wide .tpl.php to .twig conversion. + // @todo Replace this hack with generic $theme->engine loading (including base + // themes). include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine'; - - if (isset($registry_callback)) { - _theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine)); - } } /** @@ -300,90 +293,13 @@ function theme_get_registry($complete = TRUE) { if (!isset($drupal_static_fast)) { $drupal_static_fast['registry'] = &drupal_static('theme_get_registry'); } - $theme_registry = &$drupal_static_fast['registry']; - - // Initialize the theme, if this is called early in the bootstrap, or after - // static variables have been reset. - if (!is_array($theme_registry)) { - drupal_theme_initialize(); - $theme_registry = array(); - } - - $key = (int) $complete; - - if (!isset($theme_registry[$key])) { - list($callback, $arguments) = _theme_registry_callback(); - if (!$complete) { - $arguments[] = FALSE; - } - $theme_registry[$key] = call_user_func_array($callback, $arguments); - } - - return $theme_registry[$key]; -} - -/** - * Sets the callback that will be used by theme_get_registry(). - * - * @param $callback - * The name of the callback function. - * @param $arguments - * The arguments to pass to the function. - */ -function _theme_registry_callback($callback = NULL, array $arguments = array()) { - static $stored; - if (isset($callback)) { - $stored = array($callback, $arguments); - } - return $stored; -} + $registry = &$drupal_static_fast['registry']; -/** - * Gets the theme_registry cache; if it doesn't exist, builds it. - * - * @param $theme - * The loaded $theme object as returned by list_themes(). - * @param $base_theme - * An array of loaded $theme objects representing the ancestor themes in - * oldest first order. - * @param $theme_engine - * The name of the theme engine. - * @param $complete - * Whether to load the complete theme registry or an instance of the - * Drupal\Core\Utility\ThemeRegistry class. - * - * @return - * The theme registry array, or an instance of the - * Drupal\Core\Utility\ThemeRegistry class. - */ -function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) { - if ($complete) { - // Check the theme registry cache; if it exists, use it. - $cached = cache()->get("theme_registry:$theme->name"); - if (isset($cached->data)) { - $registry = $cached->data; - } - else { - // If not, build one and cache it. - $registry = _theme_build_registry($theme, $base_theme, $theme_engine); - // Only persist this registry if all modules are loaded. This assures a - // complete set of theme hooks. - if (module_load_all(NULL)) { - _theme_save_registry($theme, $registry); - } - } - return $registry; - } - else { - return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache', array('theme_registry' => TRUE)); + // @todo DIC this. + if (!isset($registry)) { + $registry = new Registry($GLOBALS['theme_info'], $GLOBALS['base_theme_info'], $GLOBALS['theme_engine']); } -} - -/** - * Writes the theme_registry cache into the database. - */ -function _theme_save_registry($theme, $registry) { - cache()->set("theme_registry:$theme->name", $registry, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE)); + return $registry->get($complete); } /** @@ -391,6 +307,8 @@ function _theme_save_registry($theme, $registry) { * * This function should be called when modules are added to the system, or when * a dynamic system needs to add more theme hooks. + * + * @todo Remove this. */ function drupal_theme_rebuild() { drupal_static_reset('theme_get_registry'); @@ -398,290 +316,6 @@ function drupal_theme_rebuild() { } /** - * Process a single implementation of hook_theme(). - * - * @param $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. - * - 'process functions': See theme() for detailed documentation. - * @param $name - * The name of the module, theme engine, base theme engine, theme or base - * theme implementing hook_theme(). - * @param $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(). _theme_process_registry() 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 $theme - * The loaded $theme object as returned from list_themes(). - * @param $path - * The directory where $name is. For example, modules/system or - * themes/bartik. - * - * @see theme() - * @see _theme_process_registry() - * @see hook_theme() - * @see list_themes() - */ -function _theme_process_registry(&$cache, $name, $type, $theme, $path) { - $result = array(); - - // Processor functions work in two distinct phases with the process - // functions always being executed after the preprocess functions. - $variable_process_phases = array( - 'preprocess functions' => 'preprocess', - 'process functions' => 'process', - ); - - $hook_defaults = array( - 'variables' => TRUE, - 'render element' => TRUE, - 'pattern' => TRUE, - 'base hook' => TRUE, - ); - - // Invoke the hook_theme() implementation, process 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 function and file are omitted, default to standard naming - // conventions. - if (!isset($info['template']) && !isset($info['function'])) { - $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook; - } - - 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 DRUPAL_ROOT . '/' . $include_file; - $result[$hook]['includes'][] = $include_file; - } - - // 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); - } - - // The following apply only to theming hooks implemented as templates. - if (isset($info['template'])) { - // Prepend the current theming path when none is set. - if (!isset($info['path'])) { - $result[$hook]['template'] = $path . '/templates/' . $info['template']; - } - if ($type == 'module') { - // Add two render engines for modules. - // @todo Remove and make twig the default engine. - $render_engines = array( - '.twig' => 'twig', - '.tpl.php' => 'phptemplate' - ); - - // Find the best engine for this template. - foreach ($render_engines as $extension => $engine) { - // Render the output using the template file. - $template_file = $result[$hook]['template'] . $extension; - if (isset($info['path'])) { - $template_file = $info['path'] . '/' . $template_file; - } - if (file_exists($template_file)) { - $result[$hook]['template_file'] = $template_file; - $result[$hook]['engine'] = $engine; - break; - } - } - } - } - - // Allow variable processors for all theming hooks, whether the hook is - // implemented as a template or as a function. - foreach ($variable_process_phases as $phase_key => $phase) { - // Check for existing variable processors. Ensure arrayness. - if (!isset($info[$phase_key]) || !is_array($info[$phase_key])) { - $info[$phase_key] = array(); - $prefixes = array(); - if ($type == 'module') { - // Default variable processor prefix. - $prefixes[] = 'template'; - // Add all modules so they can intervene with their own variable - // processors. This allows them to provide variable processors even - // if they are not the owner of the current hook. - $prefixes += module_list(); - } - elseif ($type == 'theme_engine' || $type == 'base_theme_engine') { - // Theme engines get an extra set that come before the normally - // named variable processors. - $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 - // processors. - $prefixes[] = $name; - } - foreach ($prefixes as $prefix) { - // Only use non-hook-specific variable processors for theming hooks - // implemented as templates. See theme(). - if (isset($info['template']) && function_exists($prefix . '_' . $phase)) { - $info[$phase_key][] = $prefix . '_' . $phase; - } - if (function_exists($prefix . '_' . $phase . '_' . $hook)) { - $info[$phase_key][] = $prefix . '_' . $phase . '_' . $hook; - } - } - } - // Check for the override flag and prevent the cached variable - // processors from being used. This allows themes or theme engines to - // remove variable processors set earlier in the registry build. - if (!empty($info['override ' . $phase_key])) { - // Flag not needed inside the registry. - unset($result[$hook]['override ' . $phase_key]); - } - elseif (isset($cache[$hook][$phase_key]) && is_array($cache[$hook][$phase_key])) { - $info[$phase_key] = array_merge($cache[$hook][$phase_key], $info[$phase_key]); - } - $result[$hook][$phase_key] = $info[$phase_key]; - } - } - - // Merge the newly created theme hooks into the existing cache. - $cache = $result + $cache; - } - - // Let themes have variable processors 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])) { - foreach ($variable_process_phases as $phase_key => $phase) { - if (!isset($info[$phase_key])) { - $cache[$hook][$phase_key] = array(); - } - // Only use non-hook-specific variable processors for theming hooks - // implemented as templates. See theme(). - if (isset($info['template']) && function_exists($name . '_' . $phase)) { - $cache[$hook][$phase_key][] = $name . '_' . $phase; - } - if (function_exists($name . '_' . $phase . '_' . $hook)) { - $cache[$hook][$phase_key][] = $name . '_' . $phase . '_' . $hook; - $cache[$hook]['theme path'] = $path; - } - // Ensure uniqueness. - $cache[$hook][$phase_key] = array_unique($cache[$hook][$phase_key]); - } - } - } - } -} - -/** - * Builds the theme registry cache. - * - * @param $theme - * The loaded $theme object as returned by list_themes(). - * @param $base_theme - * An array of loaded $theme objects representing the ancestor themes in - * oldest first order. - * @param $theme_engine - * The name of the theme engine. - */ -function _theme_build_registry($theme, $base_theme, $theme_engine) { - $cache = array(); - // First, process 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 = cache()->get('theme_registry:build:modules')) { - $cache = $cached->data; - } - else { - foreach (module_implements('theme') as $module) { - _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); - } - // Only cache this registry if all modules are loaded. - if (module_load_all(NULL)) { - cache()->set("theme_registry:build:modules", $cache, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE)); - } - } - - // Process each base theme. - foreach ($base_theme as $base) { - // If the base theme uses a theme engine, process its hooks. - $base_path = dirname($base->filename); - if ($theme_engine) { - _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path); - } - _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path); - } - - // And then the same thing, but for the theme. - if ($theme_engine) { - _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename)); - } - - // Finally, hooks provided by the theme itself. - _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename)); - - // Let modules alter the registry. - drupal_alter('theme_registry', $cache); - - // Optimize the registry to not have empty arrays for functions. - foreach ($cache as $hook => $info) { - foreach (array('preprocess functions', 'process functions') as $phase) { - if (empty($info[$phase])) { - unset($cache[$hook][$phase]); - } - } - } - return $cache; -} - -/** * Returns a list of all currently available themes. * * Retrieved from the database, if available and the site is not in maintenance @@ -958,7 +592,7 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { function theme($hook, $variables = array()) { // 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 _theme_load_registry(). + // request properly. if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) { throw new Exception(t('theme() may not be called until all modules are loaded.')); } @@ -1058,22 +692,20 @@ function theme($hook, $variables = array()) { include_once DRUPAL_ROOT . '/' . $include_file; } } - if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) { + if (isset($base_hook_info['preprocess']) || isset($base_hook_info['process'])) { $variables['theme_hook_suggestion'] = $hook; $hook = $base_hook; $info = $base_hook_info; } } - if (isset($info['preprocess functions']) || isset($info['process functions'])) { + if (isset($info['preprocess']) || isset($info['process'])) { $variables['theme_hook_suggestions'] = array(); - foreach (array('preprocess functions', 'process functions') as $phase) { + foreach (array('preprocess', 'process') as $phase) { if (!empty($info[$phase])) { foreach ($info[$phase] as $processor_function) { - if (function_exists($processor_function)) { - // We don't want a poorly behaved process function changing $hook. - $hook_clone = $hook; - $processor_function($variables, $hook_clone); - } + // We don't want a poorly behaved process function changing $hook. + $hook_clone = $hook; + $processor_function($variables, $hook_clone); } } } @@ -1106,12 +738,10 @@ function theme($hook, $variables = array()) { // Generate the output using either a function or a template. $output = ''; - if (isset($info['function'])) { - if (function_exists($info['function'])) { - $output = $info['function']($variables); - } + if (!isset($info['template']) && isset($info['function'])) { + $output = $info['function']($variables); } - else { + elseif (isset($info['template'])) { // Default render function and extension. $render_function = 'theme_render_template'; $extension = '.tpl.php'; @@ -1135,32 +765,13 @@ function theme($hook, $variables = array()) { } } - // 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 to run template_preprocess() - // twice (it would be inefficient and mess up zebra striping), 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); - $variables += $default_template_variables; - } - // Render the output using the template file. - $template_file = $info['template'] . $extension; - if (isset($info['path'])) { - $template_file = $info['path'] . '/' . $template_file; - } - - // Modules can override this. if (isset($info['template_file'])) { $template_file = $info['template_file']; } + else { + $template_file = $info['path'] . '/' . $info['template'] . $extension; + } $output = $render_function($template_file, $variables); } @@ -3129,18 +2740,24 @@ function drupal_common_theme() { // From theme.inc. 'html' => array( 'render element' => 'page', + 'preprocess' => array('template_preprocess_html'), + 'process' => array('template_process_html'), 'template' => 'html', ), 'page' => array( 'render element' => 'page', + 'preprocess' => array('template_preprocess_page'), + 'process' => array('template_process_page'), 'template' => 'page', ), 'region' => array( 'render element' => 'elements', + 'preprocess' => array('template_preprocess_region'), 'template' => 'region', ), 'datetime' => array( 'variables' => array('timestamp' => NULL, 'text' => NULL, 'attributes' => array(), 'html' => FALSE), + 'preprocess' => array('template_preprocess_datetime'), 'template' => 'datetime', ), 'status_messages' => array( @@ -3190,6 +2807,7 @@ function drupal_common_theme() { ), 'item_list' => array( 'variables' => array('items' => array(), 'title' => '', 'type' => 'ul', 'attributes' => array()), + 'preprocess' => array('template_preprocess_item_list'), ), 'more_help_link' => array( 'variables' => array('url' => NULL), @@ -3212,6 +2830,8 @@ function drupal_common_theme() { // From theme.maintenance.inc. 'maintenance_page' => array( 'variables' => array('content' => NULL, 'show_messages' => TRUE), + 'preprocess' => array('template_preprocess_maintenance_page'), + 'process' => array('template_process_maintenance_page'), 'template' => 'maintenance-page', ), 'update_page' => array( @@ -3254,6 +2874,7 @@ function drupal_common_theme() { ), 'menu_tree' => array( 'render element' => 'tree', + 'preprocess' => array('template_preprocess_menu_tree'), ), 'menu_local_task' => array( 'render element' => 'element', @@ -3267,6 +2888,7 @@ function drupal_common_theme() { // From form.inc. 'input' => array( 'render element' => 'element', + 'preprocess' => array('template_preprocess_input'), ), 'select' => array( 'render element' => 'element', diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index ba88661..7baa164 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -90,9 +90,11 @@ function _drupal_maintenance_theme() { /** * Builds the registry when the site needs to bypass any database calls. + * + * @todo Remove this. */ function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) { - return _theme_build_registry($theme, $base_theme, $theme_engine); + return theme_get_registry(); } /** diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php new file mode 100644 index 0000000..ac2232f --- /dev/null +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -0,0 +1,641 @@ +registry. + */ +class Registry { + + /** + * The theme object representing the active theme for this registry. + * + * @var object + */ + protected $theme; + + /** + * An array of base theme objects. + * + * @var array + */ + protected $base_themes; + + /** + * The name of the theme engine of $theme. + * + * @var string + */ + protected $engine; + + /** + * 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 path to the extension denoted by 'type' and 'name'. + * @todo Remove 'theme path', it's useless. + * - 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 incomplete, runtime theme registry. + * + * @var \Drupal\Core\Utility\ThemeRegistry + */ + protected $runtimeRegistry; + + /** + * Constructs a new theme registry instance. + * + * @param object $theme + * An object with (at least) the following information: + * - uri: The path to the theme. + * - owner: The name of the theme's base theme. + * - engine: The name of the theme engine to use. + * @param array $base_themes + * (optional) An array of objects that represent the base themes of $theme, + * each having the same properties as $theme above, ordered by base theme + * hierarchy; i.e., the first element is the root of all themes. + * @param string $theme_engine + * The name of the theme engine. + * + * @todo Remove $theme_engine, duplicates ->engine. + * @todo Stack all themes into a single $themes in appropriate order. + */ + public function __construct($theme, array $base_themes = array(), $theme_engine) { + // @todo Inject Cache. + // @todo Inject new ModuleHandler. + $this->theme = $theme; + $this->base_themes = $base_themes; + $this->engine = $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. + * + * @todo Refactor calling code to remove $complete parameter. + */ + public function get($complete = TRUE) { + if ($complete) { + if (!isset($this->registry)) { + $this->registry = $this->load(); + } + return $this->registry; + } + else { + return $this->getRuntime(); + } + } + + /** + * Returns the incomplete, runtime theme registry. + * + * @return \Drupal\Core\Utility\ThemeRegistry + * An 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() { + if (!isset($this->runtimeRegistry)) { + $this->runtimeRegistry = new ThemeRegistry('theme_registry:runtime:' . $this->theme->name, 'cache', array('theme_registry' => TRUE)); + } + return $this->runtimeRegistry; + } + + /** + * 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() + */ + public function load($complete = TRUE) { + if (!$complete) { + return $this->getRuntime(); + } + // Check the theme registry cache; if it exists, use it. + $cached = cache()->get('theme_registry:' . $this->theme->name); + if (isset($cached->data)) { + $registry = $cached->data; + } + else { + // If not, build one and cache it. + $registry = $this->build(); + // Only persist this registry if all modules are loaded. This assures a + // complete set of theme hooks. + if (module_load_all(NULL)) { + $this->save($registry); + } + } + return $registry; + } + + /** + * Sets the theme registry cache. + * + * @param array $registry + * The complete theme registry. + */ + public function save($registry) { + cache()->set('theme_registry:' . $this->theme->name, $registry, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE)); + } + + /** + * Builds the theme registry from scratch. + * + * 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(). + * + * @see hook_theme() + * + * 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 theme() + * @see hook_theme_registry_alter() + */ + public function build() { + // @todo Replace local $registry variables in methods with $this->registry. + $registry = array(); + + // hook_theme() implementations of modules are always the same. + if ($cached = cache()->get('theme_registry:build:modules')) { + $registry = $cached->data; + } + else { + foreach (module_implements('theme') as $module) { + $this->processExtension($registry, 'module', $module, $module, drupal_get_path('module', $module)); + } + // Only cache this registry if all modules are loaded. + // @todo Get rid of these checks. + if (module_load_all(NULL)) { + cache()->set("theme_registry:build:modules", $registry, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE)); + } + } + + // Process each base theme. + foreach ($this->base_themes as $base) { + // If the base theme uses a theme engine, process its hooks. + $base_path = dirname($base->uri); + if ($this->engine) { + $this->processExtension($registry, 'base_theme_engine', $this->engine, $base->name, $base_path); + } + $this->processExtension($registry, 'base_theme', $base->name, $base->name, $base_path); + } + + // And then the same thing, but for the theme. + if ($this->engine) { + $this->processExtension($registry, 'theme_engine', $this->engine, $this->theme->name, dirname($this->theme->uri)); + } + // Finally, hooks provided by the theme itself. + $this->processExtension($registry, 'theme', $this->theme->name, $this->theme->name, dirname($this->theme->uri)); + + // Let modules alter the registry. + drupal_alter('theme_registry', $registry); + + // Map base hook suggestions and clean up unnecessary hooks. + foreach ($registry as $hook => &$info) { + if (empty($info['exists'])) { + // Determine whether this is a hook suggestion for an existing hook. + $base_hook = $hook; + // Iteratively strip everything after the last '__' delimiter, until an + // implementation is found. + while ($pos = strrpos($base_hook, '__')) { + $base_hook = substr($base_hook, 0, $pos); + if (isset($registry[$base_hook])) { + break; + } + } + if ($pos !== FALSE && $base_hook !== $hook) { + $info['base hook'] = $base_hook; + unset($info['exists']); + } + else { + unset($registry[$hook]); + } + } + } + return $registry; + } + + /** + * Process a single implementation of hook_theme(). + * + * @param array $registry + * The theme registry that will eventually be cached. + * @param string $type + * One of 'module', 'base_theme_engine', 'theme_engine', 'base_theme', or + * 'theme'. + * @param string $name + * The name of the extension implementing hook_theme(). + * @param string $theme_name + * The name of the extension for which theme hooks are currently processed. + * This equals $name for all extension types, except when $type is a theme + * engine, in which case $theme_name and $theme_path are pointing to the + * respective [base] theme the theme engine is associated with. + * @param string $theme_path + * The directory of $theme_name; e.g., 'core/modules/node' or + * 'themes/bartik'. + * + * @see theme() + * @see hook_theme() + */ + public function processExtension(&$registry, $type, $name, $theme_name, $theme_path) { + $function = $name . '_theme'; + foreach ($function($registry, $type, $theme_name, $theme_path) as $hook => $info) { + // Ensure this hook key exists; it will be set either way. + $registry += array($hook => array( + 'type' => $type, + 'name' => $name, + )); + + if (isset($info['file'])) { + $include_path = isset($info['path']) ? $info['path'] : $theme_path; + $info['includes'][] = $include_path . '/' . $info['file']; + unset($info['file']); + } + + // An actual theme hook must define either 'variables' or a 'render element'. + if (isset($info['variables']) || isset($info['render element'])) { + $info['exists'] = TRUE; + + // The effective path_to_theme() during theme(). + // @todo This variable is so horribly broken... remove it. + $info['theme path'] = $theme_path; + + if (isset($info['template'])) { + // A template implementation always takes precedence over functions. + // A potentially existing function pointer is obsolete. + unset($registry[$hook]['function']); + // Default the template path to the 'templates' directory of the + // extension, unless overridden. + if (!isset($info['path'])) { + $info['path'] = $theme_path . '/templates'; + } + // Find the preferred theme engine for this module template. + // @todo Remove and make Twig the default engine. + if ($type == 'module') { + $render_engines = array( + '.twig' => 'twig', + '.tpl.php' => 'phptemplate', + ); + foreach ($render_engines as $extension => $engine) { + // Render the output using the template file. + $template_file = $info['path'] . '/' . $info['template'] . $extension; + if (file_exists($template_file)) { + $info['template_file'] = $template_file; + $info['engine'] = $engine; + break; + } + } + } + } + // Otherwise, the implementation must be a function. However, functions + // do not need to be specified manually; the array key of the hook is + // expected to be taken over as function, unless overridden. + elseif (!isset($info['function'])) { + if ($type == 'module') { + $info['function'] = 'theme_' . $hook; + } + else { + $info['function'] = $name . '_' . $hook; + } + } + } + // 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 { + // 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. + if ($type == 'theme_engine' && isset($info['template'])) { + unset($registry[$hook]['template_file']); + } + } + + // If a template should be used, inject the default template_preprocess(). + // 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']); + } + } + + // 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. + // @see hook_theme() + if (!empty($info['no preprocess'])) { + unset($registry[$hook]['preprocess']); + unset($registry[$hook]['no preprocess']); + } + } + } + + /** + * Retrieves module hook implementations for a given theme hook name. + * + * @param string $hook + * The hook name to discover. + * + * @return array + * An array of module hook implementations; i.e., the actual function names. + */ + protected function getHookImplementations($hook) { + $implementations = module_implements($hook); + $add_suffix = function ($extension) use ($hook) { + return $extension . '_' . $hook; + }; + return array_map($add_suffix, $implementations); + } + + /** + * 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. + * + * This function should be called when modules are added to the system, or when + * a dynamic system needs to add more theme hooks. + */ + public function reset() { + unset($this->runtimeRegistry); + unset($this->registry); + cache()->invalidateTags(array('theme_registry' => TRUE)); + } + + /** + * Allows themes and/or theme engines to discover overridden theme functions. + * + * @param $cache + * The existing cache of theme hooks to test against. + * @param $prefixes + * An array of prefixes to test, in reverse order of importance. + * + * @return $implementations + * The functions found, suitable for returning from hook_theme(). + * + * @todo Currently unused. Convert this. + */ + public function findFunctions($cache, $prefixes) { + $implementations = array(); + $functions = get_defined_functions(); + + foreach ($cache as $hook => $info) { + foreach ($prefixes as $prefix) { + // Find theme functions that implement possible "suggestion" variants of + // registered theme hooks and add those as new registered theme hooks. + // The 'pattern' key defines a common prefix that all suggestions must + // start with. The default is the name of the hook followed by '__'. An + // 'base hook' key is added to each entry made for a found suggestion, + // so that common functionality can be implemented for all suggestions of + // the same base hook. To keep things simple, deep hierarchy of + // suggestions is not supported: each suggestion's 'base hook' key + // refers to a base hook, not to another suggestion, and all suggestions + // are found using the base hook's pattern, not a pattern from an + // intermediary suggestion. + $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); + if (!isset($info['base hook']) && !empty($pattern)) { + $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']); + if ($matches) { + foreach ($matches as $match) { + $new_hook = substr($match, strlen($prefix) + 1); + $arg_name = isset($info['variables']) ? 'variables' : 'render element'; + $implementations[$new_hook] = array( + 'function' => $match, + $arg_name => $info[$arg_name], + 'base hook' => $hook, + ); + } + } + } + // Find theme functions that implement registered theme hooks and include + // that in what is returned so that the registry knows that the theme has + // this implementation. + if (function_exists($prefix . '_' . $hook)) { + $implementations[$hook] = array( + 'function' => $prefix . '_' . $hook, + ); + } + } + } + return $implementations; + } + + /** + * Allows themes and/or theme engines to easily discover overridden templates. + * + * @param $cache + * The existing cache of theme hooks to test against. + * @param $extension + * The extension that these templates will have. + * @param $path + * The path to search. + * + * @todo Currently unused. Convert this. + */ + public function findTemplates($cache, $extension, $path) { + $implementations = array(); + + // Collect paths to all sub-themes grouped by base themes. These will be + // used for filtering. This allows base themes to have sub-themes in its + // folder hierarchy without affecting the base themes template discovery. + $theme_paths = array(); + foreach (list_themes() as $theme_info) { + if (!empty($theme_info->base_theme)) { + $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename); + } + } + foreach ($theme_paths as $basetheme => $subthemes) { + foreach ($subthemes as $subtheme => $subtheme_path) { + if (isset($theme_paths[$subtheme])) { + $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]); + } + } + } + global $theme; + $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array(); + + // Escape the periods in the extension. + $regex = '/' . str_replace('.', '\.', $extension) . '$/'; + // Get a listing of all template files in the path to search. + $files = file_scan_directory($path, $regex, array('key' => 'name')); + + // Find templates that implement registered theme hooks and include that in + // what is returned so that the registry knows that the theme has this + // implementation. + foreach ($files as $template => $file) { + // Ignore sub-theme templates for the current theme. + if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) { + continue; + } + // Chop off the remaining '.tpl' extension. $template already has the + // rightmost extension removed, but there might still be more, such as with + // .tpl.php, which still has .tpl in $template at this point. + if (($pos = strpos($template, '.tpl')) !== FALSE) { + $template = substr($template, 0, $pos); + } + // Transform - in filenames to _ to match function naming scheme + // for the purposes of searching. + $hook = strtr($template, '-', '_'); + if (isset($cache[$hook])) { + $implementations[$hook] = array( + 'template' => $template, + 'path' => dirname($file->uri), + ); + } + + // Match templates based on the 'template' filename. + foreach ($cache as $hook => $info) { + if (isset($info['template'])) { + $template_candidates = array($info['template'], str_replace($info['theme path'] . '/templates/', '', $info['template'])); + if (in_array($template, $template_candidates)) { + $implementations[$hook] = array( + 'template' => $template, + 'path' => dirname($file->uri), + ); + } + } + } + } + + // Find templates that implement possible "suggestion" variants of registered + // theme hooks and add those as new registered theme hooks. See + // drupal_find_theme_functions() for more information about suggestions and + // the use of 'pattern' and 'base hook'. + $patterns = array_keys($files); + foreach ($cache as $hook => $info) { + $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); + if (!isset($info['base hook']) && !empty($pattern)) { + // Transform _ in pattern to - to match file naming scheme + // for the purposes of searching. + $pattern = strtr($pattern, '_', '-'); + + $matches = preg_grep('/^' . $pattern . '/', $patterns); + if ($matches) { + foreach ($matches as $match) { + $file = $match; + // Chop off the remaining extensions if there are any. $template + // already has the rightmost extension removed, but there might still + // be more, such as with .tpl.php, which still has .tpl in $template + // at this point. + if (($pos = strpos($match, '.')) !== FALSE) { + $file = substr($match, 0, $pos); + } + // Put the underscores back in for the hook name and register this + // pattern. + $arg_name = isset($info['variables']) ? 'variables' : 'render element'; + $implementations[strtr($file, '-', '_')] = array( + 'template' => $file, + 'path' => dirname($files[$match]->uri), + $arg_name => $info[$arg_name], + 'base hook' => $hook, + ); + } + } + } + } + return $implementations; + } + +} diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 35cdc19..1d262d3 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -54,6 +54,7 @@ function aggregator_theme() { 'aggregator_wrapper' => array( 'variables' => array('content' => NULL), 'file' => 'aggregator.pages.inc', + 'preprocess' => array('template_preprocess_aggregator_wrapper'), 'template' => 'aggregator-wrapper', ), 'aggregator_categorize_items' => array( @@ -63,6 +64,7 @@ function aggregator_theme() { 'aggregator_feed_source' => array( 'variables' => array('feed' => NULL), 'file' => 'aggregator.pages.inc', + 'preprocess' => array('template_preprocess_aggregator_feed_source'), 'template' => 'aggregator-feed-source', ), 'aggregator_block_item' => array( @@ -71,15 +73,18 @@ function aggregator_theme() { 'aggregator_summary_items' => array( 'variables' => array('summary_items' => NULL, 'source' => NULL), 'file' => 'aggregator.pages.inc', + 'preprocess' => array('template_preprocess_aggregator_summary_items'), 'template' => 'aggregator-summary-items', ), 'aggregator_summary_item' => array( 'variables' => array('item' => NULL), + 'preprocess' => array('template_preprocess_aggregator_item'), 'file' => 'aggregator.pages.inc', ), 'aggregator_item' => array( 'variables' => array('item' => NULL), 'file' => 'aggregator.pages.inc', + 'preprocess' => array('template_preprocess_aggregator_item'), 'template' => 'aggregator-item', ), 'aggregator_page_opml' => array( @@ -90,6 +95,9 @@ function aggregator_theme() { 'variables' => array('feeds' => NULL, 'category' => NULL), 'file' => 'aggregator.pages.inc', ), + 'block' => array( + 'preprocess' => array('aggregator_preprocess_block'), + ), ); } diff --git a/core/modules/block/block.module b/core/modules/block/block.module index d9155c3..c75e0c0 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -85,11 +85,13 @@ function block_theme() { return array( 'block' => array( 'render element' => 'elements', - 'template' => 'block', + 'preprocess' => array('template_preprocess_block'), + 'template' => 'block', ), 'block_admin_display_form' => array( 'template' => 'block-admin-display-form', 'file' => 'block.admin.inc', + 'preprocess' => array('template_preprocess_block_admin_display_form'), 'render element' => 'form', ), ); diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 99edfc5..366d64d 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -43,10 +43,12 @@ function book_theme() { return array( 'book_navigation' => array( 'variables' => array('book_link' => NULL), + 'preprocess' => array('template_preprocess_book_navigation'), 'template' => 'book-navigation', ), 'book_export_html' => array( 'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL), + 'preprocess' => array('template_preprocess_book_export_html'), 'template' => 'book-export-html', ), 'book_admin_table' => array( @@ -54,10 +56,12 @@ function book_theme() { ), 'book_all_books_block' => array( 'render element' => 'book_menus', + 'preprocess' => array('template_preprocess_book_all_books_block'), 'template' => 'book-all-books-block', ), 'book_node_export_html' => array( 'variables' => array('node' => NULL, 'children' => NULL), + 'preprocess' => array('template_preprocess_book_node_export_html'), 'template' => 'book-node-export-html', ), ); diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 7ebef3e..f2bc837 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -186,6 +186,9 @@ function comment_field_extra_fields() { */ function comment_theme() { return array( + 'block__comment' => array( + 'preprocess' => array('comment_preprocess_block'), + ), 'comment_block' => array( 'variables' => array('number' => NULL), ), @@ -193,6 +196,7 @@ function comment_theme() { 'variables' => array('comment' => NULL), ), 'comment' => array( + 'preprocess' => array('template_preprocess_comment'), 'template' => 'comment', 'render element' => 'elements', ), @@ -200,6 +204,7 @@ function comment_theme() { 'variables' => array('node' => NULL), ), 'comment_wrapper' => array( + 'preprocess' => array('template_preprocess_comment_wrapper'), 'template' => 'comment-wrapper', 'render element' => 'content', ), diff --git a/core/modules/field/field.module b/core/modules/field/field.module index df37a5b..6f2ce2e 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -308,6 +308,8 @@ function field_theme() { return array( 'field' => array( 'render element' => 'element', + 'preprocess' => array('template_preprocess_field'), + 'process' => array('template_process_field'), ), 'field_multiple_value_form' => array( 'render element' => 'element', diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index fbe06a0..a65e6e0 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -61,23 +61,31 @@ function forum_help($path, $arg) { */ function forum_theme() { return array( + 'block__forum' => array( + 'preprocess' => array('forum_preprocess_block'), + ), 'forums' => array( + 'preprocess' => array('template_preprocess_forums'), 'template' => 'forums', 'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL), ), 'forum_list' => array( + 'preprocess' => array('template_preprocess_forum_list'), 'template' => 'forum-list', 'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL), ), 'forum_topic_list' => array( + 'preprocess' => array('template_preprocess_forum_topic_list'), 'template' => 'forum-topic-list', 'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL), ), 'forum_icon' => array( + 'preprocess' => array('template_preprocess_forum_icon'), 'template' => 'forum-icon', 'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE), ), 'forum_submitted' => array( + 'preprocess' => array('template_preprocess_forum_submitted'), 'template' => 'forum-submitted', 'variables' => array('topic' => NULL), ), diff --git a/core/modules/help/help.module b/core/modules/help/help.module index aac8636..6a51bb5 100644 --- a/core/modules/help/help.module +++ b/core/modules/help/help.module @@ -68,6 +68,16 @@ function help_help($path, $arg) { } /** + * Implements hook_theme(). + */ +function help_theme() { + $theme['block__system_help_block'] = array( + 'preprocess' => array('help_preprocess_block'), + ); + return $theme; +} + +/** * Implements hook_preprocess_HOOK() for block.tpl.php. */ function help_preprocess_block(&$variables) { diff --git a/core/modules/language/language.module b/core/modules/language/language.module index c2356be..6acb7a1 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -188,6 +188,9 @@ function language_permission() { */ function language_theme() { return array( + 'block__language' => array( + 'preprocess' => array('language_preprocess_block'), + ), 'language_negotiation_configure_form' => array( 'render element' => 'form', ), diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 5ba05d2..1187ed8 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -259,6 +259,9 @@ function locale_permission() { */ function locale_theme() { return array( + 'node' => array( + 'preprocess' => array('locale_preprocess_node'), + ), 'locale_translate_edit_form_strings' => array( 'render element' => 'form', 'file' => 'locale.pages.inc', diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 3d4a360..5317d8b 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -188,6 +188,9 @@ function menu_uri(Menu $menu) { */ function menu_theme() { return array( + 'block__menu' => array( + 'preprocess' => array('menu_preprocess_block'), + ), 'menu_overview_form' => array( 'file' => 'menu.admin.inc', 'render element' => 'form', diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 9e8fbd3..53ee795 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -147,8 +147,12 @@ function node_help($path, $arg) { */ function node_theme() { return array( + 'block__node' => array( + 'preprocess' => array('node_preprocess_block'), + ), 'node' => array( 'render element' => 'elements', + 'preprocess' => array('template_preprocess_node'), 'template' => 'node', ), 'node_search_admin' => array( diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index 54eaf18..1ee332e 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -71,13 +71,24 @@ function overlay_permission() { */ function overlay_theme() { return array( + 'html' => array( + 'preprocess' => array('overlay_preprocess_html'), + ), + 'maintenance_page' => array( + 'preprocess' => array('overlay_preprocess_maintenance_page'), + ), 'overlay' => array( 'render element' => 'page', + 'preprocess' => array('template_preprocess_overlay'), + 'process' => array('template_process_overlay'), 'template' => 'overlay', ), 'overlay_disable_message' => array( 'render element' => 'element', ), + 'page' => array( + 'preprocess' => array('overlay_preprocess_page'), + ), ); } diff --git a/core/modules/poll/poll.module b/core/modules/poll/poll.module index 85d29b4..9d3e593 100644 --- a/core/modules/poll/poll.module +++ b/core/modules/poll/poll.module @@ -34,7 +34,11 @@ function poll_help($path, $arg) { */ function poll_theme() { $theme_hooks = array( + 'block__poll' => array( + 'preprocess' => array('poll_preprocess_block'), + ), 'poll_vote' => array( + 'preprocess' => array('template_preprocess_poll_vote'), 'template' => 'poll-vote', 'render element' => 'form', ), @@ -42,6 +46,7 @@ function poll_theme() { 'render element' => 'form', ), 'poll_results' => array( + 'preprocess' => array('template_preprocess_poll_results'), 'template' => 'poll-results', 'variables' => array('raw_title' => NULL, 'results' => NULL, 'votes' => NULL, 'raw_links' => NULL, 'block' => NULL, 'nid' => NULL, 'vote' => NULL), ), diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 55d89cb..899a20c 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -437,6 +437,30 @@ function rdf_theme() { 'rdf_metadata' => array( 'variables' => array('metadata' => array()), ), + 'comment' => array( + 'preprocess' => array('rdf_preprocess_comment'), + ), + 'field' => array( + 'preprocess' => array('rdf_preprocess_field'), + ), + 'html' => array( + 'preprocess' => array('rdf_preprocess_html'), + ), + 'image' => array( + 'preprocess' => array('rdf_preprocess_image'), + ), + 'node' => array( + 'preprocess' => array('rdf_preprocess_node'), + ), + 'taxonomy_term' => array( + 'preprocess' => array('rdf_preprocess_taxonomy_term'), + ), + 'user' => array( + 'preprocess' => array('rdf_preprocess_user'), + ), + 'username' => array( + 'preprocess' => array('rdf_preprocess_username'), + ), ); } diff --git a/core/modules/search/search.module b/core/modules/search/search.module index 941621c..3eb5c9a 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -103,14 +103,19 @@ function search_help($path, $arg) { */ function search_theme() { return array( + 'block__search_form_block' => array( + 'preprocess' => array('search_preprocess_block'), + ), 'search_result' => array( 'variables' => array('result' => NULL, 'module' => NULL), 'file' => 'search.pages.inc', + 'preprocess' => array('template_preprocess_search_result'), 'template' => 'search-result', ), 'search_results' => array( 'variables' => array('results' => NULL, 'module' => NULL), 'file' => 'search.pages.inc', + 'preprocess' => array('template_preprocess_search_results'), 'template' => 'search-results', ), ); diff --git a/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module index edc3cbc..f3c1f52 100644 --- a/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module +++ b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module @@ -10,6 +10,16 @@ */ /** + * Implements hook_theme(). + */ +function search_embedded_form_theme() { + $theme['search_result'] = array( + 'preprocess' => array('search_embedded_form_preprocess_search_result'), + ); + return $theme; +} + +/** * Implements hook_menu(). */ function search_embedded_form_menu() { diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index ea58cc4..3cc1804 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -176,6 +176,12 @@ function shortcut_admin_paths() { */ function shortcut_theme() { return array( + 'block__shortcut' => array( + 'preprocess' => array('shortcut_preprocess_block'), + ), + 'page' => array( + 'preprocess' => array('shortcut_preprocess_page'), + ), 'shortcut_set_customize' => array( 'render element' => 'form', 'file' => 'shortcut.admin.inc', diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 801742c..61533e0 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -83,6 +83,16 @@ function statistics_exit() { } /** + * Implements hook_theme(). + */ +function statistics_theme() { + $theme['block__statistics'] = array( + 'preprocess' => array('statistics_preprocess_block'), + ); + return $theme; +} + +/** * Implements hook_permission(). */ function statistics_permission() { diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php index 843106e..21742e4 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php @@ -38,6 +38,7 @@ function setUp() { * Tests access to user autocompletion and verify the correct results. */ function testUserAutocomplete() { + // @todo Replace with injected mock registry in DUTB test. $this->drupalLogin($this->account); $this->drupalGet('user/autocomplete', array('query' => array('q' => $this->account->name))); $this->assertRaw($this->account->name); 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 5267300..1ad6fa1 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php @@ -79,6 +79,22 @@ function testPreprocessForSuggestions() { } /** + * Ensures suggestion preprocess functions run even for default implementations. + * + * The theme hook used by this test has its base preprocess function in a + * separate file, so this test also ensures that that file is correctly loaded + * when needed. + */ + function testSuggestionPreprocessForDefaults() { + // Test with both an unprimed and primed theme registry. + drupal_theme_rebuild(); + for ($i = 0; $i < 2; $i++) { + $this->drupalGet('theme-test/preprocess-suggestion'); + $this->assertText('Theme hook implementor=test_theme_theme_test_preprocess__suggestion(). Foo=template_preprocess_theme_test_preprocess', 'Theme hook ran with data available from a preprocess function for the suggested hook.'); + } + } + + /** * Ensure page-front template suggestion is added when on front page. */ function testFrontPageThemeSuggestion() { @@ -192,14 +208,8 @@ function testClassLoading() { * Tests drupal_find_theme_templates(). */ public function testFindThemeTemplates() { - $cache = array(); - - // Prime the theme cache. - foreach (module_implements('theme') as $module) { - _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); - } - - $templates = drupal_find_theme_templates($cache, '.tpl.php', drupal_get_path('theme', 'test_theme')); + $registry = theme_get_registry(); + $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 6a9d988..5abb4bc 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTestTwig.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTestTwig.php @@ -47,13 +47,7 @@ function testTemplateOverride() { * Tests drupal_find_theme_templates */ function testFindThemeTemplates() { - - $cache = array(); - - // Prime the theme cache - foreach (module_implements('theme') as $module) { - _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); - } + $cache = theme_get_registry(); // Check for correct content // @todo Remove this tests once double engine code is removed diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 7e95a03..d86920d 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -1806,21 +1806,13 @@ function hook_permission() { * - base hook: A string declaring the base theme hook if this theme * implementation is actually implementing a suggestion for another theme * hook. - * - pattern: A regular expression pattern to be used to allow this theme - * implementation to have a dynamic name. The convention is to use __ to - * differentiate the dynamic portion of the theme. For example, to allow - * forums to be themed individually, the pattern might be: 'forum__'. Then, - * when the forum is themed, call: - * @code - * theme(array('forum__' . $tid, 'forum'), $forum) - * @endcode * - preprocess functions: A list of functions used to preprocess this data. * Ordinarily this won't be used; it's automatically filled in. By default, * for a module this will be filled in as template_preprocess_HOOK. For * a theme this will be filled in as phptemplate_preprocess and * phptemplate_preprocess_HOOK as well as themename_preprocess and * themename_preprocess_HOOK. - * - override preprocess functions: Set to TRUE when a theme does NOT want + * - no preprocess: Set to TRUE when a theme does NOT want * the standard preprocess functions to run. This can be used to give a * theme FULL control over how variables are set. For example, if a theme * wants total control over how certain variables in the page.tpl.php are @@ -1866,8 +1858,9 @@ function hook_theme($existing, $type, $theme, $path) { * Changes here will not be visible until the next cache clear. * * The $theme_registry array is keyed by theme hook name, and contains the - * information returned from hook_theme(), as well as additional properties - * added by _theme_process_registry(). + * processed information returned from hook_theme(). + * + * @see \Drupal\Core\Theme\Registry::$registry * * For example: * @code @@ -1890,7 +1883,6 @@ function hook_theme($existing, $type, $theme, $path) { * The entire cache of theme registry information, post-processing. * * @see hook_theme() - * @see _theme_process_registry() */ function hook_theme_registry_alter(&$theme_registry) { // Kill the next/previous forum topic navigation links. diff --git a/core/modules/system/system.module b/core/modules/system/system.module index cdf7b90..d778218 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -133,6 +133,9 @@ function system_help($path, $arg) { */ function system_theme() { return array_merge(drupal_common_theme(), array( + 'block' => array( + 'preprocess' => array('system_preprocess_block'), + ), 'system_themes_page' => array( 'variables' => array('theme_groups' => NULL), 'file' => 'system.admin.inc', diff --git a/core/modules/system/tests/modules/theme_test/theme_test.inc b/core/modules/system/tests/modules/theme_test/theme_test.inc index 6cde683..e43df50 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.inc +++ b/core/modules/system/tests/modules/theme_test/theme_test.inc @@ -13,3 +13,22 @@ function theme_theme_test($variables) { function template_preprocess_theme_test(&$variables) { $variables['foo'] = 'template_preprocess_theme_test'; } + +/** + * Returns HTML for the 'theme_test_preprocess' theme hook used by tests. + */ +function theme_theme_test_preprocess($variables) { + return 'Theme hook implementor=theme_theme_test_preprocess(). Foo=' . $variables['foo']; +} + +/** + * Implements hook_preprocess_HOOK() for theme_theme_test_preprocess(). + * + * Despite not having a corresponding theme function for this suggestion, the + * specific preprocess function should still be used. + */ +function template_preprocess_theme_test_preprocess(&$variables) { + $variables['foo'] = 'template_preprocess_theme_test_preprocess'; +} + + 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 73ff44b..d5f8cc6 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -4,9 +4,17 @@ * Implements hook_theme(). */ function theme_test_theme($existing, $type, $theme, $path) { + $items['html'] = array( + 'preprocess' => array('theme_test_preprocess_html'), + ); $items['theme_test'] = array( 'file' => 'theme_test.inc', 'variables' => array('foo' => ''), + 'preprocess' => array('template_preprocess_theme_test'), + ); + $items['theme_test_preprocess'] = array( + 'file' => 'theme_test.inc', + 'variables' => array('foo' => ''), ); $items['theme_test_template_test'] = array( 'template' => 'theme_test.template_test', @@ -42,6 +50,13 @@ function theme_test_menu() { 'theme callback' => '_theme_custom_theme', 'type' => MENU_CALLBACK, ); + $items['theme-test/preprocess-suggestion'] = array( + 'title' => 'Preprocess suggestion', + 'page callback' => '_theme_test_preprocess_suggestion', + 'access callback' => TRUE, + 'theme callback' => '_theme_custom_theme', + 'type' => MENU_CALLBACK, + ); $items['theme-test/alter'] = array( 'title' => 'Suggestion', 'page callback' => '_theme_test_alter', @@ -82,19 +97,6 @@ function theme_test_init() { // still capable of returning output and theming the page as a whole. $GLOBALS['theme_test_output'] = theme('more_link', array('url' => 'user', 'title' => 'Themed output generated in hook_init()')); } - if (arg(0) == 'user' && arg(1) == 'autocomplete') { - // Register a fake registry loading callback. If it gets called by - // theme_get_registry(), the registry has not been initialized yet. - _theme_registry_callback('_theme_test_load_registry', array()); - } -} - -/** - * Fake registry loading callback. - */ -function _theme_test_load_registry() { - print 'registry initialized'; - return array(); } /** @@ -158,6 +160,13 @@ function _theme_test_suggestion() { } /** + * Page callback, calls a theme hook suggestion. + */ +function _theme_test_preprocess_suggestion() { + return theme(array('theme_test_preprocess__suggestion', 'theme_test_preprocess'), array()); +} + +/** * Implements hook_preprocess_HOOK() for html.tpl.php. */ function theme_test_preprocess_html(&$variables) { diff --git a/core/modules/system/tests/themes/test_theme/template.php b/core/modules/system/tests/themes/test_theme/template.php index 8275818..4d2e947 100644 --- a/core/modules/system/tests/themes/test_theme/template.php +++ b/core/modules/system/tests/themes/test_theme/template.php @@ -8,6 +8,13 @@ function test_theme_theme_test__suggestion($variables) { } /** + * Tests a theme overriding a default hook with a suggestion. + */ +function test_theme_theme_test_preprocess__suggestion($variables) { + return 'Theme hook implementor=test_theme_theme_test_preprocess__suggestion(). Foo=' . $variables['foo']; +} + +/** * Tests a theme implementing an alter hook. * * The confusing function name here is due to this being an implementation of diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index a15874e..a9a6942 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -233,6 +233,7 @@ function taxonomy_theme() { ), 'taxonomy_term' => array( 'render element' => 'elements', + 'preprocess' => array('template_preprocess_taxonomy_term'), 'template' => 'taxonomy-term', ), ); diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index e543326..4dbded4 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -42,6 +42,7 @@ function toolbar_permission() { function toolbar_theme($existing, $type, $theme, $path) { $items['toolbar'] = array( 'render element' => 'toolbar', + 'preprocess' => array('template_preprocess_toolbar'), 'template' => 'toolbar', ); return $items; diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 02f5072..88583ec 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -88,8 +88,12 @@ function user_help($path, $arg) { */ function user_theme() { return array( + 'block__user' => array( + 'preprocess' => array('user_preprocess_block'), + ), 'user' => array( 'render element' => 'elements', + 'preprocess' => array('template_preprocess_user'), 'template' => 'user', 'file' => 'user.pages.inc', ), @@ -109,6 +113,8 @@ function user_theme() { 'variables' => array('signature' => NULL), ), 'username' => array( + 'preprocess' => array('template_preprocess_username'), + 'process' => array('template_process_username'), 'variables' => array('account' => NULL), ), ); 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 a9d79ba..6851f70 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 @@ -7,6 +7,7 @@ namespace Drupal\views\Plugin\views\display; +use Drupal\Core\Theme\Registry; use Drupal\views\ViewExecutable; use \Drupal\views\Plugin\views\PluginBase; @@ -1765,7 +1766,7 @@ public function buildOptionsForm(&$form, &$form_state) { include_once DRUPAL_ROOT . '/' . $theme->owner; } } - $this->theme_registry = _theme_load_registry($theme, $base_theme, $theme_engine); + $this->theme_registry = new Registry($theme, $base_theme, $theme_engine); } // If there's a theme engine involved, we also need to know its extension diff --git a/core/modules/views/tests/views_test_data/views_test_data.module b/core/modules/views/tests/views_test_data/views_test_data.module index 324a446..742b14a 100644 --- a/core/modules/views/tests/views_test_data/views_test_data.module +++ b/core/modules/views/tests/views_test_data/views_test_data.module @@ -8,6 +8,17 @@ use Drupal\views\ViewExecutable; /** + * Implements hook_theme(). + */ +function views_test_data_theme() { + $theme['views_view_mapping_test'] = array( + 'render element' => 'element', + 'preprocess' => array('template_preprocess_views_view_mapping_test'), + ); + return $theme; +} + +/** * Implements hook_permission(). */ function views_test_data_permission() { diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 287e2e0..d1ea0fa 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -82,6 +82,16 @@ function views_pre_render_view_element($element) { * Implement hook_theme(). Register views theming functions. */ function views_theme($existing, $type, $theme, $path) { + $hooks['comment'] = array( + 'preprocess' => array('views_preprocess_comment'), + ); + $hooks['html'] = array( + 'preprocess' => array('views_preprocess_html'), + ); + $hooks['node'] = array( + 'preprocess' => array('views_preprocess_node'), + ); + module_load_include('inc', 'views', 'views.theme'); // Some quasi clever array merging here. diff --git a/core/modules/views/views_ui/views_ui.module b/core/modules/views/views_ui/views_ui.module index 7552f63..7b10cd9 100644 --- a/core/modules/views/views_ui/views_ui.module +++ b/core/modules/views/views_ui/views_ui.module @@ -268,6 +268,10 @@ function views_ui_theme() { 'render element' => 'element', 'file' => 'views_ui.theme.inc', ), + + 'views_view' => array( + 'preprocess' => array('views_ui_preprocess_views_view'), + ), ); } diff --git a/core/themes/bartik/template.php b/core/themes/bartik/template.php index 9a6c8a9..b0095cf 100644 --- a/core/themes/bartik/template.php +++ b/core/themes/bartik/template.php @@ -6,6 +6,27 @@ */ /** + * Implements hook_theme(). + */ +function bartik_theme() { + $theme['block'] = array( + 'preprocess' => array('bartik_preprocess_block'), + ); + $theme['html'] = array( + 'preprocess' => array('bartik_preprocess_html'), + 'process' => array('bartik_process_html'), + ); + $theme['maintenance_page'] = array( + 'preprocess' => array('bartik_preprocess_maintenance_page'), + 'process' => array('bartik_process_maintenance_page'), + ); + $theme['page'] = array( + 'process' => array('bartik_process_page'), + ); + return $theme; +} + +/** * Implements hook_preprocess_HOOK() for html.tpl.php. * * Adds body classes if certain regions have content. diff --git a/core/themes/seven/template.php b/core/themes/seven/template.php index d679369..c6d0162 100644 --- a/core/themes/seven/template.php +++ b/core/themes/seven/template.php @@ -6,6 +6,25 @@ */ /** + * Implements hook_theme(). + */ +function seven_theme() { + $theme['html'] = array( + 'preprocess' => array('seven_preprocess_html'), + ); + $theme['install_page'] = array( + 'preprocess' => array('seven_preprocess_install_page'), + ); + $theme['maintenance_page'] = array( + 'preprocess' => array('seven_preprocess_maintenance_page'), + ); + $theme['page'] = array( + 'preprocess' => array('seven_preprocess_page'), + ); + return $theme; +} + +/** * Implements hook_preprocess_HOOK() for maintenance-page.tpl.php. */ function seven_preprocess_maintenance_page(&$vars) {