diff --git a/src/Forms/RequiredFields.php b/src/Forms/RequiredFields.php index 3f0e055c0b7..9e226309237 100644 --- a/src/Forms/RequiredFields.php +++ b/src/Forms/RequiredFields.php @@ -14,6 +14,11 @@ */ class RequiredFields extends Validator { + /** + * Whether to globally allow whitespace only as a valid value for a required field + * Can be overridden on a per-instance basis + */ + private static bool $allow_whitespace_only = true; /** * List of required fields @@ -22,6 +27,12 @@ class RequiredFields extends Validator */ protected $required; + /** + * Whether to allow whitespace only as a valid value for a required field for this instance + * By default, this is set to null which will revert to the global default + */ + private ?bool $allowWhitespaceOnly = null; + /** * Pass each field to be validated as a separate argument to the constructor * of this object. (an array of elements are ok). @@ -41,6 +52,22 @@ public function __construct() parent::__construct(); } + /** + * Get whether to allow whitespace only as a valid value for a required field + */ + public function getAllowWhitespaceOnly(): ?bool + { + return $this->allowWhitespaceOnly ?? static::config()->get('allow_whitespace_only'); + } + + /** + * Set whether to allow whitespace only as a valid value for a required field + */ + public function setAllowWhitespaceOnly(?bool $allow) + { + $this->allowWhitespaceOnly = $allow; + } + /** * Clears all the validation from this object. * @@ -122,6 +149,10 @@ public function php($data) } } else { $stringValue = (string) $value; + if (!$this->getAllowWhitespaceOnly()) { + $stringValue = preg_replace('/^\s+/u', '', $stringValue); + $stringValue = preg_replace('/\s+$/u', '', $stringValue); + } if (is_a($formField, HasOneRelationFieldInterface::class)) { // test for blank string as well as '0' because older versions of silverstripe/admin FormBuilder // forms created using redux-form would have a value of null for unsaved records diff --git a/tests/php/Forms/RequiredFieldsTest.php b/tests/php/Forms/RequiredFieldsTest.php index 5c080cfc9f9..d6b08255739 100644 --- a/tests/php/Forms/RequiredFieldsTest.php +++ b/tests/php/Forms/RequiredFieldsTest.php @@ -8,6 +8,8 @@ use SilverStripe\Forms\SearchableDropdownField; use SilverStripe\Forms\TreeDropdownField; use SilverStripe\Security\Group; +use SilverStripe\Forms\TextField; +use SilverStripe\Forms\FieldList; class RequiredFieldsTest extends SapphireTest { @@ -322,4 +324,135 @@ public function testHasOneRelationFieldInterfaceValidation(string $className) // '1' passes required field validation $this->assertTrue($validator->php(['TestField' => '1'])); } + + public static function provideAllowWhitespaceOnly(): array + { + return [ + 'no-ws-false' => [ + 'value' => 'abc', + 'allowWhitespaceOnly' => false, + 'expected' => true, + ], + 'no-ws-true' => [ + 'value' => 'abc', + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'left-ws-false' => [ + 'value' => ' abc', + 'allowWhitespaceOnly' => false, + 'expected' => true, + ], + 'left-ws-true' => [ + 'value' => ' abc', + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'right-ws-false' => [ + 'value' => 'abc ', + 'allowWhitespaceOnly' => false, + 'expected' => true, + ], + 'right-ws-true' => [ + 'value' => 'abc ', + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'both-ws-false' => [ + 'value' => ' abc ', + 'allowWhitespaceOnly' => false, + 'expected' => true, + ], + 'both-ws-true' => [ + 'value' => ' abc ', + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'only-ws-false' => [ + 'value' => ' ', + 'allowWhitespaceOnly' => false, + 'expected' => false, + ], + 'only-ws-true' => [ + 'value' => ' ', + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'only-ws-nbsp-false' => [ + 'value' => "\xc2\xa0", + 'allowWhitespaceOnly' => false, + 'expected' => false, + ], + 'only-ws-nbsp-true' => [ + 'value' => "\xc2\xa0", + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'only-ws-unicode-false' => [ + // zero width no-break space + 'value' => "\u{2028}", + 'allowWhitespaceOnly' => false, + 'expected' => false, + ], + 'only-ws-unicode-true' => [ + // zero width no-break space + 'value' => "\u{2028}", + 'allowWhitespaceOnly' => true, + 'expected' => true, + ], + 'no-value-false' => [ + 'value' => '', + 'allowWhitespaceOnly' => false, + 'expected' => false, + ], + 'no-value-true' => [ + 'value' => '', + 'allowWhitespaceOnly' => true, + 'expected' => false, + ], + ]; + } + + /** + * @dataProvider provideAllowWhitespaceOnly + */ + public function testAllowWhitespaceOnlyConfig( + string $value, + bool $allowWhitespaceOnly, + bool $expected, + ): void { + $validator = new RequiredFields(['TestField']); + $this->assertSame(true, $validator->getAllowWhitespaceOnly()); + $field = new TextField('TestField'); + $field->setValue($value); + $form = new Form(null, null, new FieldList([$field]), null, $validator); + RequiredFields::config()->set('allow_whitespace_only', $allowWhitespaceOnly); + $result = $validator->validate($form); + $this->assertEquals($expected, $result->isValid()); + } + + /** + * @dataProvider provideAllowWhitespaceOnly + */ + public function testAllowWhitespaceOnlySetter( + string $value, + bool $allowWhitespaceOnly, + bool $expected, + ): void { + $validator = new RequiredFields(['TestField']); + $validator->setAllowWhitespaceOnly($allowWhitespaceOnly); + $this->assertSame($allowWhitespaceOnly, $validator->getAllowWhitespaceOnly()); + $field = new TextField('TestField'); + $field->setValue($value); + $form = new Form(null, null, new FieldList([$field]), null, $validator); + $result = $validator->validate($form); + $this->assertEquals($expected, $result->isValid()); + // assert that global config makes no difference + RequiredFields::config()->set('allow_whitespace_only', true); + $result = $validator->validate($form); + $this->assertEquals($expected, $result->isValid()); + RequiredFields::config()->set('allow_whitespace_only', false); + $result = $validator->validate($form); + $this->assertEquals($expected, $result->isValid()); + } }