diff --git a/src/Form/Control/Callbacks/EmailValidateCallback.php b/src/Form/Control/Callbacks/EmailValidateCallback.php new file mode 100644 index 0000000..8e4c522 --- /dev/null +++ b/src/Form/Control/Callbacks/EmailValidateCallback.php @@ -0,0 +1,45 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\json_forms\Form\Control\Callbacks; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\Email; + +final class EmailValidateCallback { + + /** + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $formState + * @param array $completeForm + */ + public static function validate(array &$element, FormStateInterface $formState, array $completeForm): void { + // @phpstan-ignore argument.type + $value = $formState->getValue($element['#parents']); + + // Prevent Email::validateEmail() from setting an empty string when the + // current value is NULL and allowed to be NULL. + if (NULL !== $value || TRUE !== $element['#_nullable']) { + Email::validateEmail($element, $formState, $completeForm); + } + } + +} diff --git a/src/Form/Control/Callbacks/StringValueCallback.php b/src/Form/Control/Callbacks/StringValueCallback.php new file mode 100644 index 0000000..e3ad6c1 --- /dev/null +++ b/src/Form/Control/Callbacks/StringValueCallback.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\json_forms\Form\Control\Callbacks; + +use Drupal\Core\Form\FormStateInterface; + +final class StringValueCallback { + + /** + * @param array $element + * @param mixed $input + * @param \Drupal\Core\Form\FormStateInterface $formState + * + * @return mixed + */ + public static function convert(array $element, $input, FormStateInterface $formState) { + $value = $input; + if (FALSE === $value) { + $value = $element['#default_value'] ?? ''; + } + + if ('' === $value) { + if (TRUE === $element['#_nullable']) { + $value = NULL; + } + } + + if (NULL === $value) { + // Prevent empty string as value. Drupal sets an empty string in this + // case if no value is set in the form state. + $formState->setValueForElement($element, NULL); + } + elseif ('textfield' === $element['#type'] && is_string($value)) { + $value = str_replace(["\r", "\n"], '', $value); + } + + return $value; + } + +} diff --git a/src/Form/Control/Callbacks/UrlValidateCallback.php b/src/Form/Control/Callbacks/UrlValidateCallback.php new file mode 100644 index 0000000..15095a5 --- /dev/null +++ b/src/Form/Control/Callbacks/UrlValidateCallback.php @@ -0,0 +1,45 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\json_forms\Form\Control\Callbacks; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\Url; + +final class UrlValidateCallback { + + /** + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $formState + * @param array $completeForm + */ + public static function validate(array &$element, FormStateInterface $formState, array $completeForm): void { + // @phpstan-ignore argument.type + $value = $formState->getValue($element['#parents']); + + // Prevent Url::validateUrl() from setting an empty string when the current + // value is NULL and allowed to be NULL. + if (NULL !== $value || TRUE !== $element['#_nullable']) { + Url::validateUrl($element, $formState, $completeForm); + } + } + +} diff --git a/src/Form/Control/EmailArrayFactory.php b/src/Form/Control/EmailArrayFactory.php index c8e6821..a05bd24 100644 --- a/src/Form/Control/EmailArrayFactory.php +++ b/src/Form/Control/EmailArrayFactory.php @@ -22,6 +22,7 @@ namespace Drupal\json_forms\Form\Control; use Drupal\Core\Form\FormStateInterface; +use Drupal\json_forms\Form\Control\Callbacks\EmailValidateCallback; use Drupal\json_forms\Form\FormArrayFactoryInterface; use Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition; use Drupal\json_forms\JsonForms\Definition\DefinitionInterface; @@ -42,6 +43,7 @@ public function createFormArray( ): array { return [ '#type' => 'email', + '#element_validate' => [EmailValidateCallback::class . '::validate'], ] + parent::createFormArray($definition, $formState, $formArrayFactory); } diff --git a/src/Form/Control/StringArrayFactory.php b/src/Form/Control/StringArrayFactory.php index a2d9d38..506e9b4 100644 --- a/src/Form/Control/StringArrayFactory.php +++ b/src/Form/Control/StringArrayFactory.php @@ -24,6 +24,7 @@ use Assert\Assertion; use Drupal\Core\Form\FormStateInterface; use Drupal\json_forms\Form\AbstractConcreteFormArrayFactory; +use Drupal\json_forms\Form\Control\Callbacks\StringValueCallback; use Drupal\json_forms\Form\Control\Util\BasicFormPropertiesFactory; use Drupal\json_forms\Form\FormArrayFactoryInterface; use Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition; @@ -47,6 +48,7 @@ public function createFormArray( $form = [ '#type' => TRUE === $definition->getOptionsValue('multi', FALSE) ? 'textarea' : 'textfield', + '#value_callback' => StringValueCallback::class . '::convert', ] + BasicFormPropertiesFactory::createFieldProperties($definition, $formState); if (NULL !== $definition->getMaxLength()) { diff --git a/src/Form/Control/UrlArrayFactory.php b/src/Form/Control/UrlArrayFactory.php index 58f2f38..8df48ec 100644 --- a/src/Form/Control/UrlArrayFactory.php +++ b/src/Form/Control/UrlArrayFactory.php @@ -22,6 +22,7 @@ namespace Drupal\json_forms\Form\Control; use Drupal\Core\Form\FormStateInterface; +use Drupal\json_forms\Form\Control\Callbacks\UrlValidateCallback; use Drupal\json_forms\Form\FormArrayFactoryInterface; use Drupal\json_forms\JsonForms\Definition\Control\ControlDefinition; use Drupal\json_forms\JsonForms\Definition\DefinitionInterface; @@ -42,6 +43,7 @@ public function createFormArray( ): array { return [ '#type' => 'url', + '#element_validate' => [UrlValidateCallback::class . '::validate'], ] + parent::createFormArray($definition, $formState, $formArrayFactory); } diff --git a/src/Form/Control/Util/BasicFormPropertiesFactory.php b/src/Form/Control/Util/BasicFormPropertiesFactory.php index 344774b..bf2f0b3 100644 --- a/src/Form/Control/Util/BasicFormPropertiesFactory.php +++ b/src/Form/Control/Util/BasicFormPropertiesFactory.php @@ -71,6 +71,7 @@ public static function createFieldProperties(ControlDefinition $definition, Form '#disabled' => $definition->isReadOnly(), '#required' => $definition->isRequired(), '#limit_validation_errors' => [], + '#_nullable' => $definition->isNullable(), ]; if ((!$formState->isCached() || $definition->isReadOnly()) diff --git a/src/JsonForms/Definition/Control/ControlDefinition.php b/src/JsonForms/Definition/Control/ControlDefinition.php index 44f9e8f..2ab6331 100644 --- a/src/JsonForms/Definition/Control/ControlDefinition.php +++ b/src/JsonForms/Definition/Control/ControlDefinition.php @@ -308,6 +308,10 @@ public function getType(): string { return $this->propertySchema->type; } + public function isNullable(): bool { + return in_array('null', (array) $this->propertySchema->type, TRUE); + } + public function isReadOnly(): bool { return $this->controlSchema->options->readonly ?? $this->propertySchema->readOnly ?? (property_exists($this->propertySchema, '$calculate') || $this->parentUiReadonly);