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 {
