Index: devel.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/devel/devel.module,v
retrieving revision 1.187
diff -u -p -r1.187 devel.module
--- devel.module	16 Aug 2007 12:57:48 -0000	1.187
+++ devel.module	16 Aug 2007 22:56:37 -0000
@@ -1,5 +1,5 @@
  $data) {
-    // Add in devel_preprocess so it's used as the last variable preprocessor.
-    // This way, it can gather *all* template suggestions, not just the ones set by modules.
-    if (!isset($data['function']) && !in_array('devel_preprocess', $data['preprocess functions'])) {
-      $theme_registry[$hook]['preprocess functions'][] = 'devel_preprocess';
-    }
-    elseif (isset($data['function'])) {
-      // For intercepting theme functions not connected to template files.
-      // Copy over original registry of the hook so it can be caught later.
-      $theme_registry[$hook]['devel'] = $theme_registry[$hook];
-      // Replace the defaults to be intercepted.
-      $theme_registry[$hook]['function'] = 'devel_catch_theme_function';
-      $theme_registry[$hook]['type'] = 'module';
-      $theme_registry[$hook]['theme path'] = drupal_get_path('module', 'devel');
-    }
+    // Supply alternate hook (theme function) free of previous data.
+    $theme_registry_alt[$hook]['function'] = 'devel_catch_theme_function';
+    $theme_registry_alt[$hook]['type'] = 'module';
+    $theme_registry_alt[$hook]['theme path'] = drupal_get_path('module', 'devel');    
+    // Copy original hook data and set hook for easy access to be used in devel_catch_theme_function().
+    $theme_registry_alt[$hook]['devel'] = $theme_registry[$hook];
+    $theme_registry_alt[$hook]['devel']['hook'] = $hook;
   }
