Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Form / FormErrorHandler.php
diff --git a/web/core/lib/Drupal/Core/Form/FormErrorHandler.php b/web/core/lib/Drupal/Core/Form/FormErrorHandler.php
new file mode 100644 (file)
index 0000000..9783cb5
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+
+namespace Drupal\Core\Form;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Render\Element;
+
+/**
+ * Handles form errors.
+ */
+class FormErrorHandler implements FormErrorHandlerInterface {
+
+  /**
+   * {@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) {
+    $errors = $form_state->getErrors();
+
+    // Loop through all form errors and set an error message.
+    foreach ($errors as $error) {
+      $this->drupalSetMessage($error, 'error');
+    }
+  }
+
+  /**
+   * Stores errors and a list of child element errors directly on each element.
+   *
+   * Grouping elements like containers, details, fieldgroups and fieldsets may
+   * need error info from their child elements to be able to accessibly show
+   * form error messages to a user. For example, a details element should be
+   * opened when child elements have errors.
+   *
+   * Grouping example:
+   * Assume you have a 'street' element somewhere in a form, which is displayed
+   * in a details element 'address'. It might be:
+   * @code
+   * $form['street'] = [
+   *   '#type' => 'textfield',
+   *   '#title' => $this->t('Street'),
+   *   '#group' => 'address',
+   *   '#required' => TRUE,
+   * ];
+   * $form['address'] = [
+   *   '#type' => 'details',
+   *   '#title' => $this->t('Address'),
+   * ];
+   * @endcode
+   *
+   * When submitting an empty street field, the generated error is available to
+   * the different render elements like so:
+   * @code
+   * // The street textfield element.
+   * $element = [
+   *   '#errors' => {Drupal\Core\StringTranslation\TranslatableMarkup},
+   *   '#children_errors' => [],
+   * ];
+   * // The address detail element.
+   * $element = [
+   *   '#errors' => null,
+   *   '#children_errors' => [
+   *      'street' => {Drupal\Core\StringTranslation\TranslatableMarkup}
+   *   ],
+   * ];
+   * @endcode
+   *
+   * The list of child element errors of an element is an associative array. A
+   * child element error is keyed with the #array_parents value of the
+   * respective element. The key is formed by imploding this value with '][' as
+   * glue. For example, a value ['contact_info', 'name'] becomes
+   * 'contact_info][name'.
+   *
+   * @param array $form
+   *   An associative array containing a reference to the complete structure of
+   *   the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $elements
+   *   An associative array containing the part of the form structure that will
+   *   be processed while traversing up the tree. For recursion only; leave
+   *   empty when calling this method.
+   */
+  protected function setElementErrorsFromFormState(array &$form, FormStateInterface $form_state, array &$elements = []) {
+    // At the start of traversing up the form tree set the to be processed
+    // elements to the complete form structure by reference so that we can
+    // modify the original form. When processing grouped elements a reference to
+    // the complete form is needed.
+    if (empty($elements)) {
+      $elements = &$form;
+    }
+
+    // Recurse through all element children.
+    foreach (Element::children($elements) as $key) {
+      if (!empty($elements[$key])) {
+        // Get the child by reference so that we can update the original form.
+        $child = &$elements[$key];
+
+        // Call self to traverse up the form tree. The current element's child
+        // contains the next elements to be processed.
+        $this->setElementErrorsFromFormState($form, $form_state, $child);
+
+        $children_errors = [];
+
+        // Inherit all recorded "children errors" of the direct child.
+        if (!empty($child['#children_errors'])) {
+          $children_errors = $child['#children_errors'];
+        }
+
+        // Additionally store the errors of the direct child itself, keyed by
+        // it's parent elements structure.
+        if (!empty($child['#errors'])) {
+          $child_parents = implode('][', $child['#array_parents']);
+          $children_errors[$child_parents] = $child['#errors'];
+        }
+
+        if (!empty($elements['#children_errors'])) {
+          $elements['#children_errors'] += $children_errors;
+        }
+        else {
+          $elements['#children_errors'] = $children_errors;
+        }
+
+        // If this direct child belongs to a group populate the grouping element
+        // with the children errors.
+        if (!empty($child['#group'])) {
+          $parents = explode('][', $child['#group']);
+          $group_element = NestedArray::getValue($form, $parents);
+          if (isset($group_element['#children_errors'])) {
+            $group_element['#children_errors'] = $group_element['#children_errors'] + $children_errors;
+          }
+          else {
+            $group_element['#children_errors'] = $children_errors;
+          }
+          NestedArray::setValue($form, $parents, $group_element);
+        }
+      }
+    }
+
+    // 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);
+  }
+
+}