Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.598
diff -u -p -r1.598 theme.inc
--- includes/theme.inc	19 May 2010 19:22:24 -0000	1.598
+++ includes/theme.inc	5 Jun 2010 21:13:17 -0000
@@ -753,7 +753,7 @@ function theme($hook, $variables = array
   }
 
   // If there's no implementation, check for more generic fallbacks. If there's
-  // still no implementation, log an error and return an empty string.
+  // still no implementation, invoke the error handler.
   if (!isset($hooks[$hook])) {
     // Iteratively strip everything after the last '__' delimiter, until an
     // implementation is found.
@@ -764,8 +764,8 @@ function theme($hook, $variables = array
       }
     }
     if (!isset($hooks[$hook])) {
-      watchdog('theme', 'Theme key "@key" not found.', array('@key' => $hook), WATCHDOG_WARNING);
-      return '';
+      $function = theme_get_error_handler();
+      return $function($hook);
     }
   }
 
@@ -856,6 +856,7 @@ function theme($hook, $variables = array
     foreach (array_reverse($suggestions) as $suggestion) {
       if (isset($hooks[$suggestion])) {
         $info = $hooks[$suggestion];
+        $used_suggestion = $suggestion;
         break;
       }
     }
@@ -866,6 +867,15 @@ function theme($hook, $variables = array
     if (function_exists($info['function'])) {
       $output = $info['function']($variables);
     }
+    else {
+      // If a function implementation is registered but doesn't exist, it's
+      // either due to a bug in a module or theme, or module or theme code has
+      // been updated without the theme registry being rebuilt. Let a pluggable
+      // error handler decide what to do.
+      $key = isset($used_suggestion) ? $used_suggestion : $hook;
+      $function = theme_get_error_handler();
+      $output = $function($key, 'function', $info['function'], $variables);
+    }
   }
   else {
     // Default render function and extension.
@@ -908,6 +918,16 @@ function theme($hook, $variables = array
       $template_file = $info['path'] . '/' . $template_file;
     }
     $output = $render_function($template_file, $variables);
+
+    // A successfully rendered template should always return a string (an empty
+    // string is valid). The template renderer can return literal FALSE to
+    // indicate an error (e.g., if the template file doesn't exist). Let a
+    // pluggable error handler decide how to handle the error.
+    if ($output === FALSE) {
+      $key = isset($used_suggestion) ? $used_suggestion : $hook;
+      $function = theme_get_error_handler();
+      $output = $function($key, 'template', $template_file, $variables);
+    }
   }
 
   // restore path_to_theme()
@@ -1217,7 +1237,89 @@ function theme_render_template($template
   extract($variables, EXTR_SKIP);               // Extract the variables to a local namespace
   ob_start();                                   // Start output buffering
   include DRUPAL_ROOT . '/' . $template_file;   // Include the template file
-  return ob_get_clean();                        // End buffering and return its contents
+  $output = ob_get_clean();                     // End buffering and get its contents
+  // We don't want to call file_exists() unnecessarily, but if nothing was
+  // rendered, it might be due to a missing template file, and we need to flag
+  // that as an error by returning FALSE.
+  if ($output === '' && !file_exists(DRUPAL_ROOT . '/' . $template_file)) {
+    $output = FALSE;
+  }
+  return $output;
+}
+
+/**
+ * Returns the function that theme() should call when a registered implementation is missing or fails.
+ */
+function theme_get_error_handler() {
+  $function = theme_set_error_handler();
+  if (!function_exists($function)) {
+    $function = '_theme_default_error_handler';
+  }
+  return $function;
+}
+
+/**
+ * Sets the function that theme() should call when a registered implementation is missing or fails.
+ */
+function theme_set_error_handler($new_function = NULL) {
+  $function = &drupal_static(__FUNCTION__, '_theme_default_error_handler');
+  if (isset($new_function)) {
+    $function = $new_function;
+  }
+  return $function;
+}
+
+/**
+ * Default error handler for when a theme registry key is missing or has a problematic implementation.
+ *
+ * @param $key
+ *   The key within the theme registry that is missing or has a problematic
+ *   implementation.
+ * @param $implementation_type
+ *   NULL if the key is missing from the registry. Otherwise 'function' or
+ *   'template', depending on whether the implementation is a function or a
+ *   template.
+ * @param $implementation_name
+ *   The name of the function or template file that is missing or problematic.
+ * @param $variables
+ *   The keyed array of variables that would be passed to the function or
+ *   template if it was working. This variable is not used by this default
+ *   error handler, but may be used by a custom error handler.
+ *
+ * @return
+ *   A themed HTML string for theme() to return.
+ *
+ * @see theme()
+ * @see theme_set_error_handler()
+ */
+function _theme_default_error_handler($key, $implementation_type = NULL, $implementation_name = NULL, $variables = NULL) {
+  $logged = &drupal_static(__FUNCTION__, array());
+
+  // Let the user know that something is wrong with the page, but don't expose
+  // any details. One message per page is fine, even if there are multiple
+  // errors.
+  if (empty($logged)) {
+    drupal_set_message(t('Some page content may be missing from this page. Please notify the site administrator.'), 'warning');
+  }
+
+  // Give the administrator some details. One log entry per key is fine, even if
+  // it is invoked multiple times per page.
+  if (!isset($logged[$key])) {
+    $logged[$key] = TRUE;
+    switch ($implementation_type) {
+      case 'function':
+        watchdog('theme', 'Theme function "@function" not found.', array('@function' => $implementation_name), WATCHDOG_WARNING);
+        break;
+      case 'template':
+        watchdog('theme', 'Theme template "@template" not found.', array('@template' => $implementation_name), WATCHDOG_WARNING);
+        break;
+      case NULL:
+        watchdog('theme', 'Theme key "@key" not found.', array('@key' => $key), WATCHDOG_WARNING);
+        break;
+    }
+  }
+
+  return '';
 }
 
 /**