-  _theme_save_registry($GLOBALS['theme_info'], $theme_registry);
+  // TODO: This still doesn't work.
+  //_theme_save_registry($GLOBALS['theme_info'], $theme_registry);
+  cache_set("theme_registry:garland", $theme_registry_alt); // Hard coded just to test.
+
 }
 
 /**
- * Show all theme templates that could have been used on this page.
- * TODO: highlight the one that was actually used
+ * Process and display all theme data used on a page.
  **/
 function devel_template_log() {
   if (variable_get('dev_template_log', 0)) {
-    $extension = devel_get_theme_extension();
-    $header = array('Template name', "Template files ($extension)");
     if (isset($GLOBALS['devel_theme_functions'])) {
-      foreach ($GLOBALS['devel_theme_functions'] as $counter => $function) {
-        // TODO: path_to_theme() is not right in hook_footer()
-        // TODO: drupal_discover_template() needs leading './' so as to avoid lookup in whole include pat
-        // $used = drupal_discover_template($function['template_files'], $extension);
-        // array_push($function['template_files'], $function['function']);
-        // $key = array_search($used, $function['template_files']);
-        // $function['template_files'][$key] = theme('placeholder', $function['template_files'][$key]);
-        $id = "devel_template_log_link_$counter";
-        $marker = "
\n";
-        $rows[] = array($marker. $function['function'], implode(', ', $function['template_files']));
-        // unset($function['template_files']);
-      }
-      return theme('table', $header, $rows);
-    }
-  }
-}
+      
+      $version = drupal_major_version_map(VERSION);
+      $api = variable_get('devel_api_url', 'api.drupal.org');
+      static $hook_count = array();
+      
+      foreach ($GLOBALS['devel_theme_functions'] as $hook_count => $data) {
+        
+        $hook = $data['hook'];
+        
+        $function = l('api', "http://$api/api/$version/function/". $data['function']);
+        $function .= ' | '. $data['function'] .'';
+        
+        foreach ($data['wildcards'] as $wildcard => $state) {
+          $wildcards[$hook][] = $state ? ''. $wildcard .'(), ' : $wildcard .'(), ';
+        }
+        foreach ($data['template_files'] as $template => $state) {
+          // TODO: $template uses the full path if it was suggested from a module.
+          // !! path_to_theme() bug.!!
+          $templates[$hook][] = $data['path'] .'/'. ($state ? "$template" : "$template");
+        }
+        // If the hook has a template, then it has these..
+        if (isset($templates[$hook])) {
+          $variables[$hook] = isset($variables[$hook]) ? array_merge($variables[$hook], $data['variables']) : $data['variables'];
+          foreach ($data['preprocess functions'] as $pre_func) {
+            $linked_preprocess[] = 
+              l('api', "http://$api/api/function/$pre_func/$version") .' | '.
+              ''. $pre_func .'';
+          }
+          $preprocess[$hook] = isset($preprocess[$hook]) ? array_merge($preprocess[$hook], $linked_preprocess) : $linked_preprocess;
+        }
 
-// would be nice if theme() broke this into separate function so we don't copy logic here. this one is better - has cache
-function devel_get_theme_extension() {
-  global $theme_engine;
-  static $extension = NULL;
-
-  if (!$extension) {
-    $extension_function = $theme_engine .'_extension';
-    if (function_exists($extension_function)) {
-      $extension = $extension_function();
-    }
-    else {
-      $extension = '.tpl.php';
+        // Set header for hook group. This is only shown once per hook.
+        static $this_hook = NULL;
+        if ($this_hook != $hook) {
+          $run_count = devel_counter($hook, TRUE);
+          $rows[$hook .'_head'][1] = array(
+            'data' => $function .' '. format_plural($run_count, '1 run', '!count runs', array('!count' => $run_count )) .'',
+            'class' => 'devel_function_head',
+          );
+          $rows[$hook .'_head'][2] = array(
+            'data' => $data['function_type'],
+            'class' => 'devel_function_head',
+          );
+          $rows[$hook .'_head'][3] = array(
+            'data' => $data['path'],
+            'class' => 'devel_function_head',
+          );
+          $this_hook = $hook;
+        }
+        
+        // We need to run these last. Check by comparing counters.
+        if (isset($wildcards[$hook]) && devel_counter($hook, TRUE) == $data['counter']) {
+          $rows[$hook .'_wildcards'][] = array(
+            'data' => "
+              
+                - Wild cards:+
- $wildcards+
+            "
+            ,
+            'colspan' => '3',
+          );
+        }
+        if (isset($templates[$hook]) && devel_counter($hook, TRUE) == $data['counter']) {
+          $unique_templates = array_unique($templates[$hook]);
+          $unique_preprocess = array_unique($preprocess[$hook]);
+          $unique_variables = array_unique($variables[$hook]);
+          $rows[$hook .'_templates'][] = array(
+            'data' => "
+
+                - Templates:+
- ". implode('
 ', $unique_templates) ."
+
+
+                - Variable Functions:+
- ". implode('
 ', $unique_preprocess) ."
+
+
+                - Variables:+
- $". implode(', $', $unique_variables) ."+
+            ",
+            'colspan' => '3',
+          );
+        }
+      }
+      
+      $header = array('Theme function', 'Source type', 'Source path');
+      if (!isset($rows)) {
+        $rows = array();
+      }
+      
+      return theme('table', $header, $rows, array('class' => 'devel-theme-function-list'));
     }
   }
-  return $extension;
 }
 
 /**
- * This function gets injected into the registry in devel_exit(). It logs theme template calls.
-*/
-function devel_preprocess($vars, $function) {
-  $counter = devel_counter();
-  $GLOBALS['devel_theme_functions'][$counter] = array(
-    'function' => $function,
-    'template_files' => $vars['template_files'],
-  );
-}
-
-/**
- * Intercepts theme *functions*, adds to template log, and dispatches to original theme function.
+ * Intercepts all theme functions, adds to template log, and dispatches to original theme function.
  * This function gets injected into theme registry in devel_exit(). 
  */
 function devel_catch_theme_function() {
-  static $i=0;
-  $args = func_get_args();
 
   // Get the function that is normally called.
   $trace = debug_backtrace();
-  $call_theme_func = $trace[2]['args'][0];
+  $hook = $trace[2]['args'][0];
 
-  // Get registry for the original function data.
-  $theme_registry = theme_get_registry();
-  $hook_registry_data = $theme_registry[$call_theme_func]['devel'];
+  // The twin of theme().
+  $args = func_get_args();
+  $theme_data = devel_theme_evil_twin($hook, $args);
 
-  // Include a file if this theme function is held elsewhere. Partial copy of theme().
-  if (!empty($hook_registry_data['file'])) {
-    $function_file = $hook_registry_data['file'];
-    if (isset($hook_registry_data['path'])) {
-      $function_file = $hook_registry_data['path'] .'/'. $function_file;
-    }
-    include_once($function_file);
-  }
+  // Bail if there's not output. Prevents un-needed markup.
+  if ($theme_data['output']) {
+    // Add counter
+    $counter = devel_counter($hook);
+    $theme_data['meta']['counter'] = $counter;
+    $theme_data['meta']['hook'] = $hook;
   
-  // log the call
-  $counter = devel_counter();
-  $GLOBALS['devel_theme_functions'][$counter] = array(
-    'function' => $hook_registry_data['function'],
-    'template_files' => array(),
-  );
+    // $theme data['meta'] is keyed with the following:
+    // wildcards, path, function, function_type, extension, preprocess functions, template_files, variables (keys), output plus the above.
+    $GLOBALS['devel_theme_functions'][$hook .'_'. $counter] = $theme_data['meta'];
 
-  return devel_template_marker($counter). call_user_func_array($hook_registry_data['function'], $args);
+    return devel_template_marker($counter, $theme_data['meta']) . $theme_data['output'];
+  }
 }
 
