diff --git a/includes/theme.inc b/includes/theme.inc index ebe5817..bd93af2 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1029,6 +1029,7 @@ function theme($hook, $variables = array()) { } $hook = $candidate; } + $theme_hook_original = $hook; // If there's no implementation, check for more generic fallbacks. If there's // still no implementation, log an error and return an empty string. @@ -1090,6 +1091,8 @@ function theme($hook, $variables = array()) { $variables += array($info['render element'] => array()); } + $variables['theme_hook_original'] = $theme_hook_original; + // Invoke the variable processors, if any. The processors may specify // 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 @@ -1198,7 +1201,12 @@ function theme($hook, $variables = array()) { if (isset($info['path'])) { $template_file = $info['path'] . '/' . $template_file; } - $output = $render_function($template_file, $variables); + if (variable_get('theme_debug_output', FALSE)) { + $output = _theme_render_template_debug($render_function, $template_file, $variables, $extension); + } + else { + $output = $render_function($template_file, $variables); + } } // restore path_to_theme() @@ -1521,6 +1529,72 @@ function theme_render_template($template_file, $variables) { } /** + * Renders a template for any engine. + * + * Includes the possibility to get debug output by setting the + * theme_debug_output variable to TRUE. + * + * @param string $template_function + * The function to call for rendering the template. + * @param string $template_file + * The filename of the template to render. + * @param array $variables + * A keyed array of variables that will appear in the output. + * @param string $extension + * The extension used by the theme engine for template files. + * + * @return + * The output generated by the template including debug information. + */ +function _theme_render_template_debug($template_function, $template_file, $variables, $extension) { + $output = array( + 'debug_prefix' => '', + 'debug_info' => '', + 'rendered_markup' => call_user_func($template_function, $template_file, $variables), + 'debug_suffix' => '', + ); + $output['debug_prefix'] .= "\n\n"; + $output['debug_prefix'] .= "\n"; + // If there are theme suggestions, reverse the array so more specific + // suggestions are shown first. + if (!empty($variables['theme_hook_suggestions'])) { + $variables['theme_hook_suggestions'] = array_reverse($variables['theme_hook_suggestions']); + } + // Add debug output for directly called suggestions like + // '#theme' => 'comment__node__article'. + if (strpos($variables['theme_hook_original'], '__') !== FALSE) { + $derived_suggestions[] = $hook = $variables['theme_hook_original']; + while ($pos = strrpos($hook, '__')) { + $hook = substr($hook, 0, $pos); + $derived_suggestions[] = $hook; + } + // Get the value of the base hook (last derived suggestion) and append it + // to the end of all theme suggestions. + $base_hook = array_pop($derived_suggestions); + $variables['theme_hook_suggestions'] = array_merge($derived_suggestions, $variables['theme_hook_suggestions']); + $variables['theme_hook_suggestions'][] = $base_hook; + } + if (!empty($variables['theme_hook_suggestions'])) { + $current_template = basename($template_file); + $suggestions = $variables['theme_hook_suggestions']; + // Only add the original theme hook if it wasn't a directly called + // suggestion. + if (strpos($variables['theme_hook_original'], '__') === FALSE) { + $suggestions[] = $variables['theme_hook_original']; + } + foreach ($suggestions as &$suggestion) { + $template = strtr($suggestion, '_', '-') . $extension; + $prefix = ($template == $current_template) ? 'x' : '*'; + $suggestion = $prefix . ' ' . $template; + } + $output['debug_info'] .= "\n"; + } + $output['debug_info'] .= "\n\n"; + $output['debug_suffix'] .= "\n\n\n"; + return implode('', $output); +} + +/** * Enables a given list of themes. * * @param $theme_list diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test index 519a7a9..29c322e 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -500,3 +500,68 @@ class ThemeRegistryTestCase extends DrupalWebTestCase { $this->assertTrue($registry['theme_test_template_test_2'], 'Offset was returned correctly from the theme registry'); } } + +/** + * Tests for theme debug markup. + */ +class ThemeDebugMarkupTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Theme debug markup', + 'description' => 'Tests theme debug markup output.', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test', 'node'); + theme_enable(array('test_theme')); + } + + /** + * Tests debug markup added to template output. + */ + function testDebugOutput() { + variable_set('theme_default', 'test_theme'); + // Enable the debug output. + variable_set('theme_debug_output', TRUE); + + $registry = theme_get_registry(); + $extension = '.tpl.php'; + // Populate array of templates. + $templates = drupal_find_theme_templates($registry, $extension, drupal_get_path('theme', 'test_theme')); + $templates += drupal_find_theme_templates($registry, $extension, drupal_get_path('module', 'node')); + + // Create a node and test different features of the debug markup. + $node = $this->drupalCreateNode(); + $this->drupalGet('node/' . $node->nid); + $this->assertRaw('', 'Twig debug markup found in theme output when debug is enabled.'); + $this->assertRaw("CALL: theme('node')", 'Theme call information found.'); + $this->assertRaw('x node--1' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node' . $extension, 'Suggested template files found in order and node ID specific template shown as current template.'); + $template_filename = $templates['node__1']['path'] . '/' . $templates['node__1']['template'] . $extension; + $this->assertRaw("BEGIN OUTPUT from '$template_filename'", 'Full path to current template file found.'); + + // Create another node and make sure the template suggestions shown in the + // debug markup are correct. + $node2 = $this->drupalCreateNode(); + $this->drupalGet('node/' . $node2->nid); + $this->assertRaw('* node--2' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' x node' . $extension, 'Suggested template files found in order and base template shown as current template.'); + + // Create another node and make sure the template suggestions shown in the + // debug markup are correct. + $node3 = $this->drupalCreateNode(); + $build = array('#theme' => 'node__foo__bar'); + $build += node_view($node3); + $output = drupal_render($build); + $this->assertTrue(strpos($output, "CALL: theme('node__foo__bar')") !== FALSE, 'Theme call information found.'); + $this->assertTrue(strpos($output, '* node--foo--bar' . $extension . PHP_EOL . ' * node--foo' . $extension . PHP_EOL . ' * node--3' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.'); + + // Disable theme debug. + variable_set('theme_debug_output', FALSE); + + $this->drupalGet('node/' . $node->nid); + $this->assertNoRaw('', 'Twig debug markup not found in theme output when debug is disabled.'); + } + +}