diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 0bf1d63..3277b38 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -209,6 +209,25 @@ protected function doRender(&$elements, $is_root_call = FALSE) { return ''; } + // Render only the children if the #render_children property is set. + if (isset($elements['#render_children'])) { + // A non-empty #children property takes precedence. + if (!empty($elements['#children'])) { + $children = ['#children']; + } + else { + $children = Element::children($elements); + } + + if (empty($children)) { + return ''; + } + + // It is okay to modify the original elements for this, because this will + // be called from a twig template, where the variable is passed by value. + $elements = array_intersect_key($elements, array_flip($children)); + } + if (!isset($elements['#access']) && isset($elements['#access_callback'])) { if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) { $elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']); @@ -428,10 +447,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) { } // Call the element's #theme function if it is set. Then any children of the - // 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 ($theme_is_implemented && !isset($elements['#render_children'])) { + // element have to be rendered there. + if ($theme_is_implemented) { $elements['#children'] = $this->theme->render($elements['#theme'], $elements); // If ThemeManagerInterface::render() returns FALSE this means that the @@ -440,10 +457,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { $theme_is_implemented = ($elements['#children'] !== FALSE); } - // If #theme is not implemented or #render_children is set and the element - // has an empty #children attribute, render the children now. This is the - // same process as Renderer::render() but is inlined for speed. - if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) { + // If #theme is not implemented and the element has an empty #children + // attribute, render the children now. This is the same process as + // Renderer::render() but is inlined for speed. + if (!$theme_is_implemented && empty($elements['#children'])) { foreach ($children as $key) { $elements['#children'] .= $this->doRender($elements[$key]); } @@ -467,9 +484,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { // because the #type 'page' render array from drupal_prepare_page() would // render the $page and wrap it into the html.html.twig template without the // attached assets otherwise. - // 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'])) { + if (isset($elements['#theme_wrappers'])) { foreach ($elements['#theme_wrappers'] as $key => $value) { // If the value of a #theme_wrappers item is an array then the theme // hook is found in the key of the item and the value contains attribute diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php index 9d43060..de3b1c5 100644 --- a/core/modules/file/src/Tests/FileFieldValidateTest.php +++ b/core/modules/file/src/Tests/FileFieldValidateTest.php @@ -71,8 +71,10 @@ function testFileMaxSize() { $field_name = strtolower($this->randomMachineName()); $this->createFileField($field_name, 'node', $type_name, array(), array('required' => '1')); - $small_file = $this->getTestFile('text', 131072); // 128KB. - $large_file = $this->getTestFile('text', 1310720); // 1.2MB + // 128KB. + $small_file = $this->getTestFile('text', 131072); + // 1.2MB. + $large_file = $this->getTestFile('text', 1310720); // Test uploading both a large and small file with different increments. $sizes = array( @@ -185,4 +187,25 @@ public function testFileRemoval() { $this->assertText('Article ' . $node->getTitle() . ' has been updated.'); } + /** + * Test the validation message is displayed only once for ajax uploads. + */ + public function testAJAXValidationMessage() { + $field_name = strtolower($this->randomMachineName()); + $this->createFileField($field_name, 'node', 'article'); + + $this->drupalGet('node/add/article'); + /** @var \Drupal\file\FileInterface $image_file */ + $image_file = $this->getTestFile('image'); + $edit = array( + 'files[' . $field_name . '_0]' => $this->container->get('file_system')->realpath($image_file->getFileUri()), + 'title[0][value]' => $this->randomMachineName(), + ); + $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_0_upload_button'); + $elements = $this->xpath('//div[contains(@class, :class)]', array( + ':class' => 'messages--error', + )); + $this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.'); + } + } diff --git a/core/modules/image/src/Tests/ImageFieldValidateTest.php b/core/modules/image/src/Tests/ImageFieldValidateTest.php index 2532524..c873f56 100644 --- a/core/modules/image/src/Tests/ImageFieldValidateTest.php +++ b/core/modules/image/src/Tests/ImageFieldValidateTest.php @@ -8,6 +8,7 @@ * @group image */ class ImageFieldValidateTest extends ImageFieldTestBase { + /** * Test min/max resolution settings. */ @@ -102,4 +103,44 @@ function testRequiredAttributes() { $this->assertNoText(t('Title field is required.')); } + /** + * Returns field settings. + * + * @param int[] $min_resolution + * The minimum width and height resolution setting. + * @param int[] $max_resolution + * The maximum width and height resolution setting. + * + * @return array + */ + protected function getFieldSettings($min_resolution, $max_resolution) { + return [ + 'max_resolution' => $max_resolution['width'] . 'x' . $max_resolution['height'], + 'min_resolution' => $min_resolution['width'] . 'x' . $min_resolution['height'], + 'alt_field' => 0, + ]; + } + + /** + * Test the validation message is displayed only once for ajax uploads. + */ + public function testAJAXValidationMessage() { + $field_name = strtolower($this->randomMachineName()); + $this->createImageField($field_name, 'article'); + + $this->drupalGet('node/add/article'); + /** @var \Drupal\file\FileInterface[] $text_files */ + $text_files = $this->drupalGetTestFiles('text'); + $text_file = reset($text_files); + $edit = array( + 'files[' . $field_name . '_0]' => $this->container->get('file_system')->realpath($text_file->uri), + 'title[0][value]' => $this->randomMachineName(), + ); + $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_0_upload_button'); + $elements = $this->xpath('//div[contains(@class, :class)]', array( + ':class' => 'messages--error', + )); + $this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.'); + } + }