Index: includes/theme.inc ========================================================= --- includes/theme.inc (revision 1.503) +++ includes/theme.inc Sat Aug 08 19:58:06 PDT 2009 @@ -365,49 +365,56 @@ } // Check for sub-directories. $result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path; + } - + + // Allow variable processors for all theming hooks, whether the hook is + // implemented as a template or as a function. - foreach ($template_phases as $phase_key => $template_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) { + foreach ($template_phases as $phase_key => $template_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) { - if (function_exists($prefix . '_' . $template_phase)) { + // Only use non-hook-specific variable processors for theming hooks implemented as templates. + // This helps avoid an unnecessary performance hit on function-implemented theming hooks, + // many of which are executed often and need to be fast. Also, function implementations + // of theming hooks can't make use of variables other than the ones declared as the hook's + // arguments, and therefore, are unlikely to benefit from non-hook-specific processors. + if (isset($info['template']) && function_exists($prefix . '_' . $template_phase)) { - $info[$phase_key][] = $prefix . '_' . $template_phase; - } - if (function_exists($prefix . '_' . $template_phase . '_' . $hook)) { - $info[$phase_key][] = $prefix . '_' . $template_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]; + $info[$phase_key][] = $prefix . '_' . $template_phase; + } + if (function_exists($prefix . '_' . $template_phase . '_' . $hook)) { + $info[$phase_key][] = $prefix . '_' . $template_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]; - } } } @@ -418,12 +425,17 @@ // 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 it's a template and not registered by the theme or engine. - if (!empty($info['template']) && empty($result[$hook])) { + // Check only if not registered by the theme or engine. + if (empty($result[$hook])) { foreach ($template_phases as $phase_key => $template_phase) { if (!isset($info[$phase_key])) { $cache[$hook][$phase_key] = array(); } + // Only use non-hook-specific variable processors for theming hooks implemented as templates. + // This helps avoid an unnecessary performance hit on function-implemented theming hooks, + // many of which are executed often and need to be fast. Also, function implementations + // of theming hooks can't make use of variables other than the ones declared as the hook's + // arguments, and therefore, are unlikely to benefit from non-hook-specific processors. if (function_exists($name . '_' . $template_phase)) { $cache[$hook][$phase_key][] = $name . '_' . $template_phase; } @@ -568,9 +580,6 @@ * registry is checked to determine which implementation to use, which may * be a function or a template. * - * If the implementation is a function, it is executed and its return value - * passed along. - * * If the implementation is a template, the arguments are converted to a * $variables array. This array is then modified by the module implementing * the hook, theme engine (if applicable) and the theme. The following @@ -672,6 +681,15 @@ * priority than items in template_files. theme() will then look for these * files, one at a time, and use the first one * that exists. + * + * If the implementation is a function, only the hook-specific preprocess + * and process functions (the ones ending in _HOOK) are called from the + * above list. These hooks can set a special variable, 'theme_function', + * in order to force a different implementation function to be executed + * instead of the one registered for the theme hook. This function, or + * the original registered function, is then executed and its return value + * is passed along. + * * @param $hook * The name of the theme function to call. May be an array, in which * case the first hook that actually has an implementation registered @@ -723,19 +741,54 @@ } if (isset($info['function'])) { // The theme call is a function. - if (drupal_function_exists($info['function'])) { + $function = $info['function']; + - // If a theme function that does not expect a renderable array is called - // with a renderable array as the only argument (via drupal_render), then - // we take the arguments from the properties of the renderable array. If - // missing, use hook_theme() defaults. - if (isset($args[0]) && is_array($args[0]) && isset($args[0]['#theme']) && count($info['arguments']) > 1) { - $new_args = array(); - foreach ($info['arguments'] as $name => $default) { - $new_args[] = isset($args[0]["#$name"]) ? $args[0]["#$name"] : $default; - } - $args = $new_args; - } + // If a theme function that does not expect a renderable array is called + // with a renderable array as the only argument (via drupal_render), then + // we take the arguments from the properties of the renderable array. If + // missing, use hook_theme() defaults. + if (isset($args[0]) && is_array($args[0]) && isset($args[0]['#theme']) && count($info['arguments']) > 1) { + $new_args = array(); + foreach ($info['arguments'] as $name => $default) { + $new_args[] = isset($args[0]["#$name"]) ? $args[0]["#$name"] : $default; + } + $args = $new_args; + } - $output = call_user_func_array($info['function'], $args); + + // Invoke the variable processors, if any. + if (count($info['preprocess functions'] || count($info['process functions']))) { + $variables = array( + 'theme_function' => $function, + ); + if (!empty($info['arguments'])) { + $count = 0; + foreach ($info['arguments'] as $name => $default) { + $variables[$name] = isset($args[$count]) ? $args[$count] : $default; + $count++; + } + } + // This construct ensures that we can keep a reference through call_user_func_array. + $processor_args = array(&$variables, $hook); + foreach (array('preprocess functions', 'process functions') as $template_phase) { + foreach ($info[$template_phase] as $template_function) { + if (drupal_function_exists($template_function)) { + call_user_func_array($template_function, $processor_args); + } + } + } + if (!empty($info['arguments'])) { + $count = 0; + foreach ($info['arguments'] as $name => $default) { + $args[$count] = $variables[$name]; + $count++; + } + } + $function = $variables['theme_function']; + } + + // Call the function. + if (drupal_function_exists($function)) { + $output = call_user_func_array($function, $args); } } else {