? test_theme_log.patch 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 20 Aug 2007 06:31:19 -0000 @@ -2,6 +2,52 @@ padding-top: 2em; } -.devel_template_log_call, .devel_template_log_link { - display: none; +/* This should never be shown. Used as markers in the DOM */ +.devel-dom-marker { + display: none !important; +} + +.devel-log-js { +} +.devel-theme-function-list { + font-size: 1em; +} +.devel-theme-function-list dl { + margin-top: 0; +} +.wildcards-inactive, .templates-inactive, .devel-function-no-output { + font-style: italic; +} +.templates-does-not-exist, .wildcards-does-not-exist { + text-decoration: line-through; + font-style: italic; +} +.templates-does-not-exist:hover, .wildcards-does-not-exist:hover { + text-decoration: none; +} +.devel-templates, .devel-preprocessors { + width: 45%; + float: left; +} +.devel-variables { + float: left; +} +.devel-function-run { + font-size: .85em; +} +.devel-timer { + font-style: italic; + font-size: .9em; +} + +#template-log-link { + position: fixed; + top: 0; + right: 0; + padding: 0 .7em; + text-align: right; + background: #fff; + border: 1px; + opacity: .8; + z-index: 9999; } \ No newline at end of file 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 20 Aug 2007 06:31:20 -0000 @@ -1,5 +1,5 @@ '. devel_timer() .' '. $query_summary. ''; + $output .= '
Theme function iterations: '. $total_run_count .' ('. $minus_output .' runs resulted in no output.)
'; + $link_div = ''; + + return $time_info . theme('table', $header, $rows, array('id' => 'devel-theme-logs', 'class' => 'devel-theme-function-list')) . $link_div; } - 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. - * This function gets injected into theme registry in devel_exit(). + * Used to intercept all theme functions, collect theming data and dispatches + * to the original theme function. + * + * This function gets injected into theme registry in devel_exit(). + * + * @see devel_theme_registry_inject() */ function devel_catch_theme_function() { - static $i=0; $args = func_get_args(); + // Get registry. + $hooks = theme_get_registry(); // Get the function that is normally called. $trace = debug_backtrace(); - $call_theme_func = $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']; + $hook = $trace[2]['args'][0]; - // 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; + // Check for wildcard functions, collect data and provide 'devel__HOOK' for each wildcard. + // This is needed due to the swap in the registry. + if (is_array($hook)) { + $flag_first = FALSE; + foreach ($hook as $candidate) { + $devel_hook[] = 'devel__'. $candidate; + if (isset($hooks['devel__'. $candidate]) && !$flag_first) { + $wildcards['active'][] = $candidate; + $flag_first = TRUE; + } + else { + $state = isset($hooks['devel__'. $candidate]) ? 'inactive' : 'does not exist'; + $wildcards[$state][] = $candidate; + } } - include_once($function_file); + $hook = $candidate; + } + else { + $devel_hook = 'devel__'. $hook; } + // $hook is definitely a string at this point.. + + // Increment counter. + devel_counter($hook); - // log the call - $counter = devel_counter(); - $GLOBALS['devel_theme_functions'][$counter] = array( - 'function' => $hook_registry_data['function'], - 'template_files' => array(), - ); + /** + * This runs theme() once again with new hook pointing to original data. + * The original 'preprocess functions' is the default last parameter for templated functions. + * It will be available within $variables inside devel_preprocess(). + */ + $args = array_merge(array($devel_hook), $args); + $output = call_user_func_array('theme', $args); + + $add_data['registry'] = $hooks['devel__'. $hook]; + if (isset($wildcards)) { + $add_data['wildcards'] = $wildcards; + } + if (!empty($output)) { + $add_data['output'] = TRUE; + } + // Collect for the template log. + $return_data = devel_collect_theme_data($hook, $add_data, TRUE); + $return_data['hook'] = $hook; + + // Bail if there's no output. Prevents un-needed markup. + if ($output) { + $output = ($hook != 'page' ? devel_template_marker($return_data, $output) : $output); + } - return devel_template_marker($counter). call_user_func_array($hook_registry_data['function'], $args); + return $output; } -function devel_template_marker($counter) { - $id = "devel_template_log_call_". $counter; - return "\n"; +/** + * Used to handle preprocess function. Done here to filter hook names and to + * get easy access to all variables. + */ +function devel_preprocess(&$variables, $hook) { + // Filter out 'devel_' prefix. + $hook = str_replace('devel__', '', $hook); + $registry = $variables['hook_registry']; + unset($variables['hook_registry']); + /** + * This is a partially copied from theme(). Process all variable functions here. + */ + if (is_array($registry['preprocess functions'])) { + $args = array(&$variables, $hook); + foreach ($registry['preprocess functions'] as $preprocess_function) { + if (function_exists($preprocess_function)) { + call_user_func_array($preprocess_function, $args); + } + } + } + /** + * Process templates and template suggestions. + */ + $templates = $variables['template_files']; + $ext = devel_get_theme_extension(); + // Checks for the existance of each suggestion. FIFO, reverse order. + $flag_first = FALSE; + foreach (array_reverse($templates) as $template) { + $check_template = path_to_theme() .'/'. $template .$ext; + if ($exists = file_exists($check_template) && !$flag_first) { + $state = 'active'; + $flag_first = TRUE; + } + elseif ($exists) { + $state = 'inactive'; + } + else { + $state = 'does not exist'; + } + $data['templates'][$state][] = $check_template; + } + // Get default template not part of suggestions. + $default_template = isset($registry['path']) ? $registry['path'] .'/' : ''; + $default_template .= $registry['file'] . $ext; + // Get state of the default template. + $state = $flag_first ? 'inactive' : 'active'; + $data['templates'][$state][] = $default_template .' -base'; + // Add in variables. + $data['variables']['active'] = array_keys($variables); + // Collect for the template log. + devel_collect_theme_data($hook, $data); } -// just hand out next counter -function devel_counter() { - static $counter = 0; - $counter++; - return $counter; +/** + * Collects data used for a loaded page. Parsed through devel_template_log(). + */ +function devel_collect_theme_data($hook = NULL, $data = NULL) { + static $theme_data = array(); + if (isset($hook)) { + // Get current hook count. + $count = devel_counter($hook, TRUE); + $theme_data[$hook]['run count'] = $count; + // Filter out items that doesn't need repeating. + if (isset($data['registry']['arguments']['hook_registry']['preprocess functions'])) { + $theme_data[$hook]['dynamic'][1]['preprocessors']['active'] = $data['registry']['arguments']['hook_registry']['preprocess functions']; + unset($data['registry']['arguments']); + unset($data['registry']['preprocess functions']); + } + if (isset($data['registry'])) { + $theme_data[$hook] = array_merge($theme_data[$hook], $data['registry']); + unset($data['registry']); + } + if (!empty($data)) { + if (!isset($theme_data[$hook]['dynamic'][$count])) { + $theme_data[$hook]['dynamic'][$count] = array(); + } + $theme_data[$hook]['dynamic'][$count] = array_merge($theme_data[$hook]['dynamic'][$count], $data); + } + } + return isset($hook) && !empty($theme_data[$hook]) ? $theme_data[$hook] : $theme_data; +} + +/** + * Sprinkles non-harmful markers within the page DOM. + * + * It looks like two markers have to be set to let jQuery select within the + * range of the markers. + */ +function devel_template_marker($data, $output) { + $id = 'theme-hook-'. form_clean_id($data['hook']) .'-'. devel_counter($data['hook'], TRUE); + $class = 'devel-dom-marker'; + $start_class = $class .' theme-hook-'. form_clean_id($data['hook']); + $end_class = $class .' end-theme-hook-'. form_clean_id($data['hook']); + return "$output"; +} + +// 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]; } // Menu callback. I would love prettier hierarchy browser for this. Index: jquery.devel.js =================================================================== RCS file: jquery.devel.js diff -N jquery.devel.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ jquery.devel.js 20 Aug 2007 06:31:20 -0000 @@ -0,0 +1,21 @@ + +Drupal.behaviors.browseTemplate = function (context) { + // Move to top of DOM then hide logs. + $('body').prepend($('#devel-theme-logs').addClass('devel-log-js').hide()); + + $('#template-log-link').click(function() { + $('#devel-theme-logs').slideDown('medium').click(function() { + $(this).hide(); + }); + }); + + $('#devel-theme-logs a.hook-pointer').click(function() { + var pointer = $(this).attr('id'); + $('.' + pointer).each(function() { + // Outlines do not affect the DOM.. + $('~ :not(.devel-dom-marker):eq(0)', this).css({border: '1px solid red'}); + }); + $(this.parentNode.parentNode.parentNode.parentNode).slideUp('fast'); + return false; + }); +}