diff --git a/core/includes/common.inc b/core/includes/common.inc index e03ba330fe9a654e74ab69c4916e32199c39473f..5e8e2d1e683ffb59cba0c01f399f015729e5b995 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5233,8 +5233,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. @@ -5272,7 +5274,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 c1792e861cc69c990ffdc43c61d03df5515e568c..721453ad4a8ccdce0ad39eb3662915b3e04c5dd1 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 20ccc2bff8a377d2ef06660ac9f16367a98febd2..664a73b646158909f59f2e1bfb8a123df32b0083 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php @@ -193,6 +193,27 @@ function testRegistryRebuild() { $this->assertIdentical(theme('theme_test_foo', array('foo' => 'c')), 'c', 'The theme registry contains theme_test_foo again after re-enabling the module.'); } + /** + * 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. */ 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 137fdfcc8532f3b13d4a6ddaf79fb1f630874829..d610a19c328139c46d6f21475b8d4ede6ca9e321 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']); +}