diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 92070d5..17283e3 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -499,29 +499,47 @@ class ThemeRegistry Extends DrupalCacheArray { 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); + + // 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', + ); + + // Prepare prefixes for processor functions. + $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 ($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 + // 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 @@ -529,72 +547,65 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { $result[$hook]['type'] = $type; $result[$hook]['theme path'] = $path; - // If function and file are omitted, default to standard naming - // conventions. + // If a theme function or template file is not defined, default to the + // standard naming convention for theme functions. if (!isset($info['template']) && !isset($info['function'])) { $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook; } + // Collect previously set files for inclusion. 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 + // If the theme implementation defines a file to include, 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. + // files. These files will be included once by theme() calls. + $themed_path = isset($info['path']) ? $info['path'] : $path; + 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; + include_once DRUPAL_ROOT . '/' . $themed_path . '/' . $info['file']; + $result[$hook]['includes'][] = $themed_path . '/' . $info['file']; + } + + // Do the same for template files. + if (isset($info['template'])) { + $result[$hook]['template'] = $themed_path . '/' . $info['template']; } // If the default keys are not set, use the default values registered // by the module. if (isset($cache[$hook])) { + $hook_defaults = array( + 'variables' => TRUE, + 'render element' => TRUE, + 'pattern' => TRUE, + 'base hook' => TRUE, + ); $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 . '/' . $info['template']; - } + $current_prefixes = $prefixes; + if (isset($cache[$hook]['function']) && $cache[$hook]['type'] === 'module' && isset($info['template'])) { + // If the default implementation is a function, but a template + // overrides that default implementation, introduce the default + // template processors. It is assumed that when it is implemented as + // a theme function, it must run as quickly as possible so only the + // default varible processors are introduced. The ones provided by + // modules not directly related to this theming hook will be ignored. + $current_prefixes = array('template') + $current_prefixes; } // 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])) { + // Check for existing variable processors set through hook_theme(). + if (!isset($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) { + foreach ($current_prefixes as $prefix) { // Only use non-hook-specific variable processors for theming hooks - // implemented as templates. See theme(). + // implemented as templates. if (isset($info['template']) && function_exists($prefix . '_' . $phase)) { $info[$phase_key][] = $prefix . '_' . $phase; } @@ -610,7 +621,7 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { // Flag not needed inside the registry. unset($result[$hook]['override ' . $phase_key]); } - elseif (isset($cache[$hook][$phase_key]) && is_array($cache[$hook][$phase_key])) { + elseif (isset($cache[$hook][$phase_key])) { $info[$phase_key] = array_merge($cache[$hook][$phase_key], $info[$phase_key]); } $result[$hook][$phase_key] = $info[$phase_key]; @@ -620,31 +631,90 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { // 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]); +/** + * This completes the theme registry adding missing functions and hooks. + */ +function _theme_post_process_registry(&$cache, $theme, $base_theme, $theme_engine) { + + // Get all user defined functions. + list(, $user_func) = array_values(get_defined_functions()); + $user_func = array_combine($user_func, $user_func); + + // Gather prefixes. This will be used to limit the found functions to the + // expected naming conventions. + $prefixes = module_list(); + if ($theme_engine) { + $prefixes[] = $theme_engine . '_engine'; + } + foreach ($base_theme as $base) { + $prefixes[] = $base->name; + } + $prefixes[] = $theme->name; + + // Collect all known hooks. Discovered functions must be based on a known hook. + $hooks = implode('|', array_keys($cache)); + + // Collect all variable processor functions in the correct order. + $processors = array(); + foreach ($prefixes as $prefix) { + $processors += preg_grep("/^{$prefix}_(pre)?process_($hooks)(__)?/", $user_func); + } + + // Add missing variable processors. This is needed for hooks that do not + // explictly register the hook. For example, when a theme contains a variable + // process function but it does not implement a template, it will go missing. + // This will add the expected function. It also allows modules or themes to + // have a variable process function based on a pattern even if the hook does + // not exist. + foreach ($processors as $processor) { + $hook = substr($processor, strpos($processor, 'process_') + strlen('process_')); + $phase = strpos($processor, 'preprocess') ? 'preprocess functions' : 'process functions'; + + if (isset($cache[$hook][$phase]) && !in_array($processor, $cache[$hook][$phase])) { + // Add missing processor to existing hook. + $cache[$hook][$phase][] = $processor; + } + elseif (!isset($cache[$hook]) && strpos($hook, '__')) { + // Process non-existing hook and register it. + // Search for the base hook. + $base_hook = $hook; + while (!isset($cache[$base_hook]) && $pos = strrpos($base_hook, '__')) { + $base_hook = substr($base_hook, 0, $pos); + // If the current hook is based on a pattern, get the base hook. + if (isset($cache[$base_hook]['base hook'])) { + $base_hook = $cache[$base_hook]['base hook']; + } + } + if (isset($cache[$base_hook])) { + // Pull from the base hook and register the new + $cache[$hook] = $cache[$base_hook]; + if (isset($cache[$hook][$phase])) { + $cache[$hook][$phase][] = $processor; } } } } + + // Inherit all base hook variable processors into pattern hooks. This ensures + // that derivative hooks have a complete set of variable process functions. + foreach ($cache as $hook => $info) { + foreach (array('preprocess functions', 'process functions') as $phase) { + // The 'base hook' is only applied to derivative hooks already registered + // from a pattern. This is typically set from drupal_find_theme_functions() + // and drupal_find_theme_templates(). + if (isset($info['base hook']) && isset($cache[$info['base hook']][$phase])) { + $diff = array_diff($cache[$info['base hook']][$phase], $info[$phase]); + $cache[$hook][$phase] = array_merge($diff, $info[$phase]); + } + + // Optimize the registry. + if (isset($cache[$hook][$phase]) && empty($cache[$hook][$phase])) { + unset($cache[$hook][$phase]); + } + } + } } /** @@ -695,17 +765,12 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) { // Finally, hooks provided by the theme itself. _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename)); + // Finalize the build. + _theme_post_process_registry($cache, $theme, $base_theme, $theme_engine); + // 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; } @@ -945,14 +1010,13 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { function theme($hook, $variables = array()) { static $hooks = NULL; - // 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(). - if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) { - throw new Exception(t('theme() may not be called until all modules are loaded.')); - } - if (!isset($hooks)) { + // 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(). + if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) { + throw new Exception(t('theme() may not be called until all modules are loaded.')); + } drupal_theme_initialize(); $hooks = theme_get_registry(FALSE); } @@ -1000,6 +1064,7 @@ function theme($hook, $variables = array()) { foreach ($info['includes'] as $include_file) { include_once DRUPAL_ROOT . '/' . $include_file; } + //unset($hooks[$hook]['includes']); } // If a renderable array is passed as $variables, then set $variables to @@ -1008,39 +1073,25 @@ function theme($hook, $variables = array()) { $element = $variables; $variables = array(); if (isset($info['variables'])) { - foreach (array_keys($info['variables']) as $name) { - if (isset($element["#$name"])) { - $variables[$name] = $element["#$name"]; - } + foreach ($info['variables'] as $name => $default) { + $variables[$name] = isset($element["#$name"]) ? $element["#$name"] : $default; } } else { $variables[$info['render element']] = $element; } } - - // Merge in argument defaults. - if (!empty($info['variables'])) { - $variables += $info['variables']; - } - elseif (!empty($info['render element'])) { - $variables += array($info['render element'] => array()); - } - - // Invoke the variable processors, if any. The processors may specify - // alternate suggestions for which hook's template/function to use. If the - // hook is a suggestion of a base hook, invoke the variable processors of - // the base hook, but retain the suggestion as a high priority suggestion to - // be used unless overridden by a variable processor function. - if (isset($info['base hook'])) { - $base_hook = $info['base hook']; - $base_hook_info = $hooks[$base_hook]; - if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) { - $variables['theme_hook_suggestion'] = $hook; - $hook = $base_hook; - $info = $base_hook_info; + else { + // Direct theme() call. Not passed from a renderable array. + // Merge in argument defaults. + if (!empty($info['variables'])) { + $variables += $info['variables']; + } + elseif (!empty($info['render element'])) { + $variables += array($info['render element'] => array()); } } + if (isset($info['preprocess functions']) || isset($info['process functions'])) { $variables['theme_hook_suggestions'] = array(); foreach (array('preprocess functions', 'process functions') as $phase) { @@ -1084,9 +1135,7 @@ 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); - } + $output = $info['function']($variables); } else { // Default render function and extension. @@ -1107,28 +1156,8 @@ 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; - } - $output = $render_function($template_file, $variables); + $output = $render_function($info['template'] . $extension, $variables); } // restore path_to_theme()