Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.506
diff -u -p -r1.506 theme.inc
--- includes/theme.inc	19 Aug 2009 22:44:05 -0000	1.506
+++ includes/theme.inc	19 Aug 2009 23:57:24 -0000
@@ -486,12 +486,29 @@ function _theme_build_registry($theme, $
   // Finally, hooks provided by the theme itself.
   _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));
 
+  // Add 'alter functions' to each theme hook, so that we can run the equivalent
+  // of drupal_alter() on theme output at the end of the theme() function, but
+  // with better performance. Another benefit of storing these functions in the
+  // registry is that modules implementing hook_theme_registry_alter() can
+  // adjust them. This doesn't need to be built one at a time with
+  // _theme_process_registry(), because we're only interested in modules
+  // implementing the alter hooks, since that's the case with other uses of
+  // drupal_alter(). If someone comes up with a convincing reason to let themes
+  // implement alter functions the way they can implement preprocess functions,
+  // then this code would need to move to _theme_process_registry().
+  foreach ($cache as $hook => $info) {
+    $cache[$hook]['alter functions'] = array();
+    foreach (module_implements('theme_' . $hook . '_alter') as $module) {
+      $cache[$hook]['alter functions'][] = $module . '_theme_' . $hook . '_alter';
+    }
+  }
+
   // 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) {
+    foreach (array('preprocess functions', 'process functions', 'alter functions') as $phase) {
       if (empty($info[$phase])) {
         unset($cache[$hook][$phase]);
       }
@@ -718,6 +735,9 @@ function list_themes($refresh = FALSE) {
  *   highest priority function that exists. If none exists, theme() will call
  *   the original registered function for the theme hook.
  *
+ * Modules can alter the final output from the theme by implementing
+ * - MODULE_theme_HOOK_alter(&$output, $variables, $hook)
+ *
  * @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
@@ -788,7 +808,9 @@ function theme() {
     // we do have processors, then the overhead of calling them overshadows the
     // overhead of calling empty().
     if (!isset($info['_no_processors'])) {
-      if (!empty($info['preprocess functions']) || !empty($info['process functions'])) {
+      // If we don't have preprocess or process functions, but have alter
+      // functions, we still need to prepare $variables.
+      if (!empty($info['preprocess functions']) || !empty($info['process functions']) || !empty($info['alter functions'])) {
         $variables = array(
           'theme_functions' => array(),
         );
@@ -919,6 +941,19 @@ function theme() {
   }
   // restore path_to_theme()
   $theme_path = $temp;
+
+  // Let 'alter functions' alter the output. Similar to drupal_alter(), but
+  // using the registry as a performance optimization and for greater
+  // flexibility.
+  if (!empty($info['alter functions'])) {
+    foreach ($info['alter functions'] as $alter_function) {
+      // Better performance than just drupal_function_exists().
+      if (function_exists($alter_function) || drupal_function_exists($alter_function)) {
+        $alter_function($output, $variables, $hook);
+      }
+    }
+  }
+
   return $output;
 }
 
