diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php index 8fd3851..9a927ec 100644 --- a/core/lib/Drupal/Core/Form/FormErrorHandler.php +++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php @@ -24,7 +24,8 @@ class FormErrorHandler implements FormErrorHandlerInterface { use LinkGeneratorTrait; /** - * Associated array of elements with errors which need to be linked to from the error message + * Associated array of elements with errors which need to be linked to from + * the error message * * @var array[] */ @@ -60,6 +61,35 @@ public function handleFormErrors(array &$form, FormStateInterface $form_state) { } /** + * Stores the errors of each element directly on the element and keep a list + * of these elements + * + * We must provide a way for non-form functions to check the errors for a + * specific element. The most common usage of this is a #pre_render callback. + * + * @param array $elements + * An associative array containing the structure of a form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function setElementErrorsFromFormState(array &$elements, FormStateInterface &$form_state) { + // Recurse through all children. + foreach (Element::children($elements) as $key) { + if (isset($elements[$key]) && $elements[$key]) { + $this->setElementErrorsFromFormState($elements[$key], $form_state); + } + } + + // Store the errors for this element on the element directly and + // keep a list of elements with errors + // @todo get the element name differently when $elements['#name'] is not set + // like from #parents or the form structure + if (($elements['#errors'] = $form_state->getError($elements)) && !empty($elements['#name'])) { + $this->errorLinkElements[$elements['#name']] = $elements; + }; + } + + /** * Loops through and displays all form errors. * * @param \Drupal\Core\Form\FormStateInterface $form_state @@ -71,9 +101,18 @@ protected function displayErrorMessages(FormStateInterface $form_state, array $e $error_links = []; $errors = $form_state->getErrors(); - // Create error links for all visible element with titles and with errors + // Create error links foreach ($error_elements as $form_element) { - if (Element::isVisibleElement($form_element) && $title = FormElementHelper::getElementTitle($form_element)) { + $title = FormElementHelper::getElementTitle($form_element); + + // Only show links to erroneous elements that are visible + $is_visible_element = Element::isVisibleElement($form_element) ? TRUE : FALSE; + // Don't show links to elements which use their parent element for inline errors + $not_use_parent = empty($form_element['#error_use_parent']); + // Only show links for elements that have a title themselves or have children with a title + $has_title = !empty($title); + + if ($is_visible_element && $not_use_parent && $has_title) { $error_links[] = $this->l($title, Url::fromRoute('', [], [ 'fragment' => $form_element['#id'], 'external' => TRUE @@ -104,32 +143,6 @@ protected function displayErrorMessages(FormStateInterface $form_state, array $e } /** - * Stores the errors of each element directly on the element. - * - * We must provide a way for non-form functions to check the errors for a - * specific element. The most common usage of this is a #pre_render callback. - * - * @param array $elements - * An associative array containing the structure of a form element. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function setElementErrorsFromFormState(array &$elements, FormStateInterface &$form_state) { - // Recurse through all children. - foreach (Element::children($elements) as $key) { - if (isset($elements[$key]) && $elements[$key]) { - $this->setElementErrorsFromFormState($elements[$key], $form_state); - } - } - - // Store the possible errors for this element on the element directly and - // keep a list of elements with errors for the error links - if ($elements['#errors'] = $form_state->getError($elements)) { - $this->errorLinkElements[$elements['#name']] = $elements; - }; - } - - /** * Wraps drupal_set_message(). * * @codeCoverageIgnore diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php index 5a7429c..a6d50e1 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php @@ -241,7 +241,7 @@ protected function doTestAuthoringInfo() { 'content_translation[created]' => '19/11/1978', ); $this->drupalPostForm($entity->urlInfo('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode)); - $this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.'); + $this->assertTrue($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]', array(':class' => ' messages--error ')), 'Invalid values generate a form error message.'); $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode)); $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.'); $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.'); diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php index 51d7402..ea8c0c9 100644 --- a/core/modules/file/src/Tests/FileFieldValidateTest.php +++ b/core/modules/file/src/Tests/FileFieldValidateTest.php @@ -34,7 +34,8 @@ function testRequired() { $edit = array(); $edit['title[0][value]'] = $this->randomMachineName(); $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish')); - $this->assertRaw(t('!title field is required.', array('!title' => $field->getLabel())), 'Node save failed when required file field was empty.'); + $this->assertText('1 error has been found: Choose a file', 'Node save failed when required file field was empty.'); + $this->assertIdentical(1, count($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]//a', array(':class' => ' messages--error '))), 'There is one link in the error message.'); // Create a new node with the uploaded file. $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); @@ -55,7 +56,8 @@ function testRequired() { $edit = array(); $edit['title[0][value]'] = $this->randomMachineName(); $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish')); - $this->assertRaw(t('!title field is required.', array('!title' => $field->getLabel())), 'Node save failed when required multiple value file field was empty.'); + $this->assertText('1 error has been found: Choose a file', 'Node save failed when required multiple value file field was empty.'); + $this->assertIdentical(1, count($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]//a', array(':class' => ' messages--error '))), 'There is one link in the error message.'); // Create a new node with the uploaded file into the multivalue field. $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); diff --git a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php index 2769506..6bb11d5 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php @@ -44,11 +44,13 @@ public function testDisplayErrorMessages() { '#type' => 'textfield', '#title' => 'Test 1', '#parents' => ['test1'], + '#name' => 'test1', ]; $form['test2'] = [ '#type' => 'textfield', '#title' => 'Test 2', '#parents' => ['test2'], + '#name' => 'test2', ]; $form_state = new FormState(); $form_state->setErrorByName('test1', 'invalid'); @@ -75,6 +77,7 @@ public function testSetElementErrorsFromFormState() { '#type' => 'textfield', '#title' => 'Test', '#parents' => ['test'], + '#name' => 'test', ]; $form_state = new FormState(); $form_state->setErrorByName('test', 'invalid');