From 3b1f85aa34c6dde064f82bb94396f42592ef18d6 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sun, 19 May 2013 16:34:52 -0700
Subject: [PATCH] Issue #1920886 by Cottser, Fabianx: Drupal_render() should
 only render the child elements when rendering a 'render element' for the
 first time.

---
 core/includes/common.inc                      | 10 ++++++---
 core/includes/theme.inc                       |  2 ++
 .../Drupal/system/Tests/Theme/ThemeTest.php   | 21 +++++++++++++++++++
 .../modules/theme_test/theme_test.module      | 17 +++++++++++++++
 4 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/core/includes/common.inc b/core/includes/common.inc
index e03ba330fe9a..5e8e2d1e683f 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 c1792e861cc6..721453ad4a8c 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 20ccc2bff8a3..664a73b64615 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 137fdfcc8532..d610a19c3281 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']);
+}
-- 
GitLab