diff --git a/core/includes/form.inc b/core/includes/form.inc index c33ff6f..6de7e58 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -10,7 +10,9 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Url; use Drupal\Core\Database\Database; +use Drupal\Core\Form\FormElementHelper; use Drupal\Core\Language\Language; +use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; use Drupal\Core\Utility\Color; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -1076,6 +1078,7 @@ function form_process_password_confirm($element) { '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'], '#required' => $element['#required'], '#attributes' => array('class' => array('password-field')), + '#error_use_parent' => TRUE, ); $element['pass2'] = array( '#type' => 'password', @@ -1083,6 +1086,7 @@ function form_process_password_confirm($element) { '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'], '#required' => $element['#required'], '#attributes' => array('class' => array('password-confirm')), + '#error_use_parent' => TRUE, ); $element['#element_validate'] = array('password_confirm_validate'); $element['#tree'] = TRUE; @@ -1186,6 +1190,8 @@ function form_process_radios($element) { '#parents' => $element['#parents'], '#id' => drupal_html_id('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, '#weight' => $weight, ); } @@ -1336,6 +1342,8 @@ function form_process_checkboxes($element) { '#default_value' => isset($value[$key]) ? $key : NULL, '#attributes' => $element['#attributes'], '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + // Errors should only be shown on the parent checkboxes element. + '#error_use_parent' => TRUE, '#weight' => $weight, ); } @@ -2556,6 +2564,36 @@ function form_pre_render_color($element) { } /** + * Preprocesses variables for theme_form(). + * + * @param array $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + * + * @ingroup themeable + */ +function template_preprocess_form(&$variables) { + if (!empty($variables['element']['#errors'])) { + $error_links = array(); + // Loop through all form errors, and display a link for each error that + // is associated with a specific form element. + foreach ($variables['element']['#errors'] as $key => $error) { + if ($element = FormElementHelper::getElementByName($key, $variables['element'])) { + $title = FormElementHelper::getElementTitle($element); + $error_links[] = l($title, '', array('fragment' => 'edit-' . str_replace('_', '-', $key), 'external' => TRUE)); + } + else { + drupal_set_message($error, 'error'); + } + } + + if (!empty($error_links)) { + drupal_set_message(format_plural(count($error_links), '1 error has been found', '@count errors have been found') . ': ' . implode(', ', $error_links), 'error'); + } + } +} + +/** * Returns HTML for a form. * * @param $variables @@ -2771,6 +2809,13 @@ function template_preprocess_form_element(&$variables) { $variables['attributes']['class'][] = 'form-disabled'; } + // Display any error messages. + if (!empty($element['#errors']) && empty($element['#error_use_parent'])) { + // Add a class if an error exists. + $attributes['class'][] = 'form-error'; + $output .= ' ' . theme('form_error_message', array('element' => $element)); + } + // If #title is not set, we don't display any label or required marker. if (!isset($element['#title'])) { $element['#title_display'] = 'none'; @@ -2873,6 +2918,25 @@ function theme_form_element_label($variables) { } /** + * Returns HTML for an inline error associated with a specific form element. + * + * @param array $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + * Properties used: '#error'. + * + * @return string + * + * @ingroup themeable + */ +function theme_form_error_message($variables) { + $output = '
'; + $output .= '' . t('Error') . ': ' . $variables['element']['#errors'] . ''; + $output .= '
'; + return $output; +} + +/** * Sets a form element's class attribute. * * Adds 'required' and 'error' classes as needed. diff --git a/core/includes/theme.inc b/core/includes/theme.inc index aa558b2..57bed6b 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2705,6 +2705,9 @@ function drupal_common_theme() { 'form_element_label' => array( 'render element' => 'element', ), + 'form_error_message' => array( + 'render element' => 'element', + ), 'vertical_tabs' => array( 'render element' => 'element', 'template' => 'vertical-tabs', diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 8e8a82a..ddcfbcc 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -860,6 +860,9 @@ public function validateForm($form_id, &$form, &$form_state) { } $form_state['values'] = $values; } + if (!$form_state['programmed']) { + $form['#errors'] = $this->getErrors($form_state); + } } /** @@ -1170,6 +1173,16 @@ public function executeHandlers($type, &$form, &$form_state) { */ public function setErrorByName($name, array &$form_state, $message = '') { if (!isset($form_state['errors'][$name])) { + // This is only used by errors set in submit handlers. + // @todo Unlike errors set during validation, these errors will not + // directly correspond to their input element, and will not interrupt + // submission. We should consider limiting usage of form errors to + // validation only, and encourage usage of drupal_set_message() in + // submit handlers. + if ($message && isset($form_state['build_info']['form_id']) && !empty($this->validatedForms[$form_state['build_info']['form_id']])) { + $this->drupalSetMessage($message, 'error'); + } + $record = TRUE; if (isset($form_state['limit_validation_errors'])) { // #limit_validation_errors is an array of "sections" within which user @@ -1197,9 +1210,6 @@ public function setErrorByName($name, array &$form_state, $message = '') { if ($record) { $form_state['errors'][$name] = $message; $this->request->attributes->set('_form_errors', TRUE); - if ($message) { - $this->drupalSetMessage($message, 'error'); - } } } diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc index 876c9f8..743b5cc 100644 --- a/core/modules/shortcut/shortcut.admin.inc +++ b/core/modules/shortcut/shortcut.admin.inc @@ -117,7 +117,7 @@ function shortcut_set_switch_validate($form, &$form_state) { if ($form_state['values']['set'] == 'new') { // Check to prevent creating a shortcut set with an empty title. if (trim($form_state['values']['label']) == '') { - form_set_error('new', $form_state, t('The new set label is required.')); + form_set_error('label', $form_state, t('The new set label is required.')); } // Check to prevent a duplicate title. if (shortcut_set_title_exists($form_state['values']['label'])) { diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css index 9a0153d..b70e9e2 100644 --- a/core/modules/system/css/system.theme.css +++ b/core/modules/system/css/system.theme.css @@ -44,6 +44,16 @@ td.active { /** * Markup generated by Form API. */ +.form-error { + background-color: #fef5f1; + border: 1px solid #ed541d; + color: #8c2e0b; + padding: 10px; +} +.form-error-message { + margin-bottom: 10px; + min-height: 25px; +} .form-item, .form-actions { margin-top: 1em;