Issue #1493324 by tim.plunkett, dmsmidt, mgifford, bleen18, davidhernandez, crasx, mparker17, stefan.r, YesCT, joelpittet, tstoeckler, larowlan, vijaycs85, swentel, rpayanm, Bojhan, LewisNyman, emma.maria, BarisW, njbarrett, rteijeiro, nod_, sun, joshtaylor, mrjmd, webchick, marcvangend, kattekrab, SKAUGHT, bowersox, andrewmacpherson, Manjit.Singh, RavindraSingh, Wim Leers, BLadwin, aspilicious, mortendk, mausolos, jessebeach, Gábor Hojtsy, anandps, falcon03, franz, andypost, rooby, rootwork, Cottser, Xano: Inline form errors for accessibility and UX
parent
3dd383704d
commit
215b967a44
|
@ -285,10 +285,13 @@ services:
|
||||||
arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token']
|
arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token']
|
||||||
form_validator:
|
form_validator:
|
||||||
class: Drupal\Core\Form\FormValidator
|
class: Drupal\Core\Form\FormValidator
|
||||||
arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form']
|
arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form', '@form_error_handler']
|
||||||
form_submitter:
|
form_submitter:
|
||||||
class: Drupal\Core\Form\FormSubmitter
|
class: Drupal\Core\Form\FormSubmitter
|
||||||
arguments: ['@request_stack', '@url_generator']
|
arguments: ['@request_stack', '@url_generator']
|
||||||
|
form_error_handler:
|
||||||
|
class: Drupal\Core\Form\FormErrorHandler
|
||||||
|
arguments: ['@string_translation', '@link_generator']
|
||||||
form_cache:
|
form_cache:
|
||||||
class: Drupal\Core\Form\FormCache
|
class: Drupal\Core\Form\FormCache
|
||||||
arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@request_stack', '@page_cache_request_policy']
|
arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@request_stack', '@page_cache_request_policy']
|
||||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Component\Utility\UrlHelper;
|
||||||
use Drupal\Component\Utility\Xss;
|
use Drupal\Component\Utility\Xss;
|
||||||
use Drupal\Core\Database\Database;
|
use Drupal\Core\Database\Database;
|
||||||
use Drupal\Core\Form\FormStateInterface;
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\Core\Form\FormElementHelper;
|
||||||
use Drupal\Core\Form\OptGroup;
|
use Drupal\Core\Form\OptGroup;
|
||||||
use Drupal\Core\Render\Element;
|
use Drupal\Core\Render\Element;
|
||||||
use Drupal\Core\Template\Attribute;
|
use Drupal\Core\Template\Attribute;
|
||||||
|
@ -203,6 +204,12 @@ function template_preprocess_fieldset(&$variables) {
|
||||||
// Add the description's id to the fieldset aria attributes.
|
// Add the description's id to the fieldset aria attributes.
|
||||||
$variables['attributes']['aria-describedby'] = $description_id;
|
$variables['attributes']['aria-describedby'] = $description_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display any error messages.
|
||||||
|
$variables['errors'] = NULL;
|
||||||
|
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
|
||||||
|
$variables['errors'] = $element['#errors'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -407,7 +414,7 @@ function template_preprocess_form_element(&$variables) {
|
||||||
);
|
);
|
||||||
$variables['attributes'] = $element['#wrapper_attributes'];
|
$variables['attributes'] = $element['#wrapper_attributes'];
|
||||||
|
|
||||||
// Add element #id for #type 'item'.
|
// Add element #id for #type 'item' and 'password_confirm'.
|
||||||
if (isset($element['#markup']) && !empty($element['#id'])) {
|
if (isset($element['#markup']) && !empty($element['#id'])) {
|
||||||
$variables['attributes']['id'] = $element['#id'];
|
$variables['attributes']['id'] = $element['#id'];
|
||||||
}
|
}
|
||||||
|
@ -423,6 +430,12 @@ function template_preprocess_form_element(&$variables) {
|
||||||
// Pass elements disabled status to template.
|
// Pass elements disabled status to template.
|
||||||
$variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
|
$variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
|
||||||
|
|
||||||
|
// Display any error messages.
|
||||||
|
$variables['errors'] = NULL;
|
||||||
|
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
|
||||||
|
$variables['errors'] = $element['#errors'];
|
||||||
|
}
|
||||||
|
|
||||||
// If #title is not set, we don't display any label.
|
// If #title is not set, we don't display any label.
|
||||||
if (!isset($element['#title'])) {
|
if (!isset($element['#title'])) {
|
||||||
$element['#title_display'] = 'none';
|
$element['#title_display'] = 'none';
|
||||||
|
|
|
@ -500,6 +500,12 @@ function template_preprocess_datetime_wrapper(&$variables) {
|
||||||
$variables['title'] = $element['#title'];
|
$variables['title'] = $element['#title'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display any error messages.
|
||||||
|
$variables['errors'] = NULL;
|
||||||
|
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
|
||||||
|
$variables['errors'] = $element['#errors'];
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($element['#description'])) {
|
if (!empty($element['#description'])) {
|
||||||
$variables['description'] = $element['#description'];
|
$variables['description'] = $element['#description'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,6 +265,7 @@ class Datelist extends DateElementBase {
|
||||||
'#attributes' => $element['#attributes'],
|
'#attributes' => $element['#attributes'],
|
||||||
'#options' => $options,
|
'#options' => $options,
|
||||||
'#required' => $element['#required'],
|
'#required' => $element['#required'],
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -267,6 +267,7 @@ class Datetime extends DateElementBase {
|
||||||
'#attributes' => $element['#attributes'] + $extra_attributes,
|
'#attributes' => $element['#attributes'] + $extra_attributes,
|
||||||
'#required' => $element['#required'],
|
'#required' => $element['#required'],
|
||||||
'#size' => max(12, strlen($element['#value']['date'])),
|
'#size' => max(12, strlen($element['#value']['date'])),
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allows custom callbacks to alter the element.
|
// Allows custom callbacks to alter the element.
|
||||||
|
@ -298,6 +299,7 @@ class Datetime extends DateElementBase {
|
||||||
'#attributes' => $element['#attributes'] + $extra_attributes,
|
'#attributes' => $element['#attributes'] + $extra_attributes,
|
||||||
'#required' => $element['#required'],
|
'#required' => $element['#required'],
|
||||||
'#size' => 12,
|
'#size' => 12,
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Allows custom callbacks to alter the element.
|
// Allows custom callbacks to alter the element.
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Form\FormElementHelper.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Form;
|
||||||
|
|
||||||
|
use Drupal\Core\Render\Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides common functionality for form elements.
|
||||||
|
*/
|
||||||
|
class FormElementHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a form element.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* The name of the form element. If the #parents property of your form
|
||||||
|
* element is ['foo', 'bar', 'baz'] then the name is 'foo][bar][baz'.
|
||||||
|
* @param array $form
|
||||||
|
* An associative array containing the structure of the form.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The form element.
|
||||||
|
*/
|
||||||
|
public static function getElementByName($name, array $form) {
|
||||||
|
foreach (Element::children($form) as $key) {
|
||||||
|
if (implode('][', $form[$key]['#parents']) === $name) {
|
||||||
|
return $form[$key];
|
||||||
|
}
|
||||||
|
elseif ($element = static::getElementByName($name, $form[$key])) {
|
||||||
|
return $element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the title for the element.
|
||||||
|
*
|
||||||
|
* If the element has no title, this will recurse through all children of the
|
||||||
|
* element until a title is found.
|
||||||
|
*
|
||||||
|
* @param array $element
|
||||||
|
* An associative array containing the properties of the form element.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The title of the element, or an empty string if none is found.
|
||||||
|
*/
|
||||||
|
public static function getElementTitle(array $element) {
|
||||||
|
$title = '';
|
||||||
|
if (isset($element['#title'])) {
|
||||||
|
$title = $element['#title'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
foreach (Element::children($element) as $key) {
|
||||||
|
if ($title = static::getElementTitle($element[$key])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Form\FormErrorHandler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Form;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\SafeMarkup;
|
||||||
|
use Drupal\Core\Render\Element;
|
||||||
|
use Drupal\Core\Routing\LinkGeneratorTrait;
|
||||||
|
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||||
|
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||||
|
use Drupal\Core\Url;
|
||||||
|
use Drupal\Core\Utility\LinkGeneratorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles form errors.
|
||||||
|
*/
|
||||||
|
class FormErrorHandler implements FormErrorHandlerInterface {
|
||||||
|
|
||||||
|
use StringTranslationTrait;
|
||||||
|
use LinkGeneratorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new FormErrorHandler.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||||
|
* The string translation service.
|
||||||
|
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
|
||||||
|
* The link generation service.
|
||||||
|
*/
|
||||||
|
public function __construct(TranslationInterface $string_translation, LinkGeneratorInterface $link_generator) {
|
||||||
|
$this->stringTranslation = $string_translation;
|
||||||
|
$this->linkGenerator = $link_generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function handleFormErrors(array &$form, FormStateInterface $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);
|
||||||
|
|
||||||
|
// Loop through and assign each element its errors.
|
||||||
|
$this->setElementErrorsFromFormState($form, $form_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
|
||||||
|
$error_links = [];
|
||||||
|
$errors = $form_state->getErrors();
|
||||||
|
// 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);
|
||||||
|
// Only show links for elements that have a title themselves or have
|
||||||
|
// children with a title.
|
||||||
|
$has_title = !empty($title);
|
||||||
|
// Only show links for elements with an ID.
|
||||||
|
$has_id = !empty($form_element['#id']);
|
||||||
|
|
||||||
|
// Do not show links to elements with suppressed messages. Most often
|
||||||
|
// their parent element is used for inline errors.
|
||||||
|
if (!empty($form_element['#error_no_message'])) {
|
||||||
|
unset($errors[$name]);
|
||||||
|
}
|
||||||
|
elseif ($is_visible_element && $has_title && $has_id) {
|
||||||
|
// We need to pass this through SafeMarkup::escape() so
|
||||||
|
// drupal_set_message() does not encode the links.
|
||||||
|
$error_links[] = SafeMarkup::escape($this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE])));
|
||||||
|
unset($errors[$name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set normal error messages for all remaining errors.
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
$this->drupalSetMessage($error, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($error_links)) {
|
||||||
|
$message = $this->formatPlural(count($error_links), '1 error has been found: !errors', '@count errors have been found: !errors', [
|
||||||
|
'!errors' => SafeMarkup::set(implode(', ', $error_links)),
|
||||||
|
]);
|
||||||
|
$this->drupalSetMessage($message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 errors for this element on the element directly.
|
||||||
|
$elements['#errors'] = $form_state->getError($elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps drupal_set_message().
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
|
||||||
|
drupal_set_message($message, $type, $repeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Form\FormErrorHandlerInterface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Form;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an interface for handling form errors.
|
||||||
|
*/
|
||||||
|
interface FormErrorHandlerInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles form errors after form validation.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function handleFormErrors(array &$form, FormStateInterface $form_state);
|
||||||
|
|
||||||
|
}
|
|
@ -1090,9 +1090,6 @@ class FormState implements FormStateInterface {
|
||||||
$errors[$name] = $message;
|
$errors[$name] = $message;
|
||||||
$this->errors = $errors;
|
$this->errors = $errors;
|
||||||
static::setAnyErrors();
|
static::setAnyErrors();
|
||||||
if ($message) {
|
|
||||||
$this->drupalSetMessage($message, 'error');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,7 +1116,7 @@ class FormState implements FormStateInterface {
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getError(array $element) {
|
public function getError(array $element) {
|
||||||
if ($errors = $this->getErrors($this)) {
|
if ($errors = $this->getErrors()) {
|
||||||
$parents = array();
|
$parents = array();
|
||||||
foreach ($element['#parents'] as $parent) {
|
foreach ($element['#parents'] as $parent) {
|
||||||
$parents[] = $parent;
|
$parents[] = $parent;
|
||||||
|
@ -1244,15 +1241,6 @@ class FormState implements FormStateInterface {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps drupal_set_message().
|
|
||||||
*
|
|
||||||
* @return array|null
|
|
||||||
*/
|
|
||||||
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
|
|
||||||
return drupal_set_message($message, $type, $repeat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps ModuleHandler::loadInclude().
|
* Wraps ModuleHandler::loadInclude().
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -417,7 +417,8 @@ interface FormStateInterface {
|
||||||
* indicate which element needs to be changed and provide an error message.
|
* 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
|
* 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
|
* 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 standard behavior of this method can be changed if a button provides
|
||||||
* the #limit_validation_errors property. Multistep forms not wanting to
|
* the #limit_validation_errors property. Multistep forms not wanting to
|
||||||
|
|
|
@ -44,6 +44,13 @@ class FormValidator implements FormValidatorInterface {
|
||||||
*/
|
*/
|
||||||
protected $logger;
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form error handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Form\FormErrorHandlerInterface
|
||||||
|
*/
|
||||||
|
protected $formErrorHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new FormValidator.
|
* Constructs a new FormValidator.
|
||||||
*
|
*
|
||||||
|
@ -55,12 +62,15 @@ class FormValidator implements FormValidatorInterface {
|
||||||
* The CSRF token generator.
|
* The CSRF token generator.
|
||||||
* @param \Psr\Log\LoggerInterface $logger
|
* @param \Psr\Log\LoggerInterface $logger
|
||||||
* A logger instance.
|
* A logger instance.
|
||||||
|
* @param \Drupal\Core\Form\FormErrorHandlerInterface $form_error_handler
|
||||||
|
* The form error handler.
|
||||||
*/
|
*/
|
||||||
public function __construct(RequestStack $request_stack, TranslationInterface $string_translation, CsrfTokenGenerator $csrf_token, LoggerInterface $logger) {
|
public function __construct(RequestStack $request_stack, TranslationInterface $string_translation, CsrfTokenGenerator $csrf_token, LoggerInterface $logger, FormErrorHandlerInterface $form_error_handler) {
|
||||||
$this->requestStack = $request_stack;
|
$this->requestStack = $request_stack;
|
||||||
$this->stringTranslation = $string_translation;
|
$this->stringTranslation = $string_translation;
|
||||||
$this->csrfToken = $csrf_token;
|
$this->csrfToken = $csrf_token;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
$this->formErrorHandler = $form_error_handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,8 +194,9 @@ class FormValidator implements FormValidatorInterface {
|
||||||
* The unique string identifying the form.
|
* The unique string identifying the form.
|
||||||
*/
|
*/
|
||||||
protected function finalizeValidation(&$form, FormStateInterface &$form_state, $form_id) {
|
protected function finalizeValidation(&$form, FormStateInterface &$form_state, $form_id) {
|
||||||
// After validation, loop through and assign each element its errors.
|
// Delegate handling of form errors to a service.
|
||||||
$this->setElementErrorsFromFormState($form, $form_state);
|
$this->formErrorHandler->handleFormErrors($form, $form_state);
|
||||||
|
|
||||||
// Mark this form as validated.
|
// Mark this form as validated.
|
||||||
$form_state->setValidationComplete();
|
$form_state->setValidationComplete();
|
||||||
}
|
}
|
||||||
|
@ -394,26 +405,4 @@ class FormValidator implements FormValidatorInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 errors for this element on the element directly.
|
|
||||||
$elements['#errors'] = $form_state->getError($elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ class Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip value and hidden elements, since they are not rendered.
|
// Skip value and hidden elements, since they are not rendered.
|
||||||
if (isset($child['#type']) && in_array($child['#type'], array('value', 'hidden'))) {
|
if (!static::isVisibleElement($child)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +152,19 @@ class Element {
|
||||||
return array_keys($visible_children);
|
return array_keys($visible_children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an element is visible.
|
||||||
|
*
|
||||||
|
* @param array $element
|
||||||
|
* The element to check for visibility.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if the element is visible, otherwise FALSE.
|
||||||
|
*/
|
||||||
|
public static function isVisibleElement($element) {
|
||||||
|
return (!isset($element['#type']) || !in_array($element['#type'], ['value', 'hidden', 'token'])) && (!isset($element['#access']) || $element['#access']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets HTML attributes based on element properties.
|
* Sets HTML attributes based on element properties.
|
||||||
*
|
*
|
||||||
|
|
|
@ -86,6 +86,8 @@ class Checkboxes extends FormElement {
|
||||||
'#default_value' => isset($value[$key]) ? $key : NULL,
|
'#default_value' => isset($value[$key]) ? $key : NULL,
|
||||||
'#attributes' => $element['#attributes'],
|
'#attributes' => $element['#attributes'],
|
||||||
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
|
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
|
||||||
|
// Errors should only be shown on the parent checkboxes element.
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
'#weight' => $weight,
|
'#weight' => $weight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ class PasswordConfirm extends FormElement {
|
||||||
$class = get_class($this);
|
$class = get_class($this);
|
||||||
return array(
|
return array(
|
||||||
'#input' => TRUE,
|
'#input' => TRUE,
|
||||||
|
'#markup' => '',
|
||||||
'#process' => array(
|
'#process' => array(
|
||||||
array($class, 'processPasswordConfirm'),
|
array($class, 'processPasswordConfirm'),
|
||||||
),
|
),
|
||||||
|
@ -53,6 +54,7 @@ class PasswordConfirm extends FormElement {
|
||||||
'#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
|
'#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
|
||||||
'#required' => $element['#required'],
|
'#required' => $element['#required'],
|
||||||
'#attributes' => array('class' => array('password-field', 'js-password-field')),
|
'#attributes' => array('class' => array('password-field', 'js-password-field')),
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
);
|
);
|
||||||
$element['pass2'] = array(
|
$element['pass2'] = array(
|
||||||
'#type' => 'password',
|
'#type' => 'password',
|
||||||
|
@ -60,6 +62,7 @@ class PasswordConfirm extends FormElement {
|
||||||
'#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
|
'#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
|
||||||
'#required' => $element['#required'],
|
'#required' => $element['#required'],
|
||||||
'#attributes' => array('class' => array('password-confirm', 'js-password-confirm')),
|
'#attributes' => array('class' => array('password-confirm', 'js-password-confirm')),
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
);
|
);
|
||||||
$element['#element_validate'] = array(array(get_called_class(), 'validatePasswordConfirm'));
|
$element['#element_validate'] = array(array(get_called_class(), 'validatePasswordConfirm'));
|
||||||
$element['#tree'] = TRUE;
|
$element['#tree'] = TRUE;
|
||||||
|
|
|
@ -83,6 +83,8 @@ class Radios extends FormElement {
|
||||||
'#parents' => $element['#parents'],
|
'#parents' => $element['#parents'],
|
||||||
'#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)),
|
'#id' => HtmlUtility::getUniqueId('edit-' . implode('-', $parents_for_id)),
|
||||||
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
|
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
|
||||||
|
// Errors should only be shown on the parent radios element.
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
'#weight' => $weight,
|
'#weight' => $weight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,7 +278,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
||||||
'content_translation[created]' => '19/11/1978',
|
'content_translation[created]' => '19/11/1978',
|
||||||
);
|
);
|
||||||
$this->drupalPostForm($entity->urlInfo('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode));
|
$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));
|
$metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
|
||||||
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
|
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
|
||||||
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
|
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
|
||||||
|
|
|
@ -1163,7 +1163,7 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state)
|
||||||
$destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL;
|
$destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL;
|
||||||
if (isset($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
|
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']));
|
\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($upload_name, t('The file could not be uploaded.'));
|
$form_state->setError($element, t('The file could not be uploaded.'));
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1173,7 +1173,7 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state)
|
||||||
if ($files_uploaded) {
|
if ($files_uploaded) {
|
||||||
if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
|
if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
|
||||||
\Drupal::logger('file')->notice('The file upload failed. %upload', array('%upload' => $upload_name));
|
\Drupal::logger('file')->notice('The file upload failed. %upload', array('%upload' => $upload_name));
|
||||||
$form_state->setErrorByName($upload_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();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -233,6 +233,7 @@ class ManagedFile extends FormElement {
|
||||||
'#multiple' => $element['#multiple'],
|
'#multiple' => $element['#multiple'],
|
||||||
'#theme_wrappers' => [],
|
'#theme_wrappers' => [],
|
||||||
'#weight' => -10,
|
'#weight' => -10,
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!empty($fids) && $element['#files']) {
|
if (!empty($fids) && $element['#files']) {
|
||||||
|
@ -328,7 +329,7 @@ class ManagedFile extends FormElement {
|
||||||
|
|
||||||
// Check required property based on the FID.
|
// Check required property based on the FID.
|
||||||
if ($element['#required'] && empty($element['fids']['#value']) && !in_array($clicked_button, ['upload_button', 'remove_button'])) {
|
if ($element['#required'] && empty($element['fids']['#value']) && !in_array($clicked_button, ['upload_button', 'remove_button'])) {
|
||||||
$form_state->setError($element['upload'], t('!name field is required.', ['!name' => $element['#title']]));
|
$form_state->setError($element, t('!name is required.', ['!name' => $element['#title']]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consolidate the array value of this field to array of FIDs.
|
// Consolidate the array value of this field to array of FIDs.
|
||||||
|
|
|
@ -34,7 +34,8 @@ class FileFieldValidateTest extends FileFieldTestBase {
|
||||||
$edit = array();
|
$edit = array();
|
||||||
$edit['title[0][value]'] = $this->randomMachineName();
|
$edit['title[0][value]'] = $this->randomMachineName();
|
||||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
$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: ' . $field->label(), 'Node save failed when required file field was empty.');
|
||||||
|
$this->assertIdentical(1, count($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]//a', [':class' => ' messages--error '])), 'There is one link in the error message.');
|
||||||
|
|
||||||
// Create a new node with the uploaded file.
|
// Create a new node with the uploaded file.
|
||||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||||
|
@ -55,7 +56,8 @@ class FileFieldValidateTest extends FileFieldTestBase {
|
||||||
$edit = array();
|
$edit = array();
|
||||||
$edit['title[0][value]'] = $this->randomMachineName();
|
$edit['title[0][value]'] = $this->randomMachineName();
|
||||||
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
|
$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: ' . $field->label(), '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', [':class' => ' messages--error '])), 'There is one link in the error message.');
|
||||||
|
|
||||||
// Create a new node with the uploaded file into the multivalue field.
|
// Create a new node with the uploaded file into the multivalue field.
|
||||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Drupal\shortcut\Tests;
|
namespace Drupal\shortcut\Tests;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\SafeMarkup;
|
||||||
use Drupal\shortcut\Entity\ShortcutSet;
|
use Drupal\shortcut\Entity\ShortcutSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,7 +133,9 @@ class ShortcutSetsTest extends ShortcutTestBase {
|
||||||
function testShortcutSetSwitchNoSetName() {
|
function testShortcutSetSwitchNoSetName() {
|
||||||
$edit = array('set' => 'new');
|
$edit = array('set' => 'new');
|
||||||
$this->drupalPostForm('user/' . $this->adminUser->id() . '/shortcuts', $edit, t('Change set'));
|
$this->drupalPostForm('user/' . $this->adminUser->id() . '/shortcuts', $edit, t('Change set'));
|
||||||
$this->assertText(t('The new set label is required.'));
|
$this->assertRaw(\Drupal::translation()->formatPlural(1, '1 error has been found: !errors', '@count errors have been found: !errors', [
|
||||||
|
'!errors' => SafeMarkup::set('<a href="#edit-label">Label</a>')
|
||||||
|
]));
|
||||||
$current_set = shortcut_current_displayed_set($this->adminUser);
|
$current_set = shortcut_current_displayed_set($this->adminUser);
|
||||||
$this->assertEqual($current_set->id(), $this->set->id(), 'Attempting to switch to a new shortcut set without providing a set name does not succeed.');
|
$this->assertEqual($current_set->id(), $this->set->id(), 'Attempting to switch to a new shortcut set without providing a set name does not succeed.');
|
||||||
$this->assertFieldByXPath("//input[@name='label' and contains(concat(' ', normalize-space(@class), ' '), ' error ')]", NULL, 'The new set label field has the error class');
|
$this->assertFieldByXPath("//input[@name='label' and contains(concat(' ', normalize-space(@class), ' '), ' error ')]", NULL, 'The new set label field has the error class');
|
||||||
|
|
|
@ -113,6 +113,17 @@ abbr.ajax-changed {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inline error messages. */
|
||||||
|
.form-error-message:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
vertical-align: sub;
|
||||||
|
background: url(../../../misc/icons/ea2800/error.svg) no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline items.
|
* Inline items.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -188,7 +188,7 @@ class FormTest extends WebTestBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the page for error messages.
|
// Check the page for error messages.
|
||||||
$errors = $this->xpath('//div[contains(@class, "error")]//li');
|
$errors = $this->xpath('//div[contains(@class, "form-error-message")]//strong');
|
||||||
foreach ($errors as $error) {
|
foreach ($errors as $error) {
|
||||||
$expected_key = array_search($error[0], $expected);
|
$expected_key = array_search($error[0], $expected);
|
||||||
// If the error message is not one of the expected messages, fail.
|
// If the error message is not one of the expected messages, fail.
|
||||||
|
|
|
@ -19,8 +19,20 @@ use Drupal\simpletest\KernelTestBase;
|
||||||
*/
|
*/
|
||||||
class TriggeringElementProgrammedUnitTest extends KernelTestBase implements FormInterface {
|
class TriggeringElementProgrammedUnitTest extends KernelTestBase implements FormInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public static $modules = array('system');
|
public static $modules = array('system');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
$this->installSchema('system', ['router']);
|
||||||
|
\Drupal::service('router.builder')->rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
|
|
||||||
namespace Drupal\system\Tests\Form;
|
namespace Drupal\system\Tests\Form;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\SafeMarkup;
|
||||||
use Drupal\Core\Render\Element;
|
use Drupal\Core\Render\Element;
|
||||||
|
use Drupal\Core\Url;
|
||||||
use Drupal\simpletest\WebTestBase;
|
use Drupal\simpletest\WebTestBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,17 +208,33 @@ class ValidationTest extends WebTestBase {
|
||||||
$edit = array();
|
$edit = array();
|
||||||
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
||||||
|
|
||||||
|
$messages = [];
|
||||||
foreach (Element::children($form) as $key) {
|
foreach (Element::children($form) as $key) {
|
||||||
if (isset($form[$key]['#required_error'])) {
|
if (isset($form[$key]['#required_error'])) {
|
||||||
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
||||||
$this->assertText($form[$key]['#required_error']);
|
$messages[] = [
|
||||||
|
'title' => $form[$key]['#title'],
|
||||||
|
'message' => $form[$key]['#required_error'],
|
||||||
|
'key' => $key,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
elseif (isset($form[$key]['#form_test_required_error'])) {
|
elseif (isset($form[$key]['#form_test_required_error'])) {
|
||||||
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
||||||
$this->assertText($form[$key]['#form_test_required_error']);
|
$messages[] = [
|
||||||
|
'title' => $form[$key]['#title'],
|
||||||
|
'message' => $form[$key]['#form_test_required_error'],
|
||||||
|
'key' => $key,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
elseif (!empty($form[$key]['#required'])) {
|
||||||
|
$messages[] = [
|
||||||
|
'title' => $form[$key]['#title'],
|
||||||
|
'message' => t('!name field is required.', ['!name' => $form[$key]['#title']]),
|
||||||
|
'key' => $key,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
|
$this->assertErrorMessages($messages);
|
||||||
|
|
||||||
// Verify that no custom validation error appears with valid values.
|
// Verify that no custom validation error appears with valid values.
|
||||||
$edit = array(
|
$edit = array(
|
||||||
|
@ -226,6 +244,7 @@ class ValidationTest extends WebTestBase {
|
||||||
);
|
);
|
||||||
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
|
||||||
|
|
||||||
|
$messages = [];
|
||||||
foreach (Element::children($form) as $key) {
|
foreach (Element::children($form) as $key) {
|
||||||
if (isset($form[$key]['#required_error'])) {
|
if (isset($form[$key]['#required_error'])) {
|
||||||
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
||||||
|
@ -235,7 +254,49 @@ class ValidationTest extends WebTestBase {
|
||||||
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
$this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
|
||||||
$this->assertNoText($form[$key]['#form_test_required_error']);
|
$this->assertNoText($form[$key]['#form_test_required_error']);
|
||||||
}
|
}
|
||||||
|
elseif (!empty($form[$key]['#required'])) {
|
||||||
|
$messages[] = [
|
||||||
|
'title' => $form[$key]['#title'],
|
||||||
|
'message' => t('!name field is required.', ['!name' => $form[$key]['#title']]),
|
||||||
|
'key' => $key,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$this->assertErrorMessages($messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the given error messages are displayed.
|
||||||
|
*
|
||||||
|
* @param array $messages
|
||||||
|
* An associative array of error messages keyed by the order they appear on
|
||||||
|
* the page, with the following key-value pairs:
|
||||||
|
* - title: The human readable form element title.
|
||||||
|
* - message: The error message for this form element.
|
||||||
|
* - key: The key used for the form element.
|
||||||
|
*/
|
||||||
|
protected function assertErrorMessages($messages) {
|
||||||
|
$element = $this->xpath('//div[@class = "form-error-message"]/strong');
|
||||||
|
$this->assertIdentical(count($messages), count($element));
|
||||||
|
|
||||||
|
$error_links = [];
|
||||||
|
foreach ($messages as $delta => $message) {
|
||||||
|
// Ensure the message appears in the correct place.
|
||||||
|
if (!isset($element[$delta])) {
|
||||||
|
$this->fail(format_string('The error message for the "@title" element with key "@key" was not found.', ['@title' => $message['title'], '@key' => $message['key']]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->assertIdentical($message['message'], (string) $element[$delta]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather the element for checking the jump link section.
|
||||||
|
$error_links[] = \Drupal::l($message['title'], Url::fromRoute('<none>', [], ['fragment' => 'edit-' . str_replace('_', '-', $message['key']), 'external' => TRUE]));
|
||||||
|
}
|
||||||
|
$top_message = \Drupal::translation()->formatPlural(count($error_links), '1 error has been found: !errors', '@count errors have been found: !errors', [
|
||||||
|
'!errors' => SafeMarkup::set(implode(', ', $error_links))
|
||||||
|
]);
|
||||||
|
$this->assertRaw($top_message);
|
||||||
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
|
$this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,4 +24,9 @@
|
||||||
<h4{{ title_attributes.addClass(title_classes) }}>{{ title }}</h4>
|
<h4{{ title_attributes.addClass(title_classes) }}>{{ title }}</h4>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ content }}
|
{{ content }}
|
||||||
|
{% if errors %}
|
||||||
|
<div>
|
||||||
|
{{ errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{{ description }}
|
{{ description }}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*
|
*
|
||||||
* Available variables:
|
* Available variables:
|
||||||
* - attributes: HTML attributes for the fieldset element.
|
* - attributes: HTML attributes for the fieldset element.
|
||||||
|
* - errors: (optional) Any errors for this fieldset element, may not be set.
|
||||||
* - required: Boolean indicating whether the fieldeset element is required.
|
* - required: Boolean indicating whether the fieldeset element is required.
|
||||||
* - legend: The legend element containing the following properties:
|
* - legend: The legend element containing the following properties:
|
||||||
* - title: Title of the fieldset, intended for use as the text of the legend.
|
* - title: Title of the fieldset, intended for use as the text of the legend.
|
||||||
|
@ -33,6 +34,11 @@
|
||||||
<span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
|
<span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
|
||||||
</legend>
|
</legend>
|
||||||
<div class="fieldset-wrapper">
|
<div class="fieldset-wrapper">
|
||||||
|
{% if errors %}
|
||||||
|
<div>
|
||||||
|
{{ errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if prefix %}
|
{% if prefix %}
|
||||||
<span class="field-prefix">{{ prefix }}</span>
|
<span class="field-prefix">{{ prefix }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*
|
*
|
||||||
* Available variables:
|
* Available variables:
|
||||||
* - attributes: HTML attributes for the containing element.
|
* - attributes: HTML attributes for the containing element.
|
||||||
|
* - errors: (optional) Any errors for this form element, may not be set.
|
||||||
* - prefix: (optional) The form element prefix, may not be set.
|
* - prefix: (optional) The form element prefix, may not be set.
|
||||||
* - suffix: (optional) The form element suffix, may not be set.
|
* - suffix: (optional) The form element suffix, may not be set.
|
||||||
* - required: The required marker, or empty if the associated form element is
|
* - required: The required marker, or empty if the associated form element is
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
'form-item-' ~ name|clean_class,
|
'form-item-' ~ name|clean_class,
|
||||||
title_display not in ['after', 'before'] ? 'form-no-label',
|
title_display not in ['after', 'before'] ? 'form-no-label',
|
||||||
disabled == 'disabled' ? 'form-disabled',
|
disabled == 'disabled' ? 'form-disabled',
|
||||||
|
errors ? 'form-error',
|
||||||
]
|
]
|
||||||
%}
|
%}
|
||||||
{%
|
{%
|
||||||
|
@ -79,6 +81,11 @@
|
||||||
{% if label_display == 'after' %}
|
{% if label_display == 'after' %}
|
||||||
{{ label }}
|
{{ label }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if errors %}
|
||||||
|
<div class="form-error-message">
|
||||||
|
{{ errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if description_display in ['after', 'invisible'] and description.content %}
|
{% if description_display in ['after', 'invisible'] and description.content %}
|
||||||
<div{{ description.attributes.addClass(description_classes) }}>
|
<div{{ description.attributes.addClass(description_classes) }}>
|
||||||
{{ description.content }}
|
{{ description.content }}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace Drupal\user\Tests;
|
namespace Drupal\user\Tests;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\SafeMarkup;
|
||||||
use Drupal\simpletest\WebTestBase;
|
use Drupal\simpletest\WebTestBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +44,16 @@ class UserBlocksTest extends WebTestBase {
|
||||||
* Test the user login block.
|
* Test the user login block.
|
||||||
*/
|
*/
|
||||||
function testUserLoginBlock() {
|
function testUserLoginBlock() {
|
||||||
|
// Make sure the validation error is displayed when try to login with
|
||||||
|
// invalid username/password.
|
||||||
|
$edit['name'] = $this->randomMachineName();
|
||||||
|
$edit['pass'] = $this->randomMachineName();
|
||||||
|
$this->drupalPostForm('node', $edit, t('Log in'));
|
||||||
|
$this->assertRaw(\Drupal::translation()->formatPlural(1, '1 error has been found: !errors', '@count errors have been found: !errors', [
|
||||||
|
'!errors' => SafeMarkup::set('<a href="#edit-name">Username</a>')
|
||||||
|
]));
|
||||||
|
$this->assertText(t('Sorry, unrecognized username or password.'));
|
||||||
|
|
||||||
// Create a user with some permission that anonymous users lack.
|
// Create a user with some permission that anonymous users lack.
|
||||||
$user = $this->drupalCreateUser(array('administer permissions'));
|
$user = $this->drupalCreateUser(array('administer permissions'));
|
||||||
|
|
||||||
|
|
|
@ -370,15 +370,11 @@ class FormBuilderTest extends FormTestBase {
|
||||||
->method('buildForm')
|
->method('buildForm')
|
||||||
->will($this->returnValue($expected_form));
|
->will($this->returnValue($expected_form));
|
||||||
|
|
||||||
$form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
|
$form_state = new FormState();
|
||||||
->setMethods(array('drupalSetMessage'))
|
|
||||||
->getMock();
|
|
||||||
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
|
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
|
||||||
$this->assertSame('test-form-id', $form['#id']);
|
$this->assertSame('test-form-id', $form['#id']);
|
||||||
|
|
||||||
$form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
|
$form_state = new FormState();
|
||||||
->setMethods(array('drupalSetMessage'))
|
|
||||||
->getMock();
|
|
||||||
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
|
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
|
||||||
$this->assertSame('test-form-id--2', $form['#id']);
|
$this->assertSame('test-form-id--2', $form['#id']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Tests\Core\Form\FormElementHelperTest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Tests\Core\Form;
|
||||||
|
|
||||||
|
use Drupal\Core\Form\FormElementHelper;
|
||||||
|
use Drupal\Tests\UnitTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the form element helper.
|
||||||
|
*
|
||||||
|
* @group Drupal
|
||||||
|
* @group Form
|
||||||
|
*
|
||||||
|
* @coversDefaultClass \Drupal\Core\Form\FormElementHelper
|
||||||
|
*/
|
||||||
|
class FormElementHelperTest extends UnitTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the getElementByName() method.
|
||||||
|
*
|
||||||
|
* @covers ::getElementByName
|
||||||
|
*
|
||||||
|
* @dataProvider getElementByNameProvider
|
||||||
|
*/
|
||||||
|
public function testGetElementByName($name, $form, $expected) {
|
||||||
|
$this->assertSame($expected, FormElementHelper::getElementByName($name, $form));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides test data.
|
||||||
|
*/
|
||||||
|
public function getElementByNameProvider() {
|
||||||
|
$data = [];
|
||||||
|
$data[] = ['id', [], []];
|
||||||
|
$data[] = [
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$data[] = [
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'fieldset' => [
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$data[] = [
|
||||||
|
'fieldset',
|
||||||
|
[
|
||||||
|
'fieldset' => [
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$data[] = [
|
||||||
|
'fieldset][id',
|
||||||
|
[
|
||||||
|
'fieldset' => [
|
||||||
|
'#tree' => TRUE,
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['fieldset', 'id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['fieldset', 'id'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the getElementTitle() method.
|
||||||
|
*
|
||||||
|
* @covers ::getElementTitle
|
||||||
|
*
|
||||||
|
* @dataProvider getElementTitleProvider
|
||||||
|
*/
|
||||||
|
public function testGetElementTitle($name, $form, $expected) {
|
||||||
|
$element = FormElementHelper::getElementByName($name, $form);
|
||||||
|
$this->assertSame($expected, FormElementHelper::getElementTitle($element));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides test data.
|
||||||
|
*/
|
||||||
|
public function getElementTitleProvider() {
|
||||||
|
$data = [];
|
||||||
|
$data[] = ['id', [], ''];
|
||||||
|
$data[] = [
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'ID',
|
||||||
|
];
|
||||||
|
$data[] = [
|
||||||
|
'id',
|
||||||
|
[
|
||||||
|
'fieldset' => [
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'ID',
|
||||||
|
];
|
||||||
|
$data[] = [
|
||||||
|
'fieldset',
|
||||||
|
[
|
||||||
|
'fieldset' => [
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'ID',
|
||||||
|
];
|
||||||
|
$data[] = [
|
||||||
|
'fieldset][id',
|
||||||
|
[
|
||||||
|
'fieldset' => [
|
||||||
|
'#tree' => TRUE,
|
||||||
|
'id' => [
|
||||||
|
'#title' => 'ID',
|
||||||
|
'#parents' => ['fieldset', 'id'],
|
||||||
|
],
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'ID',
|
||||||
|
];
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Tests\Core\Form\FormErrorHandlerTest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Tests\Core\Form;
|
||||||
|
|
||||||
|
use Drupal\Core\Form\FormState;
|
||||||
|
use Drupal\Tests\UnitTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coversDefaultClass \Drupal\Core\Form\FormErrorHandler
|
||||||
|
* @group Form
|
||||||
|
*/
|
||||||
|
class FormErrorHandlerTest extends UnitTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::handleFormErrors
|
||||||
|
* @covers ::displayErrorMessages
|
||||||
|
*/
|
||||||
|
public function testDisplayErrorMessages() {
|
||||||
|
$link_generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface');
|
||||||
|
$link_generator->expects($this->any())
|
||||||
|
->method('generate')
|
||||||
|
->willReturnArgument(0);
|
||||||
|
$form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
|
||||||
|
->setConstructorArgs([$this->getStringTranslationStub(), $link_generator])
|
||||||
|
->setMethods(['drupalSetMessage'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$form_error_handler->expects($this->at(0))
|
||||||
|
->method('drupalSetMessage')
|
||||||
|
->with('no title given', 'error');
|
||||||
|
$form_error_handler->expects($this->at(1))
|
||||||
|
->method('drupalSetMessage')
|
||||||
|
->with('element is invisible', 'error');
|
||||||
|
$form_error_handler->expects($this->at(2))
|
||||||
|
->method('drupalSetMessage')
|
||||||
|
->with('this missing element is invalid', 'error');
|
||||||
|
$form_error_handler->expects($this->at(3))
|
||||||
|
->method('drupalSetMessage')
|
||||||
|
->with('3 errors have been found: Test 1, Test 2 & a half, Test 3', 'error');
|
||||||
|
|
||||||
|
$form = [
|
||||||
|
'#parents' => [],
|
||||||
|
];
|
||||||
|
$form['test1'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#title' => 'Test 1',
|
||||||
|
'#parents' => ['test1'],
|
||||||
|
'#id' => 'edit-test1',
|
||||||
|
];
|
||||||
|
$form['test2'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#title' => 'Test 2 & a half',
|
||||||
|
'#parents' => ['test2'],
|
||||||
|
'#id' => 'edit-test2',
|
||||||
|
];
|
||||||
|
$form['fieldset'] = [
|
||||||
|
'#parents' => ['fieldset'],
|
||||||
|
'test3' => [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#title' => 'Test 3',
|
||||||
|
'#parents' => ['fieldset', 'test3'],
|
||||||
|
'#id' => 'edit-test3',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$form['test4'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#title' => 'Test 4',
|
||||||
|
'#parents' => ['test4'],
|
||||||
|
'#id' => 'edit-test4',
|
||||||
|
'#error_no_message' => TRUE,
|
||||||
|
];
|
||||||
|
$form['test5'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#parents' => ['test5'],
|
||||||
|
'#id' => 'edit-test5',
|
||||||
|
];
|
||||||
|
$form['test6'] = [
|
||||||
|
'#type' => 'value',
|
||||||
|
'#title' => 'Test 6',
|
||||||
|
'#parents' => ['test6'],
|
||||||
|
'#id' => 'edit-test6',
|
||||||
|
];
|
||||||
|
$form_state = new FormState();
|
||||||
|
$form_state->setErrorByName('test1', 'invalid');
|
||||||
|
$form_state->setErrorByName('test2', 'invalid');
|
||||||
|
$form_state->setErrorByName('fieldset][test3', 'invalid');
|
||||||
|
$form_state->setErrorByName('test4', 'no error message');
|
||||||
|
$form_state->setErrorByName('test5', 'no title given');
|
||||||
|
$form_state->setErrorByName('test6', 'element is invisible');
|
||||||
|
$form_state->setErrorByName('missing_element', 'this missing element is invalid');
|
||||||
|
$form_error_handler->handleFormErrors($form, $form_state);
|
||||||
|
$this->assertSame('invalid', $form['test1']['#errors']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::handleFormErrors
|
||||||
|
* @covers ::setElementErrorsFromFormState
|
||||||
|
*/
|
||||||
|
public function testSetElementErrorsFromFormState() {
|
||||||
|
$form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
|
||||||
|
->setConstructorArgs([$this->getStringTranslationStub(), $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface')])
|
||||||
|
->setMethods(['drupalSetMessage'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$form = [
|
||||||
|
'#parents' => [],
|
||||||
|
];
|
||||||
|
$form['test'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#title' => 'Test',
|
||||||
|
'#parents' => ['test'],
|
||||||
|
'#id' => 'edit-test',
|
||||||
|
];
|
||||||
|
$form_state = new FormState();
|
||||||
|
$form_state->setErrorByName('test', 'invalid');
|
||||||
|
$form_error_handler->handleFormErrors($form, $form_state);
|
||||||
|
$this->assertSame('invalid', $form['test']['#errors']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -62,15 +62,10 @@ class FormStateTest extends UnitTestCase {
|
||||||
* @covers ::setError
|
* @covers ::setError
|
||||||
*/
|
*/
|
||||||
public function testSetError() {
|
public function testSetError() {
|
||||||
$form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
|
$form_state = new FormState();
|
||||||
->setMethods(array('drupalSetMessage'))
|
|
||||||
->getMock();
|
|
||||||
$form_state->expects($this->once())
|
|
||||||
->method('drupalSetMessage')
|
|
||||||
->willReturn('Fail');
|
|
||||||
|
|
||||||
$element['#parents'] = array('foo', 'bar');
|
$element['#parents'] = array('foo', 'bar');
|
||||||
$form_state->setError($element, 'Fail');
|
$form_state->setError($element, 'Fail');
|
||||||
|
$this->assertSame(['foo][bar' => 'Fail'], $form_state->getErrors());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,14 +103,10 @@ class FormStateTest extends UnitTestCase {
|
||||||
*
|
*
|
||||||
* @dataProvider providerTestSetErrorByName
|
* @dataProvider providerTestSetErrorByName
|
||||||
*/
|
*/
|
||||||
public function testSetErrorByName($limit_validation_errors, $expected_errors, $set_message = FALSE) {
|
public function testSetErrorByName($limit_validation_errors, $expected_errors) {
|
||||||
$form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
|
$form_state = new FormState();
|
||||||
->setMethods(array('drupalSetMessage'))
|
|
||||||
->getMock();
|
|
||||||
$form_state->setLimitValidationErrors($limit_validation_errors);
|
$form_state->setLimitValidationErrors($limit_validation_errors);
|
||||||
$form_state->clearErrors();
|
$form_state->clearErrors();
|
||||||
$form_state->expects($set_message ? $this->once() : $this->never())
|
|
||||||
->method('drupalSetMessage');
|
|
||||||
|
|
||||||
$form_state->setErrorByName('test', 'Fail 1');
|
$form_state->setErrorByName('test', 'Fail 1');
|
||||||
$form_state->setErrorByName('test', 'Fail 2');
|
$form_state->setErrorByName('test', 'Fail 2');
|
||||||
|
@ -131,7 +122,7 @@ class FormStateTest extends UnitTestCase {
|
||||||
array(array(array('options')), array('options' => '')),
|
array(array(array('options')), array('options' => '')),
|
||||||
// Do not limit an validation, and, ensuring the first error is returned
|
// Do not limit an validation, and, ensuring the first error is returned
|
||||||
// for the 'test' element.
|
// for the 'test' element.
|
||||||
array(NULL, array('test' => 'Fail 1', 'options' => ''), TRUE),
|
[NULL, ['test' => 'Fail 1', 'options' => '']],
|
||||||
// Limit all validation.
|
// Limit all validation.
|
||||||
array(array(), array()),
|
array(array(), array()),
|
||||||
);
|
);
|
||||||
|
@ -146,9 +137,7 @@ class FormStateTest extends UnitTestCase {
|
||||||
* @expectedExceptionMessage Form errors cannot be set after form validation has finished.
|
* @expectedExceptionMessage Form errors cannot be set after form validation has finished.
|
||||||
*/
|
*/
|
||||||
public function testFormErrorsDuringSubmission() {
|
public function testFormErrorsDuringSubmission() {
|
||||||
$form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
|
$form_state = new FormState();
|
||||||
->setMethods(array('drupalSetMessage'))
|
|
||||||
->getMock();
|
|
||||||
$form_state->setValidationComplete();
|
$form_state->setValidationComplete();
|
||||||
$form_state->setErrorByName('test', 'message');
|
$form_state->setErrorByName('test', 'message');
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,9 +178,10 @@ abstract class FormTestBase extends UnitTestCase {
|
||||||
$this->requestStack = new RequestStack();
|
$this->requestStack = new RequestStack();
|
||||||
$this->requestStack->push($this->request);
|
$this->requestStack->push($this->request);
|
||||||
$this->logger = $this->getMock('Drupal\Core\Logger\LoggerChannelInterface');
|
$this->logger = $this->getMock('Drupal\Core\Logger\LoggerChannelInterface');
|
||||||
|
$form_error_handler = $this->getMock('Drupal\Core\Form\FormErrorHandlerInterface');
|
||||||
$this->formValidator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$this->formValidator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->setConstructorArgs(array($this->requestStack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger))
|
->setConstructorArgs([$this->requestStack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $form_error_handler])
|
||||||
->setMethods(array('drupalSetMessage'))
|
->setMethods(NULL)
|
||||||
->getMock();
|
->getMock();
|
||||||
$this->formSubmitter = $this->getMockBuilder('Drupal\Core\Form\FormSubmitter')
|
$this->formSubmitter = $this->getMockBuilder('Drupal\Core\Form\FormSubmitter')
|
||||||
->setConstructorArgs(array($this->requestStack, $this->urlGenerator))
|
->setConstructorArgs(array($this->requestStack, $this->urlGenerator))
|
||||||
|
|
|
@ -19,6 +19,39 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
*/
|
*/
|
||||||
class FormValidatorTest extends UnitTestCase {
|
class FormValidatorTest extends UnitTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A logger instance.
|
||||||
|
*
|
||||||
|
* @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CSRF token generator to validate the form token.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
|
||||||
|
*/
|
||||||
|
protected $csrfToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form error handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Form\FormErrorHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||||
|
*/
|
||||||
|
protected $formErrorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
$this->logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||||
|
$this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$this->formErrorHandler = $this->getMock('Drupal\Core\Form\FormErrorHandlerInterface');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the 'validation_complete' $form_state flag.
|
* Tests the 'validation_complete' $form_state flag.
|
||||||
*
|
*
|
||||||
|
@ -27,7 +60,7 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testValidationComplete() {
|
public function testValidationComplete() {
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->disableOriginalConstructor()
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(NULL)
|
->setMethods(NULL)
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
|
@ -45,7 +78,7 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testPreventDuplicateValidation() {
|
public function testPreventDuplicateValidation() {
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->disableOriginalConstructor()
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('doValidateForm'))
|
->setMethods(array('doValidateForm'))
|
||||||
->getMock();
|
->getMock();
|
||||||
$form_validator->expects($this->never())
|
$form_validator->expects($this->never())
|
||||||
|
@ -65,18 +98,19 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testMustValidate() {
|
public function testMustValidate() {
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->disableOriginalConstructor()
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('doValidateForm'))
|
->setMethods(array('doValidateForm'))
|
||||||
->getMock();
|
->getMock();
|
||||||
$form_validator->expects($this->once())
|
$form_validator->expects($this->once())
|
||||||
->method('doValidateForm');
|
->method('doValidateForm');
|
||||||
|
$this->formErrorHandler->expects($this->once())
|
||||||
|
->method('handleFormErrors');
|
||||||
|
|
||||||
$form = array();
|
$form = array();
|
||||||
$form_state = (new FormState())
|
$form_state = (new FormState())
|
||||||
->setValidationComplete()
|
->setValidationComplete()
|
||||||
->setValidationEnforced();
|
->setValidationEnforced();
|
||||||
$form_validator->validateForm('test_form_id', $form, $form_state);
|
$form_validator->validateForm('test_form_id', $form, $form_state);
|
||||||
$this->assertArrayHasKey('#errors', $form);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,16 +120,12 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
$request_stack = new RequestStack();
|
$request_stack = new RequestStack();
|
||||||
$request = new Request(array(), array(), array(), array(), array(), array('REQUEST_URI' => '/test/example?foo=bar'));
|
$request = new Request(array(), array(), array(), array(), array(), array('REQUEST_URI' => '/test/example?foo=bar'));
|
||||||
$request_stack->push($request);
|
$request_stack->push($request);
|
||||||
$csrf_token = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
|
$this->csrfToken->expects($this->once())
|
||||||
->disableOriginalConstructor()
|
|
||||||
->getMock();
|
|
||||||
$csrf_token->expects($this->once())
|
|
||||||
->method('validate')
|
->method('validate')
|
||||||
->will($this->returnValue(FALSE));
|
->will($this->returnValue(FALSE));
|
||||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
|
||||||
|
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->setConstructorArgs(array($request_stack, $this->getStringTranslationStub(), $csrf_token, $logger))
|
->setConstructorArgs([$request_stack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('doValidateForm'))
|
->setMethods(array('doValidateForm'))
|
||||||
->getMock();
|
->getMock();
|
||||||
$form_validator->expects($this->never())
|
$form_validator->expects($this->never())
|
||||||
|
@ -118,16 +148,12 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testValidateValidFormToken() {
|
public function testValidateValidFormToken() {
|
||||||
$request_stack = new RequestStack();
|
$request_stack = new RequestStack();
|
||||||
$csrf_token = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
|
$this->csrfToken->expects($this->once())
|
||||||
->disableOriginalConstructor()
|
|
||||||
->getMock();
|
|
||||||
$csrf_token->expects($this->once())
|
|
||||||
->method('validate')
|
->method('validate')
|
||||||
->will($this->returnValue(TRUE));
|
->will($this->returnValue(TRUE));
|
||||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
|
||||||
|
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->setConstructorArgs(array($request_stack, $this->getStringTranslationStub(), $csrf_token, $logger))
|
->setConstructorArgs([$request_stack, $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('doValidateForm'))
|
->setMethods(array('doValidateForm'))
|
||||||
->getMock();
|
->getMock();
|
||||||
$form_validator->expects($this->once())
|
$form_validator->expects($this->once())
|
||||||
|
@ -144,31 +170,6 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
$this->assertTrue($form_state->isValidationComplete());
|
$this->assertTrue($form_state->isValidationComplete());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers ::setElementErrorsFromFormState
|
|
||||||
*/
|
|
||||||
public function testSetElementErrorsFromFormState() {
|
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->setMethods(NULL)
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$form = array(
|
|
||||||
'#parents' => array(),
|
|
||||||
);
|
|
||||||
$form['test'] = array(
|
|
||||||
'#type' => 'textfield',
|
|
||||||
'#title' => 'Test',
|
|
||||||
'#parents' => array('test'),
|
|
||||||
);
|
|
||||||
$form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
|
|
||||||
->setMethods(array('drupalSetMessage'))
|
|
||||||
->getMock();
|
|
||||||
$form_state->setErrorByName('test', 'invalid');
|
|
||||||
$form_validator->validateForm('test_form_id', $form, $form_state);
|
|
||||||
$this->assertSame('invalid', $form['test']['#errors']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers ::handleErrorsWithLimitedValidation
|
* @covers ::handleErrorsWithLimitedValidation
|
||||||
*
|
*
|
||||||
|
@ -176,7 +177,7 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testHandleErrorsWithLimitedValidation($sections, $triggering_element, $values, $expected) {
|
public function testHandleErrorsWithLimitedValidation($sections, $triggering_element, $values, $expected) {
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->disableOriginalConstructor()
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(NULL)
|
->setMethods(NULL)
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
|
@ -272,7 +273,7 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testExecuteValidateHandlers() {
|
public function testExecuteValidateHandlers() {
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->disableOriginalConstructor()
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(NULL)
|
->setMethods(NULL)
|
||||||
->getMock();
|
->getMock();
|
||||||
$mock = $this->getMock('stdClass', array('validate_handler', 'hash_validate'));
|
$mock = $this->getMock('stdClass', array('validate_handler', 'hash_validate'));
|
||||||
|
@ -302,13 +303,8 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
* @dataProvider providerTestRequiredErrorMessage
|
* @dataProvider providerTestRequiredErrorMessage
|
||||||
*/
|
*/
|
||||||
public function testRequiredErrorMessage($element, $expected_message) {
|
public function testRequiredErrorMessage($element, $expected_message) {
|
||||||
$csrf_token = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->getMock();
|
|
||||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
|
||||||
|
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->setConstructorArgs(array(new RequestStack(), $this->getStringTranslationStub(), $csrf_token, $logger))
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('executeValidateHandlers'))
|
->setMethods(array('executeValidateHandlers'))
|
||||||
->getMock();
|
->getMock();
|
||||||
$form_validator->expects($this->once())
|
$form_validator->expects($this->once())
|
||||||
|
@ -356,7 +352,7 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
*/
|
*/
|
||||||
public function testElementValidate() {
|
public function testElementValidate() {
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->disableOriginalConstructor()
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('executeValidateHandlers'))
|
->setMethods(array('executeValidateHandlers'))
|
||||||
->getMock();
|
->getMock();
|
||||||
$form_validator->expects($this->once())
|
$form_validator->expects($this->once())
|
||||||
|
@ -383,18 +379,13 @@ class FormValidatorTest extends UnitTestCase {
|
||||||
* @dataProvider providerTestPerformRequiredValidation
|
* @dataProvider providerTestPerformRequiredValidation
|
||||||
*/
|
*/
|
||||||
public function testPerformRequiredValidation($element, $expected_message, $call_watchdog) {
|
public function testPerformRequiredValidation($element, $expected_message, $call_watchdog) {
|
||||||
$csrf_token = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->getMock();
|
|
||||||
$logger = $this->getMock('Psr\Log\LoggerInterface');
|
|
||||||
|
|
||||||
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
$form_validator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
|
||||||
->setConstructorArgs(array(new RequestStack(), $this->getStringTranslationStub(), $csrf_token, $logger))
|
->setConstructorArgs([new RequestStack(), $this->getStringTranslationStub(), $this->csrfToken, $this->logger, $this->formErrorHandler])
|
||||||
->setMethods(array('setError'))
|
->setMethods(array('setError'))
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
if ($call_watchdog) {
|
if ($call_watchdog) {
|
||||||
$logger->expects($this->once())
|
$this->logger->expects($this->once())
|
||||||
->method('error')
|
->method('error')
|
||||||
->with($this->isType('string'), $this->isType('array'));
|
->with($this->isType('string'), $this->isType('array'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace Drupal\Tests;
|
||||||
|
|
||||||
use Drupal\Component\FileCache\FileCacheFactory;
|
use Drupal\Component\FileCache\FileCacheFactory;
|
||||||
use Drupal\Component\Utility\Random;
|
use Drupal\Component\Utility\Random;
|
||||||
|
use Drupal\Component\Utility\SafeMarkup;
|
||||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||||
|
|
||||||
|
@ -208,6 +209,11 @@ abstract class UnitTestCase extends \PHPUnit_Framework_TestCase {
|
||||||
$translation->expects($this->any())
|
$translation->expects($this->any())
|
||||||
->method('translate')
|
->method('translate')
|
||||||
->will($this->returnCallback('Drupal\Component\Utility\SafeMarkup::format'));
|
->will($this->returnCallback('Drupal\Component\Utility\SafeMarkup::format'));
|
||||||
|
$translation->expects($this->any())
|
||||||
|
->method('formatPlural')
|
||||||
|
->willReturnCallback(function ($count, $singular, $plural, array $args = [], array $options = []) {
|
||||||
|
return $count === 1 ? SafeMarkup::format($singular, $args) : SafeMarkup::format($plural, $args + ['@count' => $count]);
|
||||||
|
});
|
||||||
return $translation;
|
return $translation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -269,3 +269,13 @@ input.form-submit:focus {
|
||||||
margin-left: 0.6em;
|
margin-left: 0.6em;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Form error styles. */
|
||||||
|
.form-item textarea.error + .cke {
|
||||||
|
border: 2px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form error message styles. */
|
||||||
|
.form-error-message {
|
||||||
|
color: #ea2800;
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,11 @@
|
||||||
<h4{{ title_attributes.addClass(title_classes) }}>{{ title }}</h4>
|
<h4{{ title_attributes.addClass(title_classes) }}>{{ title }}</h4>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ content }}
|
{{ content }}
|
||||||
|
{% if errors %}
|
||||||
|
<div class="form-error-message">
|
||||||
|
<strong>{{ errors }}</strong>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if description %}
|
{% if description %}
|
||||||
<div class="description">{{ description }}</div>
|
<div class="description">{{ description }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*
|
*
|
||||||
* Available variables:
|
* Available variables:
|
||||||
* - attributes: HTML attributes for the fieldset element.
|
* - attributes: HTML attributes for the fieldset element.
|
||||||
|
* - errors: (optional) Any errors for this fieldset element, may not be set.
|
||||||
* - required: Boolean indicating whether the fieldeset element is required.
|
* - required: Boolean indicating whether the fieldeset element is required.
|
||||||
* - legend: The legend element containing the following properties:
|
* - legend: The legend element containing the following properties:
|
||||||
* - title: Title of the fieldset, intended for use as the text of the legend.
|
* - title: Title of the fieldset, intended for use as the text of the legend.
|
||||||
|
@ -31,6 +32,11 @@
|
||||||
<span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
|
<span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
|
||||||
</legend>
|
</legend>
|
||||||
<div class="fieldset-wrapper">
|
<div class="fieldset-wrapper">
|
||||||
|
{% if errors %}
|
||||||
|
<div class="form-error-message">
|
||||||
|
<strong>{{ errors }}</strong>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if prefix %}
|
{% if prefix %}
|
||||||
<span class="field-prefix">{{ prefix }}</span>
|
<span class="field-prefix">{{ prefix }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*
|
*
|
||||||
* Available variables:
|
* Available variables:
|
||||||
* - attributes: HTML attributes for the containing element.
|
* - attributes: HTML attributes for the containing element.
|
||||||
|
* - errors: (optional) Any errors for this form element, may not be set.
|
||||||
* - prefix: (optional) The form element prefix, may not be set.
|
* - prefix: (optional) The form element prefix, may not be set.
|
||||||
* - suffix: (optional) The form element suffix, may not be set.
|
* - suffix: (optional) The form element suffix, may not be set.
|
||||||
* - required: The required marker, or empty if the associated form element is
|
* - required: The required marker, or empty if the associated form element is
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
'form-item-' ~ name|clean_class,
|
'form-item-' ~ name|clean_class,
|
||||||
title_display not in ['after', 'before'] ? 'form-no-label',
|
title_display not in ['after', 'before'] ? 'form-no-label',
|
||||||
disabled == 'disabled' ? 'form-disabled',
|
disabled == 'disabled' ? 'form-disabled',
|
||||||
|
errors ? 'form-error',
|
||||||
]
|
]
|
||||||
%}
|
%}
|
||||||
{%
|
{%
|
||||||
|
@ -78,6 +80,11 @@
|
||||||
{% if label_display == 'after' %}
|
{% if label_display == 'after' %}
|
||||||
{{ label }}
|
{{ label }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if errors %}
|
||||||
|
<div class="form-error-message">
|
||||||
|
<strong>{{ errors }}</strong>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if description_display in ['after', 'invisible'] and description.content %}
|
{% if description_display in ['after', 'invisible'] and description.content %}
|
||||||
<div{{ description.attributes.addClass(description_classes) }}>
|
<div{{ description.attributes.addClass(description_classes) }}>
|
||||||
{{ description.content }}
|
{{ description.content }}
|
||||||
|
|
|
@ -55,7 +55,6 @@ label[for] {
|
||||||
.form-disabled label {
|
.form-disabled label {
|
||||||
color: #737373;
|
color: #737373;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-disabled input.form-text,
|
.form-disabled input.form-text,
|
||||||
.form-disabled input.form-tel,
|
.form-disabled input.form-tel,
|
||||||
.form-disabled input.form-email,
|
.form-disabled input.form-email,
|
||||||
|
@ -70,16 +69,19 @@ label[for] {
|
||||||
background-color: hsla(0, 0%, 0%, .08);
|
background-color: hsla(0, 0%, 0%, .08);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-item input.error,
|
.form-item input.error,
|
||||||
.form-item textarea.error,
|
.form-item textarea.error,
|
||||||
.form-item select.error {
|
.form-item select.error {
|
||||||
border-width: 2px;
|
border-width: 1px;
|
||||||
border-color: #e62600;
|
border-color: #e62600;
|
||||||
background-color: hsla(15, 75%, 97%, 1);
|
background-color: hsla(15, 75%, 97%, 1);
|
||||||
box-shadow: inset 0 5px 5px -5px #b8b8b8;
|
box-shadow: inset 0 5px 5px -5px #b8b8b8;
|
||||||
color: #a51b00;
|
color: #a51b00;
|
||||||
}
|
}
|
||||||
|
.form-item textarea.error + .cke {
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #e62600;
|
||||||
|
}
|
||||||
.form-item input.error:focus,
|
.form-item input.error:focus,
|
||||||
.form-item textarea.error:focus,
|
.form-item textarea.error:focus,
|
||||||
.form-item select.error:focus {
|
.form-item select.error:focus {
|
||||||
|
@ -94,6 +96,19 @@ label[for] {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
}
|
}
|
||||||
|
.form-error-message {
|
||||||
|
margin-top: 0.15em;
|
||||||
|
color: #ea2800;
|
||||||
|
}
|
||||||
|
.fieldset-wrapper > .form-error-message {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.text-format-wrapper .form-error-message {
|
||||||
|
border: solid #ccc;
|
||||||
|
border-width: 0 1px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.25em 0.666em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Filter */
|
/* Filter */
|
||||||
|
@ -105,6 +120,7 @@ div.description,
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
.form-item .description.error {
|
.form-item .description.error {
|
||||||
|
margin-top: 0;
|
||||||
color: #a51b00;
|
color: #a51b00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,15 +199,6 @@ textarea.form-textarea {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-item .password-suggestions {
|
|
||||||
float: left; /* LTR */
|
|
||||||
clear: left; /* LTR */
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
[dir="rtl"] .form-item .password-suggestions {
|
|
||||||
float: right;
|
|
||||||
clear: right;
|
|
||||||
}
|
|
||||||
.form-item-pass .description {
|
.form-item-pass .description {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue