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 ''; } /**