Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Form / FormErrorHandler.php
1 <?php
2
3 namespace Drupal\Core\Form;
4
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Messenger\MessengerTrait;
7 use Drupal\Core\Render\Element;
8
9 /**
10  * Handles form errors.
11  */
12 class FormErrorHandler implements FormErrorHandlerInterface {
13
14   use MessengerTrait;
15
16   /**
17    * {@inheritdoc}
18    */
19   public function handleFormErrors(array &$form, FormStateInterface $form_state) {
20     // After validation check if there are errors.
21     if ($errors = $form_state->getErrors()) {
22       // Display error messages for each element.
23       $this->displayErrorMessages($form, $form_state);
24
25       // Loop through and assign each element its errors.
26       $this->setElementErrorsFromFormState($form, $form_state);
27     }
28
29     return $this;
30   }
31
32   /**
33    * Loops through and displays all form errors.
34    *
35    * @param array $form
36    *   An associative array containing the structure of the form.
37    * @param \Drupal\Core\Form\FormStateInterface $form_state
38    *   The current state of the form.
39    */
40   protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
41     $errors = $form_state->getErrors();
42
43     // Loop through all form errors and set an error message.
44     foreach ($errors as $error) {
45       $this->messenger()->addMessage($error, 'error');
46     }
47   }
48
49   /**
50    * Stores errors and a list of child element errors directly on each element.
51    *
52    * Grouping elements like containers, details, fieldgroups and fieldsets may
53    * need error info from their child elements to be able to accessibly show
54    * form error messages to a user. For example, a details element should be
55    * opened when child elements have errors.
56    *
57    * Grouping example:
58    * Assume you have a 'street' element somewhere in a form, which is displayed
59    * in a details element 'address'. It might be:
60    *
61    * @code
62    * $form['street'] = [
63    *   '#type' => 'textfield',
64    *   '#title' => $this->t('Street'),
65    *   '#group' => 'address',
66    *   '#required' => TRUE,
67    * ];
68    * $form['address'] = [
69    *   '#type' => 'details',
70    *   '#title' => $this->t('Address'),
71    * ];
72    * @endcode
73    *
74    * When submitting an empty street field, the generated error is available to
75    * the different render elements like so:
76    * @code
77    * // The street textfield element.
78    * $element = [
79    *   '#errors' => {Drupal\Core\StringTranslation\TranslatableMarkup},
80    *   '#children_errors' => [],
81    * ];
82    * // The address detail element.
83    * $element = [
84    *   '#errors' => null,
85    *   '#children_errors' => [
86    *      'street' => {Drupal\Core\StringTranslation\TranslatableMarkup}
87    *   ],
88    * ];
89    * @endcode
90    *
91    * The list of child element errors of an element is an associative array. A
92    * child element error is keyed with the #array_parents value of the
93    * respective element. The key is formed by imploding this value with '][' as
94    * glue. For example, a value ['contact_info', 'name'] becomes
95    * 'contact_info][name'.
96    *
97    * @param array $form
98    *   An associative array containing a reference to the complete structure of
99    *   the form.
100    * @param \Drupal\Core\Form\FormStateInterface $form_state
101    *   The current state of the form.
102    * @param array $elements
103    *   An associative array containing the part of the form structure that will
104    *   be processed while traversing up the tree. For recursion only; leave
105    *   empty when calling this method.
106    */
107   protected function setElementErrorsFromFormState(array &$form, FormStateInterface $form_state, array &$elements = []) {
108     // At the start of traversing up the form tree set the to be processed
109     // elements to the complete form structure by reference so that we can
110     // modify the original form. When processing grouped elements a reference to
111     // the complete form is needed.
112     if (empty($elements)) {
113       $elements = &$form;
114     }
115
116     // Recurse through all element children.
117     foreach (Element::children($elements) as $key) {
118       if (!empty($elements[$key])) {
119         // Get the child by reference so that we can update the original form.
120         $child = &$elements[$key];
121
122         // Call self to traverse up the form tree. The current element's child
123         // contains the next elements to be processed.
124         $this->setElementErrorsFromFormState($form, $form_state, $child);
125
126         $children_errors = [];
127
128         // Inherit all recorded "children errors" of the direct child.
129         if (!empty($child['#children_errors'])) {
130           $children_errors = $child['#children_errors'];
131         }
132
133         // Additionally store the errors of the direct child itself, keyed by
134         // its parent elements structure.
135         if (!empty($child['#errors'])) {
136           $child_parents = implode('][', $child['#array_parents']);
137           $children_errors[$child_parents] = $child['#errors'];
138         }
139
140         if (!empty($elements['#children_errors'])) {
141           $elements['#children_errors'] += $children_errors;
142         }
143         else {
144           $elements['#children_errors'] = $children_errors;
145         }
146
147         // If this direct child belongs to a group populate the grouping element
148         // with the children errors.
149         if (!empty($child['#group'])) {
150           $parents = explode('][', $child['#group']);
151           $group_element = NestedArray::getValue($form, $parents);
152           if (isset($group_element['#children_errors'])) {
153             $group_element['#children_errors'] = $group_element['#children_errors'] + $children_errors;
154           }
155           else {
156             $group_element['#children_errors'] = $children_errors;
157           }
158           NestedArray::setValue($form, $parents, $group_element);
159         }
160       }
161     }
162
163     // Store the errors for this element on the element directly.
164     $elements['#errors'] = $form_state->getError($elements);
165   }
166
167 }