Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ If you're using the `AjaxCompositeValidator` on a form that uses [undefinedoffse

- **[`AjaxCompositeValidator`][5]**
Subclass of [`CompositeValidator`][6] that provides AJAX validation. Resolves [an issue with losing data][7], faster turn-around for fixing validation problems, and provides a way to use the same validation for 'client-side' validation of frontend forms.
- **[`SimpleFieldsValidator`][8]**
Ensures the internal validation of form fields by calling `validate()` on them.
- **[`RequiredFieldsValidator`][9]**
Like Silverstripe's [`RequiredFields`][10] validator, but more convenient for use in a `CompositeValidator`.
- **[`WarningFieldsValidator`][11]**
Expand All @@ -40,8 +38,6 @@ Uses [`SearchFilter`s][13] to define fields as required conditionally, based on
Require a specific [elemental block(s)][15] to exist in the `ElementalArea`, with optional minimum and maximum numbers of blocks and optional positional validation.
- **[`ConstraintsValidator`][16]**
Validate values against [`symfony/validation` constraints](https://symfony.com/doc/current/reference/constraints.html). This is super powerful - definitely check it out.
- **[`RegexFieldsValidator`][17]** (deprecated)
Ensure some field(s) matches a specified regex pattern.

### [Abstract Validators][18]

Expand All @@ -65,7 +61,6 @@ Like `ValidatesMultipleFields` but requires a configuration array for each field
[5]: docs/en/01-validators.md#ajaxcompositevalidator
[6]: https://api.silverstripe.org/4/SilverStripe/Forms/CompositeValidator.html
[7]: https://github.com/silverstripe/silverstripe-elemental/issues/764
[8]: docs/en/01-validators.md#simplefieldsvalidator
[9]: docs/en/01-validators.md#requiredfieldsvalidator
[10]: https://api.silverstripe.org/4/SilverStripe/Forms/RequiredFields.html
[11]: docs/en/01-validators.md#warningfieldsvalidator
Expand All @@ -74,7 +69,6 @@ Like `ValidatesMultipleFields` but requires a configuration array for each field
[14]: docs/en/01-validators.md#requiredblocksvalidator
[15]: https://github.com/silverstripe/silverstripe-elemental
[16]: docs/en/01-validators.md#constraintsvalidator
[17]: docs/en/01-validators.md#regexfieldsvalidator
[18]: docs/en/01-validators.md#abstract-validators
[19]: docs/en/01-validators.md#basevalidator
[20]: docs/en/01-validators.md#fieldhasvaluevalidator
Expand Down
10 changes: 0 additions & 10 deletions _config/config.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
---
Name: guysartorelli-composable-validators-config
---
# Don't validate the NocaptchaField during AJAX validation as it can only be validated once.
Signify\ComposableValidators\Validators\SimpleFieldsValidator:
ignore_field_classes_on_ajax:
- UndefinedOffset\NoCaptcha\Forms\NocaptchaField

# Add styling to CMS
SilverStripe\Admin\LeftAndMain:
extra_requirements_css:
- 'guysartorelli/silverstripe-composable-validators:client/dist/left-and-main.css'

# Replace new FieldsValidator with our SimpleFieldsValidator
SilverStripe\Core\Injector\Injector:
SilverStripe\Forms\FieldsValidator:
class: Signify\ComposableValidators\Validators\SimpleFieldsValidator
16 changes: 7 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"keywords": [
"silverstripe",
"composable",
"composite",
"CompositeValidator",
"validator",
"validators",
Expand All @@ -19,7 +18,6 @@
"frontend",
"dependent",
"required",
"regex",
"warning",
"elemental"
],
Expand All @@ -37,15 +35,15 @@
"issues": "https://github.com/GuySartorelli/silverstripe-composable-validators/issues"
},
"require": {
"php": "^8.1",
"silverstripe/framework": "^5.2"
"php": "^8.3",
"silverstripe/framework": "^6"
},
"require-dev": {
"silverstripe/cms": "^5",
"silverstripe/asset-admin": "^2",
"dnadesign/silverstripe-elemental": "^5",
"silverstripe/frameworktest": "^1",
"silverstripe/recipe-testing": "^3"
"silverstripe/cms": "^6",
"silverstripe/asset-admin": "^3",
"dnadesign/silverstripe-elemental": "^6",
"silverstripe/frameworktest": "^2",
"silverstripe/recipe-testing": "^4"
},
"extra": {
"expose": [
Expand Down
95 changes: 9 additions & 86 deletions docs/en/01-validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,28 @@ All of these validators can be used in the CMS _and_ in the front-end.

## AjaxCompositeValidator

**Important:** See the [extensions docs](./02-extensions.md) for extensions that are highly recommended if you intend to use this validator.
> [!IMPORTANT]
> See the [extensions docs](./02-extensions.md) for extensions that are highly recommended if you intend to use this validator.

Note that to use this validator on the frontend, you will need to expose `jQuery` as a global variable. To avoid providing outdated or redundant copies of jQuery this module doesn't come packaged with it.

As of Silverstripe 4.7.0, all `DataObject`s have a [`CompositeValidator`](https://api.silverstripe.org/4/SilverStripe/Forms/CompositeValidator.html) automatically for CMS forms. The `AjaxCompositeValidator` is a subclass of that validator and provides AJAX validation that takes affect prior to form submission. When you click a form action that isn't validation exempt, an AJAX request is made to validate the form _prior_ to form submission. If there are any validation errors, form submission will be blocked and validation messages displayed.
All `DataObject`s have a [`CompositeValidator`](https://api.silverstripe.org/6/SilverStripe/Forms/Validation/CompositeValidator.html) automatically for CMS forms. The `AjaxCompositeValidator` is a subclass of that validator and provides AJAX validation that takes affect prior to form submission. When you click a form action that isn't validation exempt, an AJAX request is made to validate the form _prior_ to form submission. If there are any validation errors, form submission will be blocked and validation messages displayed.

This is useful for situations where data can be lost with the normal Silverstripe validation pipeline (e.g. validating fields on a page that has an [elemental area](https://github.com/silverstripe/silverstripe-elemental) - see [this issue](https://github.com/silverstripe/silverstripe-elemental/issues/764)), and is also faster as it doesn't need to reload the form after validating to display the error messages.
This is typically faster as it doesn't need to reload and re-render the form after validating to display the error messages.

This validator is also extremely useful for front-end forms, as it provides client-side-esque validation without having to re-write all of your validation logic. It even checks for Google Recaptcha v2, and will produce an error if it identifies that a Recaptcha v2 exists for the form but was not completed.

### Usage

If the [`DataObjectDefaultAjaxExtension`](./02-extensions.md#dataobjectdefaultajaxextension) extension has been applied, calling `parent::getCMSCompositeValidator()` inside the `getCMSCompositeValidator()` method will return an `AjaxCompositeValidator`, which can then be manipulated.
> [!HINT]
> See the [optional configuration](./02-extensions.md) for information about replacing the default `CompositeValidator` with `AjaxCompositeValidator`.

You can also opt to just return a new `AjaxCompositeValidator` from that method - and in front-end situations, you can instantiate a new `AjaxCompositeValidator` (preferably [via injection](https://docs.silverstripe.org/en/developer_guides/extending/injector/) i.e. `AjaxCompositeValidator::create()`).
You can also opt to just return a new `AjaxCompositeValidator` from `getCMSCompositeValidator()` - and in front-end situations, you can instantiate a new `AjaxCompositeValidator` (preferably [via injection](https://docs.silverstripe.org/en/developer_guides/extending/injector/) i.e. `AjaxCompositeValidator::create()`).

Ajax validation can also be disabled at any stage, if there is a cause for doing so.

```PHP
// This example assumes use of the validator in the CMS, with the DataObjectDefaultAjaxExtension extension applied.
// If this was for frontend use, you would be well-advised to explicitly include a SimpleFieldsValidator.
// This example assumes use of the validator in the CMS, with the optional configuration applied.
public function getCMSCompositeValidator(): CompositeValidator
{
$validator = parent::getCMSCompositeValidator();
Expand All @@ -52,67 +53,12 @@ public function getCMSCompositeValidator(): CompositeValidator

Some actions (such as delete, archive, and restore) should be allowed even if the data is not valid (we should be allowed to delete an object _especially_ if its data is invalid), but those actions are not validation exempt by default. [Two extensions](./02-extensions.md#dataobjectvalidationexemptionextension-and-gridfielditemrequestvalidationexemptionextension) are provided with this module to remedy this and we strongly recommend applying them.

## SimpleFieldsValidator

This validator simply calls validate on all fields in the form, ensuring the internal validation of form fields. It should _always_ be included in an `AjaxCompositeValidator` unless some other validator being used also performs that function (such as Silverstripe's own `RequiredFields` validator - though you should generally use this module's `RequiredFieldsValidator` instead).

### Usage

In most situations this validator will require no configuration at all.

However, this validator comes with [an extension](02-extensions.md#formfieldextension) which adds `setOmitFieldValidation()` and `getOmitFieldValidation()` methods to all `FormField`s. This can be used if, for specific use cases, internal field validation should be conditional. In that case you can set `OmitFieldValidation` to true, and handle the conditional validation of the field in a separate validator.

```PHP
// In the DataObject which needs the custom validation
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->add(SomeField::create('FieldName')->setOmitFieldValidation(true));
return $fields;
}
public function getCMSCompositeValidator() : CompositeValidator
{
$validator = parent::getCMSCompositeValidator();
$validator->addValidator(CustomValidator::create());
return $validator;
}

// In your CustomValidator
public function php($data)
{
$valid = true;
/* Other custom validation here */
if ($fieldNeedsValidation) {
$someField = $this->form->Fields()->dataFieldByName('FieldName');
$valid = $someField->validate($this) && $valid;
}
return $valid;
}
```

You may also want to omit certain `FormField` subclasses from validation during AJAX validation calls (assuming you're using the `AjaxCompositeValidator`), and only validate them during the final form submission. This can be useful if (as in the case of the [undefinedoffset/silverstripe-nocaptcha](https://github.com/UndefinedOffset/silverstripe-nocaptcha) module's `NocaptchaField`) the field cannot be validated more than once with the same value.

You can do this by setting the class name for that `FormField` in the `SimpleFieldsValidator`'s `ignore_field_classes_on_ajax` config array.

```yml
Signify\ComposableValidators\Validators\SimpleFieldsValidator:
ignore_field_classes_on_ajax:
- UndefinedOffset\NoCaptcha\Forms\NocaptchaField
```

That specific class is already added by default, but you can add others if you find similar situations.

## RequiredFieldsValidator

This is a composable replacement for [`RequiredFields`](https://api.silverstripe.org/4/SilverStripe/Forms/RequiredFields.html). It doesn't perform the internal field validation that validator does, with the assumption that it will be paired with a `SimpleFieldsValidator`. It uses (so has all of the functionality and methods of) the [`ValidatesMultipleFields`](#validatesmultiplefields) trait.
This is a composable replacement for [`RequiredFields`](https://api.silverstripe.org/4/SilverStripe/Forms/RequiredFields.html). It uses (so has all of the functionality and methods of) the [`ValidatesMultipleFields`](#validatesmultiplefields) trait.

Displays a validation error if the field(s) has no value.

### Known Issues

While this validator can be used to require data in `GridField`s, as of writing this documentation GridFields don't display validation errors. This [was resolved](https://github.com/silverstripe/silverstripe-framework/pull/10015) in Silverstripe 4.10.0, but for anyone using an older version in the meantime [an extension](./02-extensions.md#gridfieldmessagesextension) is included with this module to fix this problem. The `AjaxCompositeValidator` will display validation error messages against GridFields even without that extension.
This applies to the `WarningFieldsValidator` as well.

## WarningFieldsValidator

Similar to `RequiredFieldsValidator` except instead of blocking the item from saving, this allows the item to save and displays a warning rather than a full validation error. It uses (so has all of the functionality and methods of) the [`ValidatesMultipleFields`](#validatesmultiplefields) trait.
Expand Down Expand Up @@ -240,29 +186,6 @@ See the Symfony [validation constraints reference](https://symfony.com/doc/curre

See [validation using `symfony/validator` constraints](https://docs.silverstripe.org/en/developer_guides/model/validation/#symfony-validator) in the Silverstripe CMS documentation for any limitations imposed by Silverstripe CMS itself on this kind of validation.

## RegexFieldsValidator

> [!WARNING]
> Deprecated! Use `ConstraintsValidator` with a [`Regex` constraint](https://symfony.com/doc/current/reference/constraints/Regex.html) instead.

This validator is used to require field values to match a specific regex pattern. Often it will make sense to have this validation inside a custom `FormField` implementation, but for one-off specific pattern validation of fields that don't warrant their own `FormField` this validator is perfect. It uses (so has all of the functionality and methods of) the [`ValidatesMultipleFieldsWithConfig`](#validatesmultiplefieldswithconfig) trait.

Any value that cannot be converted to a string cannot be checked against regex and so is ignored, and therefore implicitly passes validation.

In the below example, the `NotOnlyNumbersField` field must match one of the specified regex patterns.

```php
RegexFieldsValidator::create([
'NotOnlyNumbersField' => [
'/(?!^\d+$)^.*?$/' => 'must not consist entirely of numbers',
'/^[\d]$/' => 'must have only one digit',
]
]);
```

**Note:** If any one of the patterns is matched, it passes validation. If none of the patterns match, all of the corresponding messages are displayed, including a generic prefix. So in the above example, if none of the patterns match the value of `NotOnlyNumbersField`, the following validation error message will display:
`The value for "NotOnlyNumbersField" must not consist entirely of numbers or must have only one digit`

## Abstract Validators

### BaseValidator
Expand Down
29 changes: 8 additions & 21 deletions docs/en/02-extensions.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
# Extensions
# Optional configuration

These extensions are not applied by default, but it is strongly recommended you do apply them in your project.

## DataObjectDefaultAjaxExtension
You may want to replace the default `CompositeValidator` that all `DataObject`s have (see `DataObject::getCMSCompositeValidator()`) with this module's [`AjaxCompositeValidator`](./01-validators.md#ajaxcompositevalidator).

```yml
SilverStripe\ORM\DataObject:
extensions:
- Signify\ComposableValidators\Extensions\DataObjectDefaultAjaxExtension
Injector:
SilverStripe\Forms\Validation\CompositeValidator:
class: 'Signify\ComposableValidators\Validators\AjaxCompositeValidator'
```

Replaces the default `CompositeValidator` that all `DataObject`s have (see `DataObject::getCMSCompositeValidator()`) with this module's [`AjaxCompositeValidator`](./01-validators.md#ajaxcompositevalidator).
Unfortunately at the time of writing these docs, the `CompositeValidator` is instantiated using the `new` keyword instead of using the `create()` method, so you can't just replace it outright in the `Injector` - but even if you could, we'd strongly recommend including a `SimpleFieldsValidator`, which would be tricky if possible at all to do via the `Injector`.
# Optional extensions

This extension also automatically adds a [`SimpleFieldsValidator`](./01-validators.md#simplefieldsvalidator) to ensure all form fields have valid data.
These extensions are not applied by default, but it is strongly recommended you do apply them in your project.

## DataObjectValidationExemptionExtension and GridFieldItemRequestValidationExemptionExtension

Expand All @@ -31,16 +28,6 @@ For whatever reason, the "delete", "archive", and "restore" actions in Silverstr

**These extensions are necessary** if you're using the `AjaxCompositeValidator`, but aren't applied by default in case they cause issues in some projects.

## GridFieldMessagesExtension

```yml
SilverStripe\Forms\GridField\GridField:
extensions:
- Signify\ComposableValidators\Extensions\GridFieldMessagesExtension
```

Ensures validation messages display for a `GridField`. `GridField`s didn't display validation messages prior to 4.10.0.

# Default extensions

These extensions are already applied by default. They shouldn't interfere with any project or vendor code, and are necessary for certain features to function correctly.
Expand All @@ -51,4 +38,4 @@ Provides the action used for AJAX validation via the [`AjaxCompositeValidator`](

## FormFieldExtension

Provides the `setOmitFieldValidation()` and `getOmitFieldValidation()` methods to determine if fields should be validated by the [`SimpleFieldsValidator`](./01-validators.md#simplefieldsvalidator).
Provides the `setOmitFieldValidation()` and `getOmitFieldValidation()` methods to determine if `validate()` should be called on the `FormField` instances in `Form::validate()`
27 changes: 0 additions & 27 deletions src/Extensions/DataObjectDefaultAjaxExtension.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Extensions/DataObjectValidationExemptionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class DataObjectValidationExemptionExtension extends Extension
* @see SiteTree::getCMSActions()
* @see GridFieldItemRequestValidationExemptionExtension::updateFormActions()
*/
public function updateCMSActions(FieldList $actions)
protected function updateCMSActions(FieldList $actions): void
{
// Can't just use dataFieldByName because sometimes action_save is there twice which throws an exception.
$ignoreValidationActions = [
Expand Down
6 changes: 1 addition & 5 deletions src/Extensions/FormExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ class FormExtension extends Extension
{
/**
* The actual action used to trigger AJAX validation.
*
* @param array $data
* @param Form $form
* @return HTTPResponse
*/
public function app_ajaxValidate($data, $form)
public function app_ajaxValidate(array $data, Form $form): HTTPResponse
{
$msg = null;
$result = $form->getValidator()->validate(true);
Expand Down
Loading