diff --git a/src/Form/AbstractJsonFormsForm.php b/src/Form/AbstractJsonFormsForm.php index a962794..e9b5fdd 100644 --- a/src/Form/AbstractJsonFormsForm.php +++ b/src/Form/AbstractJsonFormsForm.php @@ -155,6 +155,13 @@ protected function buildJsonFormsForm( */ public function validateForm(array &$form, FormStateInterface $formState): void { parent::validateForm($form, $formState); + if ([] !== $formState->getErrors() && [] === $this->determineLimitValidationErrors($formState)) { + // Even though the triggering element has #limit_validation_errors set to + // [] form state might contain errors, e.g. if a no radio button of a + // required radios element was selected. This might happen on + // recalculation. + $formState->clearErrors(); + } if ($formState->isSubmitted() || $formState->isValidationEnforced()) { FormCallbackExecutor::executePreSchemaValidationCallbacks($formState); @@ -240,4 +247,54 @@ protected function doGetSubmittedData(FormStateInterface $formState): array { return FieldNameUtil::toJsonData($data); } + /** + * Copied from + * \Drupal\Core\Form\FormValidator::determineLimitValidationErrors(). + * + * Determines if validation errors should be limited. + * + * @param \Drupal\Core\Form\FormStateInterface $formState + * The current state of the form. + * + * @return array|null + * + * phpcs:disable Generic.Files.LineLength.TooLong + */ + private function determineLimitValidationErrors(FormStateInterface &$formState): ?array { + // While this element is being validated, it may be desired that some + // calls to \Drupal\Core\Form\FormStateInterface::setErrorByName() be + // suppressed and not result in a form error, so that a button that + // implements low-risk functionality (such as "Previous" or "Add more") that + // doesn't require all user input to be valid can still have its submit + // handlers triggered. The triggering element's #limit_validation_errors + // property contains the information for which errors are needed, and all + // other errors are to be suppressed. The #limit_validation_errors property + // is ignored if submit handlers will run, but the element doesn't have a + // #submit property, because it's too large a security risk to have any + // invalid user input when executing form-level submit handlers. + $triggering_element = $formState->getTriggeringElement(); + if (isset($triggering_element['#limit_validation_errors']) && ($triggering_element['#limit_validation_errors'] !== FALSE) && !($formState->isSubmitted() && !isset($triggering_element['#submit']))) { + return $triggering_element['#limit_validation_errors']; + } + // If submit handlers won't run (due to the submission having been + // triggered by an element whose #executes_submit_callback property isn't + // TRUE), then it's safe to suppress all validation errors, and we do so + // by default, which is particularly useful during an Ajax submission + // triggered by a non-button. An element can override this default by + // setting the #limit_validation_errors property. For button element + // types, #limit_validation_errors defaults to FALSE, so that full + // validation is their default behavior. + elseif ($triggering_element && !isset($triggering_element['#limit_validation_errors']) && !$formState->isSubmitted()) { + return []; + } + // As an extra security measure, explicitly turn off error suppression if + // one of the above conditions wasn't met. Since this is also done at the + // end of this function, doing it here is only to handle the rare edge + // case where a validate handler invokes form processing of another form. + else { + return NULL; + } + // phpcs:enable + } + } diff --git a/src/Form/Control/Callbacks/RecalculateCallback.php b/src/Form/Control/Callbacks/RecalculateCallback.php index 35412fb..a5fb56d 100644 --- a/src/Form/Control/Callbacks/RecalculateCallback.php +++ b/src/Form/Control/Callbacks/RecalculateCallback.php @@ -32,10 +32,6 @@ final class RecalculateCallback { * sill can perform recalculation. */ public static function addAjaxCommands(AjaxResponse $response, FormStateInterface $formState): void { - // Even though the triggering element has #limit_validation_errors set to [] - // form state might contain errors, e.g. if a no radio button of a required - // radios element was selected. - $formState->clearErrors(); /** @var \Drupal\json_forms\Form\AbstractJsonFormsForm $formObject */ $formObject = $formState->getFormObject(); $newData = FieldNameUtil::toFormData($formObject->calculateData($formState));