Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.202
diff -u -p -r1.202 module.inc
--- includes/module.inc	3 Oct 2010 02:04:55 -0000	1.202
+++ includes/module.inc	17 Nov 2010 17:54:44 -0000
@@ -612,8 +612,10 @@ function module_implements($hook, $sort 
     $implementations = array();
     cache_set('module_implements', array(), 'cache_bootstrap');
     drupal_static_reset('module_hook_info');
+    drupal_static_reset('module_hook_wildcard_info');
     drupal_static_reset('drupal_alter');
     cache_clear_all('hook_info', 'cache_bootstrap');
+    cache_clear_all('hook_wildcard_info', 'cache_bootstrap');
     return;
   }
 
@@ -633,6 +635,7 @@ function module_implements($hook, $sort 
     // implementations, that the cache is updated at the end of the request.
     $implementations['#write_cache'] = TRUE;
     $hook_info = module_hook_info();
+    $hook_wildcards = module_hook_wildcard_info($hook);
     $implementations[$hook] = array();
     $list = module_list(FALSE, FALSE, $sort);
     foreach ($list as $module) {
@@ -640,6 +643,15 @@ function module_implements($hook, $sort 
       if (module_hook($module, $hook)) {
         $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
       }
+      elseif (!isset($hook_info[$hook]) && !empty($hook_wildcards)) {
+        foreach ($hook_wildcards as $hook_wildcard) {
+          $include_file = module_load_include('inc', $module, $module . '.' . $hook_wildcard['group']);
+          if (module_hook($module, $hook)) {
+            $implementations[$hook][$module] = $include_file ? $hook_wildcard['group'] : FALSE;
+            break;
+          }
+        }
+      }
     }
     // Allow modules to change the weight of specific implementations but avoid
     // an infinite loop.
@@ -708,6 +720,44 @@ function module_hook_info() {
 }
 
 /**
+ * Retrieve a list of what wildcard hooks are explicitly declared.
+ */
+function module_hook_wildcard_info($hook = NULL) {
+  $hook_wildcard_info = &drupal_static(__FUNCTION__, array());
+
+  if (empty($hook_wildcard_info)) {
+    $cache = cache_get('hook_wildcard_info', 'cache_bootstrap');
+    if ($cache === FALSE) {
+      $hook_wildcard_info = array();
+      foreach (module_hook_info() as $hook_key => $hook) {
+        // Wildcard hooks are only supported if they use 'group'.
+        if (!empty($hook['wildcard']) && !empty($hook['group'])) {
+          $hook_wildcard_info[$hook_key] = $hook;
+          $hook_wildcard_info[$hook_key]['regex'] = '/^' . preg_replace('/\\\\\*/', '.*', preg_quote($hook_key, '/')) . '$/';
+        }
+      }
+      cache_set('hook_wildcard_info', $hook_wildcard_info, 'cache_bootstrap');
+    }
+    else {
+      $hook_wildcard_info = $cache->data;
+    }
+  }
+
+  if (isset($hook)) {
+    // Attempt to check if the hook matches one of the hook wildcards.
+    $matching_wildcard_hooks = array();
+    foreach ($hook_wildcard_info as $key => $hook_wildcard) {
+      if (preg_match($hook_wildcard['regex'], $hook)) {
+        $matching_wildcard_hooks[$key] = $hook_wildcard;
+      }
+    }
+    return $matching_wildcard_hooks;
+  }
+
+  return $hook_wildcard_info;
+}
+
+/**
  * Writes the hook implementation cache.
  *
  * @see module_implements()
@@ -953,4 +1003,3 @@ function drupal_alter($type, &$data, &$c
     $function($data, $context1, $context2);
   }
 }
-
