You are here

public static function ConditionalFieldsFormHelper::formValidate in Conditional Fields 8

Same name and namespace in other branches
  1. 4.x src/ConditionalFieldsFormHelper.php \Drupal\conditional_fields\ConditionalFieldsFormHelper::formValidate()

Validation callback for any form with conditional fields.

This validation callback is added to all forms that contain fields with dependencies. It removes all validation errors from dependent fields whose dependencies are not triggered, which were collected at field-level validation in ConditionalFieldsFormHelper::dependentValidate().

See also

\Drupal\conditional_fields\ConditionalFieldsFormHelper::dependentValidate()

File

src/ConditionalFieldsFormHelper.php, line 489

Class

ConditionalFieldsFormHelper
Helper to interact with forms.

Namespace

Drupal\conditional_fields

Code

public static function formValidate($form, FormStateInterface &$form_state) {
  if (empty($form_state
    ->getValue('conditional_fields_untriggered_dependents'))) {
    return;
  }
  $untriggered_dependents_errors = [];
  foreach ($form_state
    ->getValue('conditional_fields_untriggered_dependents') as $field) {
    $parent = [
      $field['parents'][0],
    ];
    $dependent = NestedArray::getValue($form, $parent);
    $field_values_location = self::formFieldGetValues($dependent, $form_state);

    // If we couldn't find a location for the field's submitted values, let the
    // validation errors pass through to avoid security holes.
    if (!isset($field_values_location[reset($dependent['#array_parents'])])) {
      if (!empty($field['errors'])) {
        $untriggered_dependents_errors = array_merge($untriggered_dependents_errors, $field['errors']);
      }
      continue;
    }

    // Save the changed array back in place.
    // Do not use form_set_value() since it assumes
    // that the values are located at
    // $form_state['values'][ ... $element['#parents'] ... ], while the
    // documentation of hook_field_widget_form() states that field values are
    // $form_state['values'][ ... $element['#field_parents'] ... ].
    NestedArray::setValue($form_state['values'], $dependent['#field_parents'], $field_values_location);
    if (!empty($field['errors'])) {
      $untriggered_dependents_errors = array_merge($untriggered_dependents_errors, $field['errors']);
    }
  }
  if (!empty($untriggered_dependents_errors)) {

    // Since Drupal provides no clean way to selectively remove error messages,
    // we have to store all current form errors and error messages, clear them,
    // filter out from our stored values the errors originating from untriggered
    // dependent fields, and then reinstate remaining errors and messages.
    $errors = array_diff_assoc((array) $form_state
      ->getErrors(), $untriggered_dependents_errors);
    $form_state
      ->clearErrors();
    $error_messages = \Drupal::messenger()
      ->messagesByType('error');
    $removed_messages = array_values($untriggered_dependents_errors);

    // Reinstate remaining errors.
    foreach ($errors as $name => $error) {
      $form_state
        ->setErrorByName($name, $error);

      // form_set_error() calls drupal_set_message(), so we have to filter out
      // these from the messages to avoid duplicates.
      $removed_messages[] = $error;
    }

    // Reinstate remaining error messages (which, at this point,
    // are messages that were originated outside of the validation process).
    if (!empty($error_messages['error'])) {
      $error_messages_array = $error_messages['error'] instanceof MarkupInterface ? $error_messages['error']
        ->jsonSerialize() : $error_messages['error'];
      foreach (array_diff($error_messages_array, $removed_messages) as $message) {
        \Drupal::messenger()
          ->addMessage($message, 'error');
      }
    }
  }
}