diff --git a/core/includes/form.inc b/core/includes/form.inc index 63c517a..325c26d 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -207,7 +207,7 @@ function template_preprocess_fieldset(&$variables) { // Display any error messages. $variables['errors'] = NULL; - if (!empty($element['#errors']) && empty($element['#error_use_parent'])) { + if (!empty($element['#errors']) && empty($element['#error_no_message'])) { $variables['errors'] = $element['#errors']; } } @@ -438,7 +438,7 @@ function template_preprocess_form_element(&$variables) { // Display any error messages. $variables['errors'] = NULL; - if (!empty($element['#errors']) && empty($element['#error_use_parent'])) { + if (!empty($element['#errors']) && empty($element['#error_no_message'])) { $variables['errors'] = $element['#errors']; } diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php index f938a7e..a0060ed 100644 --- a/core/lib/Drupal/Core/Datetime/Element/Datelist.php +++ b/core/lib/Drupal/Core/Datetime/Element/Datelist.php @@ -265,7 +265,7 @@ public static function processDatelist(&$element, FormStateInterface $form_state '#attributes' => $element['#attributes'], '#options' => $options, '#required' => $element['#required'], - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, ); } diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php index 8af82f1..b8e4b69 100644 --- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php +++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php @@ -267,7 +267,7 @@ public static function processDatetime(&$element, FormStateInterface $form_state '#attributes' => $element['#attributes'] + $extra_attributes, '#required' => $element['#required'], '#size' => max(12, strlen($element['#value']['date'])), - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, ); // Allows custom callbacks to alter the element. @@ -299,7 +299,7 @@ public static function processDatetime(&$element, FormStateInterface $form_state '#attributes' => $element['#attributes'] + $extra_attributes, '#required' => $element['#required'], '#size' => 12, - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, ); // Allows custom callbacks to alter the element. diff --git a/core/lib/Drupal/Core/Form/FormElementHelper.php b/core/lib/Drupal/Core/Form/FormElementHelper.php index 9d3e193..013eaa1 100644 --- a/core/lib/Drupal/Core/Form/FormElementHelper.php +++ b/core/lib/Drupal/Core/Form/FormElementHelper.php @@ -28,7 +28,7 @@ class FormElementHelper { */ public static function getElementByName($name, array $form) { foreach (Element::children($form) as $key) { - if ($key === $name) { + if (implode('][', $form[$key]['#parents']) === $name) { return $form[$key]; } elseif ($element = static::getElementByName($name, $form[$key])) { diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php index 9691361..a4e3605 100644 --- a/core/lib/Drupal/Core/Form/FormErrorHandler.php +++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php @@ -24,14 +24,6 @@ class FormErrorHandler implements FormErrorHandlerInterface { use LinkGeneratorTrait; /** - * Array of elements with errors which need to be linked to from - * the error message. - * - * @var array[] - */ - protected $errorLinkElements = []; - - /** * Constructs a new FormErrorHandler. * * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation @@ -48,11 +40,14 @@ public function __construct(TranslationInterface $string_translation, LinkGenera * {@inheritdoc} */ public function handleFormErrors(array &$form, FormStateInterface $form_state) { - // After validation, loop through and assign each element its errors. - $this->setElementErrorsFromFormState($form, $form_state); + // After validation check if there are errors. + if ($errors = $form_state->getErrors()) { + // Display error messages for each element. + $this->displayErrorMessages($form, $form_state); - // Display error messages for each element. - $this->displayErrorMessages($form_state); + // Loop through and assign each element its errors. + $this->setElementErrorsFromFormState($form, $form_state); + } // Reset the list of elements with errors. $this->errorLinkElements = []; @@ -63,36 +58,38 @@ public function handleFormErrors(array &$form, FormStateInterface $form_state) { /** * Loops through and displays all form errors. * + * @param array $form + * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. - * @param array[] $error_elements - * An array of elements with errors. */ - protected function displayErrorMessages(FormStateInterface $form_state) { + protected function displayErrorMessages(array $form, FormStateInterface $form_state) { $error_links = []; $errors = $form_state->getErrors(); - // Create error links - foreach ($this->errorLinkElements as $form_element) { + // Loop through all form errors and check if we need to display a link. + foreach ($errors as $name => $error) { + $form_element = FormElementHelper::getElementByName($name, $form); $title = FormElementHelper::getElementTitle($form_element); // Only show links to erroneous elements that are visible. $is_visible_element = Element::isVisibleElement($form_element); - // And don't show links to elements which use their parent element for - // inline errors. - $not_use_parent = empty($form_element['#error_use_parent']); + // And don't show links to elements with suppressed messages. + // Most often their parent element is used for inline errors. + $show_message = empty($form_element['#error_no_message']); // And only show links for elements that have a title themselves or have // children with a title. $has_title = !empty($title); // And only show links for elements with an ID. $has_id = !empty($form_element['#id']); - if ($is_visible_element && $not_use_parent && $has_title && $has_id) { - $error_links[] = $this->l($title, Url::fromRoute('', [], [ - 'fragment' => $form_element['#id'], - 'external' => TRUE - ])); - unset($errors[implode('][', $form_element['#parents'])]); + if ($is_visible_element && $show_message && $has_title && $has_id) { + $error_links[] = $this->l($title, Url::fromRoute('', [], ['fragment' => $form_element['#id'], 'external' => TRUE])); + unset($errors[$name]); + } + // In any case hide any messages witch are suppressed. + elseif (!$show_message) { + unset($errors[$name]); } } @@ -118,14 +115,13 @@ protected function displayErrorMessages(FormStateInterface $form_state) { } /** - * Stores the errors of each element directly on the element and keeps a list - * of these elements. + * 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 array containing the structure of a form element. + * An associative array containing the structure of a form element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ @@ -137,11 +133,8 @@ protected function setElementErrorsFromFormState(array &$elements, FormStateInte } } - // Store the errors for this element on the element directly and - // keep a list of elements with errors. - if (($elements['#errors'] = $form_state->getError($elements))) { - $this->errorLinkElements[] = $elements; - } + // Store the errors for this element on the element directly. + $elements['#errors'] = $form_state->getError($elements); } /** diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php index 1cfa0b7..4a17d6b 100644 --- a/core/lib/Drupal/Core/Form/FormState.php +++ b/core/lib/Drupal/Core/Form/FormState.php @@ -1116,7 +1116,7 @@ public function clearErrors() { * {@inheritdoc} */ public function getError(array $element) { - if ($errors = $this->getErrors($this)) { + if ($errors = $this->getErrors()) { $parents = array(); foreach ($element['#parents'] as $parent) { $parents[] = $parent; diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php index 5e44ecb..e0ca1c6 100644 --- a/core/lib/Drupal/Core/Form/FormStateInterface.php +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php @@ -417,7 +417,8 @@ public static function hasAnyErrors(); * indicate which element needs to be changed and provide an error message. * This causes the Form API to not execute the form submit handlers, and * instead to re-display the form to the user with the corresponding elements - * rendered with an 'error' CSS class (shown as red by default). + * rendered with an 'error' CSS class (shown as red by default) and the error + * message near the element. * * The standard behavior of this method can be changed if a button provides * the #limit_validation_errors property. Multistep forms not wanting to diff --git a/core/lib/Drupal/Core/Render/Element/Checkboxes.php b/core/lib/Drupal/Core/Render/Element/Checkboxes.php index f3a2872..5581e29 100644 --- a/core/lib/Drupal/Core/Render/Element/Checkboxes.php +++ b/core/lib/Drupal/Core/Render/Element/Checkboxes.php @@ -75,7 +75,7 @@ public static function processCheckboxes(&$element, FormStateInterface $form_sta '#attributes' => $element['#attributes'], '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, // Errors should only be shown on the parent checkboxes element. - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, '#weight' => $weight, ); } diff --git a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php index 40ad21d..950cad2 100644 --- a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php +++ b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php @@ -54,7 +54,7 @@ public static function processPasswordConfirm(&$element, FormStateInterface $for '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'], '#required' => $element['#required'], '#attributes' => array('class' => array('password-field')), - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, ); $element['pass2'] = array( '#type' => 'password', @@ -62,7 +62,7 @@ public static function processPasswordConfirm(&$element, FormStateInterface $for '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'], '#required' => $element['#required'], '#attributes' => array('class' => array('password-confirm')), - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, ); $element['#element_validate'] = array(array(get_called_class(), 'validatePasswordConfirm')); $element['#tree'] = TRUE; diff --git a/core/lib/Drupal/Core/Render/Element/Radios.php b/core/lib/Drupal/Core/Render/Element/Radios.php index ac9c563..7dda24e 100644 --- a/core/lib/Drupal/Core/Render/Element/Radios.php +++ b/core/lib/Drupal/Core/Render/Element/Radios.php @@ -69,7 +69,7 @@ public static function processRadios(&$element, FormStateInterface $form_state, '#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)), '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, // Errors should only be shown on the parent radios element. - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, '#weight' => $weight, ); } diff --git a/core/modules/file/file.module b/core/modules/file/file.module index ad7de3f..20282ca 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1149,7 +1149,7 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state) $destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL; if (isset($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) { \Drupal::logger('file')->notice('The upload directory %directory for the file field !name could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $destination, '!name' => $element['#field_name'])); - $form_state->setErrorByName($element['#field_name'], t('The file could not be uploaded.')); + $form_state->setError($element, t('The file could not be uploaded.')); return FALSE; } @@ -1157,16 +1157,25 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state) $files_uploaded = $element['#multiple'] && count(array_filter($file_upload)) > 0; $files_uploaded |= !$element['#multiple'] && !empty($file_upload); if ($files_uploaded) { + // Temporarily store the current error messages. + // This is needed to be able to intercept errors generated during file save, + // while keeping all other errors intact. + $error_messages = drupal_get_messages('error'); if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) { \Drupal::logger('file')->notice('The file upload failed. %upload', array('%upload' => $upload_name)); - $form_state->setErrorByName($element['#field_name'], t('Files in the !name field were unable to be uploaded.', array('!name' => $element['#title']))); + $form_state->setError($element, t('Files in the !name field were unable to be uploaded.', array('!name' => $element['#title']))); return array(); } - // Intercept possible errors, to be able to create inline errors. + // Intercept possible new errors, to be able to create inline errors. if ($errors = drupal_get_messages('error')) { // File errors are aggregated into one item, so only get the first. - $form_state->setErrorByName($element['#field_name'], reset($errors['error'])); + $form_state->setError($element, reset($errors['error'])); + } + + // Restore earlier error messages. + foreach($error_messages['error'] as $error) { + drupal_set_message($error, 'error'); } // Value callback expects FIDs to be keys. diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php index 0c0d17d..afde5af 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -233,7 +233,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st '#multiple' => $element['#multiple'], '#theme_wrappers' => [], '#weight' => -10, - '#error_use_parent' => TRUE, + '#error_no_message' => TRUE, ]; if (!empty($fids) && $element['#files']) { diff --git a/core/themes/bartik/css/components/form.css b/core/themes/bartik/css/components/form.css index 858dbd7..4574da0 100644 --- a/core/themes/bartik/css/components/form.css +++ b/core/themes/bartik/css/components/form.css @@ -270,12 +270,12 @@ input.form-submit:focus { margin-right: 0; } -/* Form error styles */ +/* Form error styles. */ .form-item textarea.error + .cke { border: 2px solid red; } -/* Form error message styles */ +/* Form error message styles. */ .form-error-message { color: #ea2800; }