diff --git a/core/includes/common.inc b/core/includes/common.inc index 4668a90..de9483d 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5235,8 +5235,10 @@ function drupal_render(&$elements) { $elements['#children'] = ''; } // Call the element's #theme function if it is set. Then any children of the - // element have to be rendered there. - if (isset($elements['#theme'])) { + // element have to be rendered there. If the internal #render_children + // property is set, do not call the #theme function to prevent infinite + // recursion. + if (isset($elements['#theme']) && !isset($elements['#render_children'])) { $elements['#children'] = theme($elements['#theme'], $elements); } // If #theme was not set and the element has children, render them now. @@ -5274,7 +5276,9 @@ function drupal_render(&$elements) { // the #type 'page' render array from drupal_render_page() would render the // $page and wrap it into the html.tpl.php template without the attached // assets otherwise. - if (isset($elements['#theme_wrappers'])) { + // If the internal #render_children property is set, do not call the + // #theme_wrappers function(s) to prevent infinite recursion. + if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) { foreach ($elements['#theme_wrappers'] as $theme_wrapper) { $elements['#children'] = theme($theme_wrapper, $elements); } diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 0d0e739..2a83749 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1036,6 +1036,8 @@ function theme($hook, $variables = array()) { } else { $variables[$info['render element']] = $element; + // Give a hint to render engines to prevent infinite recursion. + $variables[$info['render element']]['#render_children'] = TRUE; } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php index 20ccc2b..664a73b 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php @@ -194,6 +194,27 @@ function testRegistryRebuild() { } /** + * Tests child element rendering for 'render element' theme hooks. + */ + function testDrupalRenderChildren() { + $element = array( + '#theme' => 'theme_test_render_element_children', + 'child' => array( + '#markup' => 'Foo', + ), + ); + $this->assertIdentical(theme('theme_test_render_element_children', $element), 'Foo', 'drupal_render() avoids #theme recursion loop when rendering a render element.'); + + $element = array( + '#theme_wrappers' => array('theme_test_render_element_children'), + 'child' => array( + '#markup' => 'Foo', + ), + ); + $this->assertIdentical(theme('theme_test_render_element_children', $element), 'Foo', 'drupal_render() avoids #theme_wrappers recursion loop when rendering a render element.'); + } + + /** * Tests theme can provide classes. */ function testClassLoading() { diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module index 137fdfc..d610a19 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -17,6 +17,9 @@ function theme_test_theme($existing, $type, $theme, $path) { $items['theme_test_foo'] = array( 'variables' => array('foo' => NULL), ); + $items['theme_test_render_element_children'] = array( + 'render element' => 'element', + ); return $items; } @@ -172,3 +175,17 @@ function theme_theme_test_foo($variables) { return $variables['foo']; } +/** + * Theme function for testing rendering of child elements via drupal_render(). + * + * Theme hooks defining a 'render element' add an internal '#render_children' + * property. When this property is found, drupal_render() avoids calling theme() + * on the top-level element to prevent infinite recursion. + * + * @param array $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + */ +function theme_theme_test_render_element_children($variables) { + return drupal_render($variables['element']); +}