mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Validate DBFields
This commit is contained in:
parent
264898ba48
commit
2b2147489e
@ -2,6 +2,8 @@
|
||||
Name: corefieldtypes
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
NullableVarchar:
|
||||
class: SilverStripe\ORM\FieldType\DBNullableVarchar
|
||||
Boolean:
|
||||
class: SilverStripe\ORM\FieldType\DBBoolean
|
||||
Currency:
|
||||
@ -20,6 +22,8 @@ SilverStripe\Core\Injector\Injector:
|
||||
class: SilverStripe\ORM\FieldType\DBDecimal
|
||||
Double:
|
||||
class: SilverStripe\ORM\FieldType\DBDouble
|
||||
Email:
|
||||
class: SilverStripe\ORM\FieldType\DBEmail
|
||||
Enum:
|
||||
class: SilverStripe\ORM\FieldType\DBEnum
|
||||
Float:
|
||||
@ -36,6 +40,8 @@ SilverStripe\Core\Injector\Injector:
|
||||
class: SilverStripe\ORM\FieldType\DBHTMLVarchar
|
||||
Int:
|
||||
class: SilverStripe\ORM\FieldType\DBInt
|
||||
MediumInt:
|
||||
class: SilverStripe\ORM\FieldType\DBMediumInt
|
||||
BigInt:
|
||||
class: SilverStripe\ORM\FieldType\DBBigInt
|
||||
Locale:
|
||||
|
@ -47,6 +47,7 @@
|
||||
"symfony/dom-crawler": "^7.0",
|
||||
"symfony/filesystem": "^7.0",
|
||||
"symfony/http-foundation": "^7.0",
|
||||
"symfony/intl": "^7.0",
|
||||
"symfony/mailer": "^7.0",
|
||||
"symfony/mime": "^7.0",
|
||||
"symfony/translation": "^7.0",
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace SilverStripe\Core\Injector;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
@ -23,6 +24,10 @@ class InjectionCreator implements Factory
|
||||
// Ensure there are no string keys as they cannot be unpacked with the `...` operator
|
||||
$values = array_values($params);
|
||||
|
||||
return new $class(...$values);
|
||||
try {
|
||||
return new $class(...$values);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,18 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use SilverStripe\Validation\EmailValidator;
|
||||
|
||||
/**
|
||||
* Text input field with validation for correct email format according to RFC 2822.
|
||||
* Text input field with validation for correct email format
|
||||
*/
|
||||
class EmailField extends TextField
|
||||
{
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => EmailValidator::class,
|
||||
],
|
||||
];
|
||||
|
||||
protected $inputType = 'email';
|
||||
/**
|
||||
@ -17,39 +24,6 @@ class EmailField extends TextField
|
||||
return 'email text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates for RFC 2822 compliant email addresses.
|
||||
*
|
||||
* @see http://www.regular-expressions.info/email.html
|
||||
* @see http://www.ietf.org/rfc/rfc2822.txt
|
||||
*
|
||||
* @param Validator $validator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
$result = true;
|
||||
$this->value = trim($this->value ?? '');
|
||||
|
||||
$pattern = '^[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$';
|
||||
|
||||
// Escape delimiter characters.
|
||||
$safePattern = str_replace('/', '\\/', $pattern ?? '');
|
||||
|
||||
if ($this->value && !preg_match('/' . $safePattern . '/i', $this->value ?? '')) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t('SilverStripe\\Forms\\EmailField.VALIDATION', 'Please enter an email address'),
|
||||
'validation'
|
||||
);
|
||||
|
||||
$result = false;
|
||||
}
|
||||
|
||||
return $this->extendValidationResult($result, $validator);
|
||||
}
|
||||
|
||||
public function getSchemaValidation()
|
||||
{
|
||||
$rules = parent::getSchemaValidation();
|
||||
|
@ -15,6 +15,8 @@ use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\View\AttributesHTML;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
/**
|
||||
* Represents a field in a form.
|
||||
@ -275,6 +277,8 @@ class FormField extends RequestHandler
|
||||
'Description' => 'HTMLFragment',
|
||||
];
|
||||
|
||||
private static array $field_validators = [];
|
||||
|
||||
/**
|
||||
* Structured schema state representing the FormField's current data and validation.
|
||||
* Used to render the FormField as a ReactJS Component on the front-end.
|
||||
@ -1231,15 +1235,25 @@ class FormField extends RequestHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method each {@link FormField} subclass must implement, determines whether the field
|
||||
* is valid or not based on the value.
|
||||
* Subclasses can define an existing FieldValidatorClass to validate the FormField value
|
||||
* They may also override this method to provide custom validation logic
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
return $this->extendValidationResult(true, $validator);
|
||||
$isValid = true;
|
||||
$name = strip_tags($this->Title() ? $this->Title() : $this->getName());
|
||||
$fieldValidators = FieldValidator::createFieldValidatorsForField($this, $name, $this->value);
|
||||
foreach ($fieldValidators as $fieldValidator) {
|
||||
$validationResult = $fieldValidator->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$validator->getResult()->combineAnd($validationResult);
|
||||
$isValid = false;
|
||||
}
|
||||
}
|
||||
return $this->extendValidationResult($isValid, $validator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use SilverStripe\Validation\StringValidator;
|
||||
|
||||
/**
|
||||
* Text input field.
|
||||
*/
|
||||
@ -14,6 +16,13 @@ class TextField extends FormField implements TippableFieldInterface
|
||||
|
||||
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TEXT;
|
||||
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => StringValidator::class,
|
||||
'argCalls' => [null, 'getMaxLength'],
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Tip|null A tip to render beside the input
|
||||
*/
|
||||
@ -117,31 +126,6 @@ class TextField extends FormField implements TippableFieldInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
$result = true;
|
||||
if (!is_null($this->maxLength) && mb_strlen($this->value ?? '') > $this->maxLength) {
|
||||
$name = strip_tags($this->Title() ? $this->Title() : $this->getName());
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'SilverStripe\\Forms\\TextField.VALIDATEMAXLENGTH',
|
||||
'The value for {name} must not exceed {maxLength} characters in length',
|
||||
['name' => $name, 'maxLength' => $this->maxLength]
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
$result = false;
|
||||
}
|
||||
return $this->extendValidationResult($result, $validator);
|
||||
}
|
||||
|
||||
public function getSchemaValidation()
|
||||
{
|
||||
$rules = parent::getSchemaValidation();
|
||||
|
@ -1230,6 +1230,15 @@ class DataObject extends ModelData implements DataObjectInterface, i18nEntityPro
|
||||
public function validate()
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
// Call DBField::validate() on every DBField
|
||||
$specs = static::getSchema()->fieldSpecs(static::class);
|
||||
foreach (array_keys($specs) as $fieldName) {
|
||||
$dbField = $this->dbObject($fieldName);
|
||||
$validationResult = $dbField->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$result->combineAnd($validationResult);
|
||||
}
|
||||
}
|
||||
$this->extend('updateValidate', $result);
|
||||
return $result;
|
||||
}
|
||||
|
@ -50,6 +50,16 @@ class DBDecimal extends DBField
|
||||
return floor($this->value ?? 0.0);
|
||||
}
|
||||
|
||||
public function getWholeSize(): int
|
||||
{
|
||||
return $this->wholeSize;
|
||||
}
|
||||
|
||||
public function getDecimalSize(): int
|
||||
{
|
||||
return $this->decimalSize;
|
||||
}
|
||||
|
||||
public function requireField(): void
|
||||
{
|
||||
$parts = [
|
||||
|
31
src/ORM/FieldType/DBEmail.php
Normal file
31
src/ORM/FieldType/DBEmail.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\FieldType;
|
||||
|
||||
use SilverStripe\Forms\EmailField;
|
||||
use SilverStripe\ORM\FieldType\DBVarchar;
|
||||
use SilverStripe\Validation\EmailValidator;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\NullableField;
|
||||
|
||||
class DBEmail extends DBVarchar
|
||||
{
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => EmailValidator::class,
|
||||
],
|
||||
];
|
||||
|
||||
public function scaffoldFormField(?string $title = null, array $params = []): ?FormField
|
||||
{
|
||||
// Set field with appropriate size
|
||||
$field = EmailField::create($this->name, $title);
|
||||
$field->setMaxLength($this->getSize());
|
||||
|
||||
// Allow the user to select if it's null instead of automatically assuming empty string is
|
||||
if (!$this->getNullifyEmpty()) {
|
||||
return NullableField::create($field);
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
}
|
@ -10,6 +10,8 @@ use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\ORM\Filters\SearchFilter;
|
||||
use SilverStripe\ORM\Queries\SQLSelect;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
/**
|
||||
* Single field in the database.
|
||||
@ -43,7 +45,6 @@ use SilverStripe\Model\ModelData;
|
||||
*/
|
||||
abstract class DBField extends ModelData implements DBIndexable
|
||||
{
|
||||
|
||||
/**
|
||||
* Raw value of this field
|
||||
*/
|
||||
@ -99,6 +100,8 @@ abstract class DBField extends ModelData implements DBIndexable
|
||||
'ProcessedRAW' => 'HTMLFragment',
|
||||
];
|
||||
|
||||
private static array $field_validators = [];
|
||||
|
||||
/**
|
||||
* Default value in the database.
|
||||
* Might be overridden on DataObject-level, but still useful for setting defaults on
|
||||
@ -468,6 +471,22 @@ abstract class DBField extends ModelData implements DBIndexable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field. Called during DataObject::validate().
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
$fieldValidators = FieldValidator::createFieldValidatorsForField($this, $this->getName(), $this->getValue());
|
||||
foreach ($fieldValidators as $fieldValidator) {
|
||||
$validationResult = $fieldValidator->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$result->combineAnd($validationResult);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FormField instance used as a default
|
||||
* for form scaffolding.
|
||||
|
@ -11,6 +11,8 @@ use SilverStripe\Model\ArrayData;
|
||||
|
||||
/**
|
||||
* Represents a signed 32 bit integer field.
|
||||
*
|
||||
* Ints are always signed i.e. they can be negative
|
||||
*/
|
||||
class DBInt extends DBField
|
||||
{
|
||||
|
24
src/ORM/FieldType/DBMediumInt.php
Normal file
24
src/ORM/FieldType/DBMediumInt.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\FieldType;
|
||||
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
/**
|
||||
* Represents a signed 32 bit integer field.
|
||||
*/
|
||||
class DBMediumInt extends DBInt
|
||||
{
|
||||
public function requireField(): void
|
||||
{
|
||||
$parts = [
|
||||
'datatype' => 'int',
|
||||
'precision' => 8,
|
||||
'null' => 'not null',
|
||||
'default' => $this->defaultVal,
|
||||
'arrayValue' => $this->arrayValue
|
||||
];
|
||||
$values = ['type' => 'int', 'parts' => $parts];
|
||||
DB::require_field($this->tableName, $this->name, $values);
|
||||
}
|
||||
}
|
12
src/ORM/FieldType/DBNullableVarchar.php
Normal file
12
src/ORM/FieldType/DBNullableVarchar.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\FieldType;
|
||||
|
||||
class DBNullableVarchar extends DBVarchar
|
||||
{
|
||||
public function __construct(?string $name = null, int $size = 255, array $options = [])
|
||||
{
|
||||
$options['nullifyEmpty'] = false;
|
||||
parent::__construct($name, $size, $options);
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ use SilverStripe\Forms\NullableField;
|
||||
use SilverStripe\Forms\TextField;
|
||||
use SilverStripe\ORM\Connect\MySQLDatabase;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Validation\StringValidator;
|
||||
|
||||
/**
|
||||
* Class Varchar represents a variable-length string of up to 255 characters, designed to store raw text
|
||||
@ -18,6 +19,13 @@ use SilverStripe\ORM\DB;
|
||||
*/
|
||||
class DBVarchar extends DBString
|
||||
{
|
||||
private static array $field_validators = [
|
||||
[
|
||||
'class' => StringValidator::class,
|
||||
'argCalls' => [null, 'getSize'],
|
||||
]
|
||||
];
|
||||
|
||||
private static array $casting = [
|
||||
'Initial' => 'Text',
|
||||
'URL' => 'Text',
|
||||
|
36
src/Validation/AbstractSymfonyValidator.php
Normal file
36
src/Validation/AbstractSymfonyValidator.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Core\Validation\ConstraintValidator;
|
||||
use SilverStripe\Validation\StringValidator;
|
||||
|
||||
/**
|
||||
* Abstract class for validators that use Symfony constraints
|
||||
*/
|
||||
abstract class AbstractSymfonyValidator extends StringValidator
|
||||
{
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
$result = parent::validateValue($result);
|
||||
if (!$result->isValid()) {
|
||||
return $result;
|
||||
}
|
||||
$message = _t(__CLASS__ . '.INVALID', 'Invalid email address');
|
||||
$constraintClass = $this->getConstraintClass();
|
||||
$constraint = new $constraintClass(message: $message);
|
||||
$validationResult = ConstraintValidator::validate($this->value, $constraint, $this->name);
|
||||
return $result->combineAnd($validationResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* The symfony constraint class to use
|
||||
*/
|
||||
abstract protected function getConstraintClass(): string;
|
||||
|
||||
/**
|
||||
* The message to use when the value is invalid
|
||||
*/
|
||||
abstract protected function getMessage(): string;
|
||||
}
|
27
src/Validation/BooleanValidator.php
Normal file
27
src/Validation/BooleanValidator.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class BooleanValidator extends FieldValidator
|
||||
{
|
||||
private const VALID_VALUES = [
|
||||
true,
|
||||
false,
|
||||
1,
|
||||
0,
|
||||
'1',
|
||||
'0'
|
||||
];
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
if (!in_array($this->value, self::VALID_VALUES, true)) {
|
||||
$message = _t(__CLASS__ . '.INVALID', 'Invalid value');
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
26
src/Validation/CompositeValidator.php
Normal file
26
src/Validation/CompositeValidator.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class CompositeValidator extends FieldValidator
|
||||
{
|
||||
// TODO FieldList / iterable or similar
|
||||
private mixed $children;
|
||||
|
||||
public function __construct(string $name, mixed $value, mixed $children = null)
|
||||
{
|
||||
parent::__construct($name, $value);
|
||||
$this->children = $children;
|
||||
}
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
foreach ($this->children as $child) {
|
||||
$result->combineAnd($child->validate());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
25
src/Validation/DateValidator.php
Normal file
25
src/Validation/DateValidator.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
/**
|
||||
* Validates that a value is a valid date, which means that
|
||||
* - it follows the PHP date format Y-m-d
|
||||
* - it follows the ISO format y-MM-dd i.e. DBDate::ISO_DATE
|
||||
*/
|
||||
class DateValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
// TODO: does this actually work? - symfony code lacks Y-m-d format
|
||||
return Constraints\Date::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid date');
|
||||
}
|
||||
}
|
24
src/Validation/DatetimeValidator.php
Normal file
24
src/Validation/DatetimeValidator.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
/**
|
||||
* Validates that a value is a valid date, which means that
|
||||
* - it follows the PHP date format Y-m-d H:i:s
|
||||
* - it follows the ISO format 'y-MM-dd HH:mm:ss' i.e. DBDateTime::ISO_DATETIME
|
||||
*/
|
||||
class DatetimeValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
return Constraints\DateTime::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid date/time');
|
||||
}
|
||||
}
|
60
src/Validation/DecimalValidator.php
Normal file
60
src/Validation/DecimalValidator.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\NumericValidator;
|
||||
|
||||
class DecimalValidator extends NumericValidator
|
||||
{
|
||||
/**
|
||||
* Whole number size e.g. For Decimal(9,2) this would be 9
|
||||
*/
|
||||
private int $wholeSize;
|
||||
|
||||
/**
|
||||
* Decimal size e.g. For Decimal(9,2) this would be 2
|
||||
*/
|
||||
private int $decimalSize;
|
||||
|
||||
public function __construct(string $name, mixed $value, int $wholeSize, int $decimalSize)
|
||||
{
|
||||
parent::__construct($name, $value);
|
||||
$this->wholeSize = $wholeSize;
|
||||
$this->decimalSize = $decimalSize;
|
||||
}
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
$result = parent::validateValue($result);
|
||||
if (!$result->isValid()) {
|
||||
return $result;
|
||||
}
|
||||
// Example of how digits are stored in the database
|
||||
// Decimal(9,2) is allowed a total of 9 digits, and will always round to 2 decimal places
|
||||
// This means it has a maximum 7 digits before the decimal point
|
||||
//
|
||||
// Valid
|
||||
// 1234567.99
|
||||
// 9999999.99
|
||||
// -9999999.99
|
||||
// 1234567.999 - will round to 1234568.00
|
||||
//
|
||||
// Not valid
|
||||
// 12345678.9 - 8 digits the before the decimal point
|
||||
// 1234567.891 - 10 digits total
|
||||
// 9999999.999 - would be rounted to 10000000.00 which exceeds the 9 digits
|
||||
|
||||
// Convert to absolute value - any the minus sign is not counted
|
||||
$absValue = abs($this->value);
|
||||
// Round to the decimal size which is what the database will do
|
||||
$rounded = round($absValue, $this->decimalSize);
|
||||
// Count this number of digits - note the minus 1 for the decimal point
|
||||
$digitCount = strlen((string) $rounded) - 1;
|
||||
if ($digitCount > $this->wholeSize) {
|
||||
$message = _t(__CLASS__ . '.TOOLARGE', 'Number is too large');
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
19
src/Validation/EmailValidator.php
Normal file
19
src/Validation/EmailValidator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
class EmailValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
return Constraints\Email::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid email address');
|
||||
}
|
||||
}
|
26
src/Validation/EnumValidator.php
Normal file
26
src/Validation/EnumValidator.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class EnumValidator extends FieldValidator
|
||||
{
|
||||
private array $allowedValues;
|
||||
|
||||
public function __construct(string $name, mixed $value, array $allowedValues)
|
||||
{
|
||||
parent::__construct($name, $value);
|
||||
$this->allowedValues = $allowedValues;
|
||||
}
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
if (!in_array($this->value, $this->allowedValues)) {
|
||||
$message = _t(__CLASS__ . '.NOTALLOWED', 'Not an allowed value');
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
76
src/Validation/FieldValidator.php
Normal file
76
src/Validation/FieldValidator.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use RuntimeException;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
* Abstract class that can be used as a validator for FormFields and DBFields
|
||||
*/
|
||||
abstract class FieldValidator
|
||||
{
|
||||
protected string $name;
|
||||
protected mixed $value;
|
||||
|
||||
public function __construct(string $name, mixed $value)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the value
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
$result = $this->validateValue($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner validatation method that that is implemented by subclasses
|
||||
*/
|
||||
abstract protected function validateValue(ValidationResult $result): ValidationResult;
|
||||
|
||||
/**
|
||||
* Create FieldValidators for a field based on the field's configuration
|
||||
*/
|
||||
public static function createFieldValidatorsForField(
|
||||
FormField|DBField $field,
|
||||
string $name,
|
||||
mixed $value
|
||||
): array {
|
||||
$fieldValidators = [];
|
||||
$config = $field->config()->get('field_validators');
|
||||
foreach ($config as $spec) {
|
||||
$class = $spec['class'];
|
||||
$argCalls = $spec['argCalls'] ?? null;
|
||||
if (!is_a($class, FieldValidator::class, true)) {
|
||||
throw new RuntimeException("Class $class is not a FieldValidator");
|
||||
}
|
||||
$args = [$name, $value];
|
||||
if (!is_null($argCalls)) {
|
||||
if (!is_array($argCalls)) {
|
||||
throw new RuntimeException("argCalls for $class is not an array");
|
||||
}
|
||||
foreach ($argCalls as $i => $argCall) {
|
||||
if (!is_string($argCall) && !is_null($argCall)) {
|
||||
throw new RuntimeException("argCall $i for $class is not a string or null");
|
||||
}
|
||||
if ($argCall) {
|
||||
$args[] = call_user_func([$field, $argCall]);
|
||||
} else {
|
||||
$args[] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
$fieldValidators[] = Injector::inst()->createWithArgs($class, $args);
|
||||
}
|
||||
return $fieldValidators;
|
||||
}
|
||||
}
|
42
src/Validation/IntValidator.php
Normal file
42
src/Validation/IntValidator.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\NumericValidator;
|
||||
|
||||
class IntValidator extends NumericValidator
|
||||
{
|
||||
/**
|
||||
* Minimum size of the number
|
||||
*/
|
||||
private int $minValue;
|
||||
|
||||
/**
|
||||
* Maximum size of the number
|
||||
*/
|
||||
private ?int $maxValue;
|
||||
|
||||
public function __construct(string $name, mixed $value, int $minValue, int $maxValue)
|
||||
{
|
||||
parent::__construct($name, $value);
|
||||
$this->minValue = $minValue;
|
||||
$this->maxValue = $maxValue;
|
||||
}
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
$result = parent::validateValue($result);
|
||||
if (!$result->isValid()) {
|
||||
return $result;
|
||||
}
|
||||
if ($this->value < $this->minValue) {
|
||||
$message = _t(__CLASS__ . '.TOOSMALL', 'Value is too small');
|
||||
$result->addFieldError($this->name, $message);
|
||||
} elseif ($this->value > $this->maxValue) {
|
||||
$message = _t(__CLASS__ . '.TOOLARGE', 'Value is too large');
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
19
src/Validation/IpValidator.php
Normal file
19
src/Validation/IpValidator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
class IpValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
return Constraints\Ip::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid IP address');
|
||||
}
|
||||
}
|
22
src/Validation/LocaleValidator.php
Normal file
22
src/Validation/LocaleValidator.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
/**
|
||||
* Validates that a value is a valid locale, e.g. de, de_DE)
|
||||
*/
|
||||
class LocaleValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
return Constraints\Locale::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid locale');
|
||||
}
|
||||
}
|
19
src/Validation/NumericValidator.php
Normal file
19
src/Validation/NumericValidator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class NumericValidator extends FieldValidator
|
||||
{
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
if (!is_numeric($this->value)) {
|
||||
$message = _t(__CLASS__ . '.NOTNUMERIC', 'Must be a number');
|
||||
$result->addFieldError($this->name, $message);
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
63
src/Validation/StringValidator.php
Normal file
63
src/Validation/StringValidator.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
/**
|
||||
* Validates that a value is a string and optionally checks its multi-byte length.
|
||||
*/
|
||||
class StringValidator extends FieldValidator
|
||||
{
|
||||
/**
|
||||
* The minimum length of the string
|
||||
*/
|
||||
private ?int $minLength;
|
||||
|
||||
/**
|
||||
* The maximum length of the string
|
||||
*/
|
||||
private ?int $maxLength;
|
||||
|
||||
public function __construct(string $name, mixed $value, ?int $minLength = null, ?int $maxLength = null)
|
||||
{
|
||||
parent::__construct($name, $value);
|
||||
$this->minLength = $minLength;
|
||||
$this->maxLength = $maxLength;
|
||||
}
|
||||
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
// Allow blank values
|
||||
if (!$this->value) {
|
||||
return $result;
|
||||
}
|
||||
if (!is_string($this->value)) {
|
||||
$message = _t(
|
||||
__CLASS__ . '.INVALID',
|
||||
'{name} must be a string',
|
||||
['name' => $this->name]
|
||||
);
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
$len = mb_strlen($this->value);
|
||||
if (!is_null($this->minLength) && $len < $this->minLength) {
|
||||
$message = _t(
|
||||
__CLASS__ . '.TOOSHORT',
|
||||
'{name} must have at least {minLength} characters',
|
||||
['name' => $this->name, 'minLength' => $this->minLength]
|
||||
);
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
if (!is_null($this->maxLength) && $len > $this->maxLength) {
|
||||
$message = _t(
|
||||
__CLASS__ . '.TOOLONG',
|
||||
'{name} cannot have more than {maxLength} characters',
|
||||
['name' => $this->name, 'maxLength' => $this->maxLength]
|
||||
);
|
||||
$result->addFieldError($this->name, $message);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
25
src/Validation/TimeValidator.php
Normal file
25
src/Validation/TimeValidator.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
/**
|
||||
* Validates that a value is a valid date, which means that
|
||||
* - it follows the PHP date format H:i:s
|
||||
* - it follows the ISO format 'HH:mm:ss' i.e. DBTime::ISO_TIME
|
||||
*/
|
||||
class TimeValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
// TODO: does this actually work? - symfony code lacks H:i:s format
|
||||
return Constraints\Time::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid time');
|
||||
}
|
||||
}
|
19
src/Validation/UrlValidator.php
Normal file
19
src/Validation/UrlValidator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
use SilverStripe\Validation\AbstractSymfonyValidator;
|
||||
|
||||
class UrlValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
return Constraints\Url::class;
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid URL');
|
||||
}
|
||||
}
|
18
src/Validation/legacy_DateTimeValidator.php
Normal file
18
src/Validation/legacy_DateTimeValidator.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Validation\legacy_DateValidator;
|
||||
|
||||
class legacy_DateTimeValidator extends legacy_DateValidator
|
||||
{
|
||||
protected function getFormat()
|
||||
{
|
||||
return 'Y-m-d H:i:s';
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid date/time');
|
||||
}
|
||||
}
|
44
src/Validation/legacy_DateValidator.php
Normal file
44
src/Validation/legacy_DateValidator.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class legacy_DateValidator extends FieldValidator
|
||||
{
|
||||
protected function validateValue(ValidationResult $result): ValidationResult
|
||||
{
|
||||
// Allow blank values
|
||||
if (!$this->value) {
|
||||
return $result;
|
||||
}
|
||||
// Attempt to parse the date. If this fails, the date is invalid.
|
||||
$date = date_create_from_format($this->getFormat(), $this->value);
|
||||
if ($date === false) {
|
||||
$result->addFieldError($this->name, $this->getMessage());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PHP date format to use for date parsing
|
||||
*/
|
||||
protected function getFormat()
|
||||
{
|
||||
// This validator uses PHP date format codes
|
||||
// The IntlDateFormatter used by DBDate uses ISO date format codes instead of PHP date format codes
|
||||
// For ISO format codes see
|
||||
// http://framework.zend.com/manual/1.12/en/zend.date.constants.html#zend.date.constants.selfdefinedformats
|
||||
// For PHP format codes see https://www.php.net/manual/en/datetime.format.php
|
||||
return 'Y-m-d';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message to use when validation fails
|
||||
*/
|
||||
protected function getMessage()
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid date');
|
||||
}
|
||||
}
|
18
src/Validation/legacy_TimeValidator.php
Normal file
18
src/Validation/legacy_TimeValidator.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Validation\legacy_DateValidator;
|
||||
|
||||
class legacy_TimeValidator extends legacy_DateValidator
|
||||
{
|
||||
protected function getFormat()
|
||||
{
|
||||
return 'H:i:s';
|
||||
}
|
||||
|
||||
protected function getMessage()
|
||||
{
|
||||
return _t(__CLASS__ . '.INVALID', 'Invalid time');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user