-function devel_template_marker($counter) {
-  $id = "devel_template_log_call_". $counter;
-  return "\n";  
+function devel_template_marker($counter, $meta) {
+  $id = $meta['function'] .'-'. $counter;
+  $class = 'devel-function-log-call';
+  $class .= ' function-'. $meta['function'];
+  if (isset($meta['template_files'])) {
+    $class .= ' '. implode(' ', $meta['preprocess functions']);
+    $class .= ' '. $meta['template_files'][array_search(TRUE, $meta['template_files'])];
+  }
+  return "\n";  
+}
+
+// hand out counter per hook.
+function devel_counter($hook, $get = FALSE) {
+  static $counter = array();
+  if (!isset($counter[$hook])) {
+    $counter[$hook] = 0;
+  }
+  if (!$get) {
+    $counter[$hook]++;
+  }
+  return $counter[$hook];
 }
 
-// just hand out next counter
-function devel_counter() {
-  static $counter = 0;
-  $counter++;
-  return $counter;
+/**
+ * Copied from theme() with slight modifications to gather data about the
+ * function as it is being run. Is this a little overboard?
+ */
+function devel_theme_evil_twin($hook, $args) {;
+
+  // Get registry for the original function data.
+  $hooks = theme_get_registry();
+  $info = $hooks[$hook]['devel'];
+
+  // Gather all possible wildcard functions.
+  $meta['wildcards'] = array();
+  if (is_array($hook)) {
+    foreach ($hook as $candidate) {
+      $mets['wildcards'][$candidate] = FALSE;
+      if (isset($info['hook'][$candidate])) {
+        $mets['wildcards'][$candidate] = TRUE;
+        break;
+      }
+    }
+    $hook = $candidate;
+  }
+
+  // We can add something here. Maybe a notice or rebuild the registry?
+  if ($info['hook'] != $hook) {
+    return;
+  }
+
+  global $theme_path;
+  // point path_to_theme() to the currently used theme path:
+  $theme_path = $info['theme path'];
+  $meta['path'] = $theme_path;
+
+  if (isset($info['function'])) {
+    // The theme call is a function.
+    $meta['function'] = $info['function'];
+    $meta['function_type'] = 'function';
+    // Include a file if this theme function is held elsewhere.
+    if (!empty($info['file'])) {
+      $function_file = $info['file'];
+      if (isset($info['path'])) {
+        $function_file = $info['path'] .'/'. $function_file;
+      }
+      include_once($function_file);
+    }
+    $output = call_user_func_array($info['function'], $args);
+  }
+  else {
+    // The theme call is a template.
+    $meta['function'] = 'theme_'. $hook;
+    $meta['function_type'] = 'template';
+    $variables = array(
+      'template_files' => array()
+    );
+    if (!empty($info['arguments'])) {
+      $count = 0;
+      foreach ($info['arguments'] as $name => $default) {
+        $variables[$name] = isset($args[$count]) ? $args[$count] : $default;
+        $count++;
+      }
+    }
+
+    // default render function and extension.
+    $render_function = 'theme_render_template';
+    $extension = '.tpl.php';
+
+    // Run through the theme engine variables, if necessary
+    global $theme_engine;
+    if (isset($theme_engine)) {
+      // If theme or theme engine is implementing this, it may have
+      // a different extension and a different renderer.
+      if ($info['type'] != 'module') {
+        if (function_exists($theme_engine .'_render_template')) {
+          $render_function = $theme_engine .'_render_template';
+        }
+        $extension_function = $theme_engine .'_extension';
+        if (function_exists($extension_function)) {
+          $extension = $extension_function();
+        }
+      }
+    }
+    $meta['extension'] = $extension;
+
+    if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) {
+      // This construct ensures that we can keep a reference through
+      // call_user_func_array.
+      $args = array(&$variables, $hook);
+      foreach ($info['preprocess functions'] as $preprocess_function) {
+        if (function_exists($preprocess_function)) {
+          call_user_func_array($preprocess_function, $args);
+        }
+      }
+      $meta['preprocess functions'] = $info['preprocess functions'];
+    }
+
+    // Get suggestions for alternate templates out of the variables
+    // that were set. This lets us dynamically choose a template
+    // from a list. The order is FILO, so this array is ordered from
+    // least appropriate first to most appropriate last.
+    $suggestions = array();
+
+    if (isset($variables['template_files'])) {
+      $suggestions = $variables['template_files'];
+    }
+    if (isset($variables['template_file'])) {
+      $suggestions[] = $variables['template_file'];
+    }
+
+    if ($suggestions) {
+      $template_file = drupal_discover_template($suggestions, $extension);
+      foreach ($suggestions as $candidate) {
+        $meta['template_files'][$candidate . $extension] = FALSE;
+        if ($candidate . $extension == $template_file) {
+          $meta['template_files'][$candidate . $extension] = TRUE;
+        }
+      }
+    }
+
+    if (empty($template_file)) {
+      $template_file = $info['file'] . $extension;
+      $meta['template_files'][$template_file] = TRUE;
+      if (isset($info['path'])) {
+        $template_file = $info['path'] .'/'. $template_file;
+      }
+    }
+    $output = $render_function($template_file, $variables);
+    
+    $meta['variables'] = array_keys($variables);
+  }
+
+  return array('output' => $output, 'meta' => $meta);
 }
 
 // Menu callback. I would love prettier hierarchy browser for this.
Index: devel.css
===================================================================
RCS file: /cvs/drupal/contributions/modules/devel/devel.css,v
retrieving revision 1.3
diff -u -p -r1.3 devel.css
--- devel.css	16 Aug 2007 05:49:58 -0000	1.3
+++ devel.css	16 Aug 2007 22:56:44 -0000
@@ -4,4 +4,19 @@
 
 .devel_template_log_call, .devel_template_log_link {
   display: none;
-}
\ No newline at end of file
+}
+
+.devel-theme-function-list {
+  font-size: 1em;
+}
+
+.devel-templates, .devel-preprocessors {
+  width: 45%;
+  float: left;
+}
+.devel-variables {
+  float: left;
+}
+.devel-theme-function-list .function-run {
+  font-size: .85em;
+}