Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.576
diff -u -p -r1.576 theme.inc
--- includes/theme.inc	17 Feb 2010 08:50:49 -0000	1.576
+++ includes/theme.inc	19 Feb 2010 03:44:38 -0000
@@ -384,15 +384,14 @@ function _theme_process_registry(&$cache
         $result[$hook]['includes'][] = $include_file;
       }
 
-      // If 'variables' have been defined previously, carry them forward.
-      // This should happen if a theme overrides a Drupal defined theme
-      // function, for example.
-      if (!isset($info['variables']) && isset($cache[$hook]['variables'])) {
-        $result[$hook]['variables'] = $cache[$hook]['variables'];
-      }
-      // Same for 'render element'.
-      if (!isset($info['render element']) && isset($cache[$hook]['render element'])) {
-        $result[$hook]['render element'] = $cache[$hook]['render element'];
+      // If these keys are left unspecified within overridden entries returned
+      // by hook_theme(), carry them forward from the prior entry. This is so
+      // that themes don't need to specify this information, since the module
+      // that registered the theme hook already has.
+      foreach (array('variables', 'render element', 'pattern', 'base hook') as $key) {
+        if (!array_key_exists($key, $info) && isset($cache[$hook][$key])) {
+          $result[$hook][$key] = $cache[$hook][$key];
+        }
       }
 
       // The following apply only to theming hooks implemented as templates.
@@ -839,7 +838,19 @@ function theme($hook, $variables = array
   }
 
   // Invoke the variable processors, if any. The processors may specify
-  // alternate suggestions for which hook's template/function to use.
+  // alternate suggestions for which hook's template/function to use. If the
+  // hook is a suggestion of a base hook, invoke the variable processors of
+  // the base hook, but retain the suggestion as a high priority suggestion to
+  // be used unless overridden by a variable processor function.
+  if (isset($info['base hook'])) {
+    $base_hook = $info['base hook'];
+    $base_hook_info = $hooks[$base_hook];
+    if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) {
+      $variables['theme_hook_suggestion'] = $hook;
+      $hook = $base_hook;
+      $info = $base_hook_info;
+    }
+  }
   if (isset($info['preprocess functions']) || isset($info['process functions'])) {
     $variables['theme_hook_suggestions'] = array();
     foreach (array('preprocess functions', 'process functions') as $phase) {
@@ -962,44 +973,53 @@ function path_to_theme() {
  * @param $prefixes
  *   An array of prefixes to test, in reverse order of importance.
  *
- * @return $templates
+ * @return $implementations
  *   The functions found, suitable for returning from hook_theme;
  */
 function drupal_find_theme_functions($cache, $prefixes) {
-  $templates = array();
+  $implementations = array();
   $functions = get_defined_functions();
 
   foreach ($cache as $hook => $info) {
     foreach ($prefixes as $prefix) {
+      // Find theme functions that implement possible "suggestion" variants of
+      // registered theme hooks and add those as new registered theme hooks.
+      // The 'pattern' key defines a common prefix that all suggestions must
+      // start with. The default is the name of the hook followed by '__'. An
+      // 'base hook' key is added to each entry made for a found suggestion,
+      // so that common functionality can be implemented for all suggestions of
+      // the same base hook. To keep things simple, deep heirarchy of
+      // suggestions is not supported: each suggestion's 'base hook' key
+      // refers to a base hook, not to another suggestion, and all suggestions
+      // are found using the base hook's pattern, not a pattern from an
+      // intermediary suggestion.
       $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
-      if (!empty($pattern)) {
+      if (!isset($info['base hook']) && !empty($pattern)) {
         $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']);
         if ($matches) {
           foreach ($matches as $match) {
             $new_hook = str_replace($prefix . '_', '', $match);
             $arg_name = isset($info['variables']) ? 'variables' : 'render element';
-            $templates[$new_hook] = array(
+            $implementations[$new_hook] = array(
               'function' => $match,
               $arg_name => $info[$arg_name],
+              'base hook' => $hook,
             );
           }
         }
       }
+      // Find theme functions that implement registered theme hooks and include
+      // that in what is returned so that the registry knows that the theme has
+      // this implementation.
       if (function_exists($prefix . '_' . $hook)) {
-        $templates[$hook] = array(
+        $implementations[$hook] = array(
           'function' => $prefix . '_' . $hook,
         );
-        // Ensure that the pattern is maintained from base themes to its sub-themes.
-        // Each sub-theme will have their functions scanned so the pattern must be
-        // held for subsequent runs.
-        if (isset($info['pattern'])) {
-          $templates[$hook]['pattern'] = $info['pattern'];
-        }
       }
     }
   }
 
-  return $templates;
+  return $implementations;
 }
 
 /**
@@ -1013,7 +1033,7 @@ function drupal_find_theme_functions($ca
  *   The path to search.
  */
 function drupal_find_theme_templates($cache, $extension, $path) {
-  $templates = array();
+  $implementations = array();
 
   // Collect paths to all sub-themes grouped by base themes. These will be
   // used for filtering. This allows base themes to have sub-themes in its
@@ -1036,9 +1056,12 @@ function drupal_find_theme_templates($ca
 
   // Escape the periods in the extension.
   $regex = '/' . str_replace('.', '\.', $extension) . '$/';
-  // Because drupal_system_listing works the way it does, we check for real
-  // templates separately from checking for patterns.
+  // Get a listing of all template files in the path to search.
   $files = drupal_system_listing($regex, $path, 'name', 0);
+
+  // Find templates that implement registered theme hooks and include that in
+  // what is returned so that the registry knows that the theme has this
+  // implementation.
   foreach ($files as $template => $file) {
     // Ignore sub-theme templates for the current theme.
     if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) {
@@ -1054,24 +1077,21 @@ function drupal_find_theme_templates($ca
     // for the purposes of searching.
     $hook = strtr($template, '-', '_');
     if (isset($cache[$hook])) {
-      $templates[$hook] = array(
+      $implementations[$hook] = array(
         'template' => $template,
         'path' => dirname($file->uri),
       );
     }
-    // Ensure that the pattern is maintained from base themes to its sub-themes.
-    // Each sub-theme will have their templates scanned so the pattern must be
-    // held for subsequent runs.
-    if (isset($cache[$hook]['pattern'])) {
-      $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
-    }
   }
 
+  // Find templates that implement possible "suggestion" variants of registered
+  // theme hooks and add those as new registered theme hooks. @see
+  // drupal_find_theme_functions() for more information about suggestions and
+  // the use of 'pattern' and 'base hook'.
   $patterns = array_keys($files);
-
   foreach ($cache as $hook => $info) {
     $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
-    if (!empty($pattern)) {
+    if (!isset($info['base hook']) && !empty($pattern)) {
       // Transform _ in pattern to - to match file naming scheme
       // for the purposes of searching.
       $pattern = strtr($pattern, '_', '-');
@@ -1082,16 +1102,17 @@ function drupal_find_theme_templates($ca
           $file = substr($match, 0, strpos($match, '.'));
           // Put the underscores back in for the hook name and register this pattern.
           $arg_name = isset($info['variables']) ? 'variables' : 'render element';
-          $templates[strtr($file, '-', '_')] = array(
+          $implementations[strtr($file, '-', '_')] = array(
             'template' => $file,
             'path' => dirname($files[$match]->uri),
             $arg_name => $info[$arg_name],
+            'base hook' => $hook,
           );
         }
       }
     }
   }
-  return $templates;
+  return $implementations;
 }
 
 /**
Index: modules/simpletest/tests/theme.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/theme.test,v
retrieving revision 1.11
diff -u -p -r1.11 theme.test
--- modules/simpletest/tests/theme.test	13 Jan 2010 05:40:03 -0000	1.11
+++ modules/simpletest/tests/theme.test	19 Feb 2010 03:44:40 -0000
@@ -9,15 +9,20 @@
 /**
  * Unit tests for the Theme API.
  */
-class TemplateUnitTest extends DrupalWebTestCase {
+class ThemeUnitTest extends DrupalWebTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Theme API',
-      'description' => 'Test low-level theme template functions.',
+      'description' => 'Test low-level theme functions.',
       'group' => 'Theme',
     );
   }
 
+  function setUp() {
+    parent::setUp('theme_test');
+    theme_enable(array('test_theme'));
+  }
+
   /**
    * Test function theme_get_suggestions() for SA-CORE-2009-003.
    */
@@ -39,6 +44,14 @@ class TemplateUnitTest extends DrupalWeb
     $suggestions = theme_get_suggestions($args, 'page');
     $this->assertEqual($suggestions, array('page__node', 'page__node__%', 'page__node__1'), t('Removed invalid \\0 from suggestions'));
   }
+
+  /**
+   * Preprocess functions for the base hook should run even for suggestion implementations.
+   */
+  function testPreprocessForSuggestions() {
+    $this->drupalGet('theme-test/suggestion');
+    $this->assertText('test_theme_breadcrumb__suggestion: 1', t('Theme hook suggestion ran with data available from a preprocess function for the base hook.'));
+  }
 }
 
 /**
Index: modules/simpletest/tests/theme_test.info
===================================================================
RCS file: modules/simpletest/tests/theme_test.info
diff -N modules/simpletest/tests/theme_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/theme_test.info	19 Feb 2010 03:44:40 -0000
@@ -0,0 +1,8 @@
+; $Id: theme_test.info,v 1.1 2008/08/16 21:11:02 dries Exp $
+name = "Theme test"
+description = "Support module for theme system testing."
+package = Testing
+version = VERSION
+core = 7.x
+files[] = theme_test.module
+hidden = TRUE
Index: modules/simpletest/tests/theme_test.module
===================================================================
RCS file: modules/simpletest/tests/theme_test.module
diff -N modules/simpletest/tests/theme_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/theme_test.module	19 Feb 2010 03:44:40 -0000
@@ -0,0 +1,40 @@
+<?php
+// $Id: theme_test.module,v 1.15 2009/12/04 16:49:47 dries Exp $
+
+/**
+ * Implements hook_menu().
+ */
+function theme_test_menu() {
+  $items['theme-test/suggestion'] = array(
+    'title' => 'Suggestion',
+    'page callback' => '_theme_test_suggestion',
+    'access arguments' => array('access content'),
+    'theme callback' => '_theme_custom_theme',
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Custom theme callback.
+ */
+function _theme_custom_theme() {
+  return 'test_theme';
+}
+
+/**
+ * Page callback, calls a theme hook suggestion.
+ */
+function _theme_test_suggestion() {
+  return theme(array('breadcrumb__suggestion', 'breadcrumb'), array());
+}
+
+/**
+ * Implements hook_preprocess_breadcrumb().
+ *
+ * Set a variable that can later be tested to see if this function ran.
+ */
+function theme_test_preprocess_breadcrumb(&$variables) {
+  $variables['theme_test_preprocess_breadcrumb'] = 1;
+}
Index: themes/tests/test_theme/template.php
===================================================================
RCS file: themes/tests/test_theme/template.php
diff -N themes/tests/test_theme/template.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ themes/tests/test_theme/template.php	19 Feb 2010 03:44:41 -0000
@@ -0,0 +1,11 @@
+<?php
+// $Id: template.php,v 1.37 2010/01/30 07:59:26 dries Exp $
+
+/**
+ * Tests a theme overriding a suggestion of a base theme hook.
+ */
+function test_theme_breadcrumb__suggestion($variables) {
+  // Tests that preprocess functions for the base theme hook get called even
+  // when the suggestion has an implementation.
+  return 'test_theme_breadcrumb__suggestion: ' . $variables['theme_test_preprocess_breadcrumb'];
+}
Index: themes/tests/test_theme/test_theme.info
===================================================================
RCS file: themes/tests/test_theme/test_theme.info
diff -N themes/tests/test_theme/test_theme.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ themes/tests/test_theme/test_theme.info	19 Feb 2010 03:44:41 -0000
@@ -0,0 +1,6 @@
+; $Id: test_theme.info,v 1.1 2009/10/08 15:40:34 dries Exp $
+name = Test theme
+description = Theme for testing the theme system
+core = 7.x
+engine = phptemplate
+hidden = TRUE
