mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge 157772f866
into 7f11bf3587
This commit is contained in:
commit
8299d329d0
@ -20,6 +20,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:
|
||||
|
@ -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",
|
||||
|
@ -35,9 +35,9 @@ class ConstraintValidator
|
||||
/** @var ConstraintViolationInterface $violation */
|
||||
foreach ($violations as $violation) {
|
||||
if ($fieldName) {
|
||||
$result->addFieldError($fieldName, $violation->getMessage());
|
||||
$result->addFieldError($fieldName, $violation->getMessage(), value: $value);
|
||||
} else {
|
||||
$result->addError($violation->getMessage());
|
||||
$result->addError($violation->getMessage(), value: $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,11 @@ class ValidationResult
|
||||
*/
|
||||
const CAST_TEXT = 'text';
|
||||
|
||||
/**
|
||||
* Default value of $value parameter
|
||||
*/
|
||||
private const VALUE_UNSET = '_VALUE_UNSET_';
|
||||
|
||||
/**
|
||||
* Is the result valid or not.
|
||||
* Note that there can be non-error messages in the list.
|
||||
@ -71,11 +76,17 @@ class ValidationResult
|
||||
* This can be usedful for ensuring no duplicate messages
|
||||
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||
* Bool values will be treated as plain text flag.
|
||||
* @param mixed $value The value that failed validation
|
||||
* @return $this
|
||||
*/
|
||||
public function addError($message, $messageType = ValidationResult::TYPE_ERROR, $code = null, $cast = ValidationResult::CAST_TEXT)
|
||||
{
|
||||
return $this->addFieldError(null, $message, $messageType, $code, $cast);
|
||||
public function addError(
|
||||
$message,
|
||||
$messageType = ValidationResult::TYPE_ERROR,
|
||||
$code = null,
|
||||
$cast = ValidationResult::CAST_TEXT,
|
||||
$value = ValidationResult::VALUE_UNSET,
|
||||
) {
|
||||
return $this->addFieldError(null, $message, $messageType, $code, $cast, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,6 +100,7 @@ class ValidationResult
|
||||
* This can be usedful for ensuring no duplicate messages
|
||||
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||
* Bool values will be treated as plain text flag.
|
||||
* @param mixed $value The value that failed validation
|
||||
* @return $this
|
||||
*/
|
||||
public function addFieldError(
|
||||
@ -96,10 +108,11 @@ class ValidationResult
|
||||
$message,
|
||||
$messageType = ValidationResult::TYPE_ERROR,
|
||||
$code = null,
|
||||
$cast = ValidationResult::CAST_TEXT
|
||||
$cast = ValidationResult::CAST_TEXT,
|
||||
$value = ValidationResult::VALUE_UNSET,
|
||||
) {
|
||||
$this->isValid = false;
|
||||
return $this->addFieldMessage($fieldName, $message, $messageType, $code, $cast);
|
||||
return $this->addFieldMessage($fieldName, $message, $messageType, $code, $cast, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,11 +125,17 @@ class ValidationResult
|
||||
* This can be usedful for ensuring no duplicate messages
|
||||
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||
* Bool values will be treated as plain text flag.
|
||||
* @param mixed $value The value that failed validation
|
||||
* @return $this
|
||||
*/
|
||||
public function addMessage($message, $messageType = ValidationResult::TYPE_ERROR, $code = null, $cast = ValidationResult::CAST_TEXT)
|
||||
{
|
||||
return $this->addFieldMessage(null, $message, $messageType, $code, $cast);
|
||||
public function addMessage(
|
||||
$message,
|
||||
$messageType = ValidationResult::TYPE_ERROR,
|
||||
$code = null,
|
||||
$cast = ValidationResult::CAST_TEXT,
|
||||
$value = ValidationResult::VALUE_UNSET,
|
||||
) {
|
||||
return $this->addFieldMessage(null, $message, $messageType, $code, $cast, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,6 +149,7 @@ class ValidationResult
|
||||
* This can be usedful for ensuring no duplicate messages
|
||||
* @param string|bool $cast Cast type; One of the CAST_ constant definitions.
|
||||
* Bool values will be treated as plain text flag.
|
||||
* @param mixed $value The value that failed validation
|
||||
* @return $this
|
||||
*/
|
||||
public function addFieldMessage(
|
||||
@ -137,7 +157,8 @@ class ValidationResult
|
||||
$message,
|
||||
$messageType = ValidationResult::TYPE_ERROR,
|
||||
$code = null,
|
||||
$cast = ValidationResult::CAST_TEXT
|
||||
$cast = ValidationResult::CAST_TEXT,
|
||||
$value = ValidationResult::VALUE_UNSET,
|
||||
) {
|
||||
if ($code && is_numeric($code)) {
|
||||
throw new InvalidArgumentException("Don't use a numeric code '$code'. Use a string.");
|
||||
@ -151,7 +172,9 @@ class ValidationResult
|
||||
'messageType' => $messageType,
|
||||
'messageCast' => $cast,
|
||||
];
|
||||
|
||||
if ($value !== ValidationResult::VALUE_UNSET) {
|
||||
$metadata['value'] = $value;
|
||||
}
|
||||
if ($code) {
|
||||
$this->messages[$code] = $metadata;
|
||||
} else {
|
||||
|
@ -119,10 +119,8 @@ class CompositeField extends FormField
|
||||
* Returns the name (ID) for the element.
|
||||
* If the CompositeField doesn't have a name, but we still want the ID/name to be set.
|
||||
* This code generates the ID from the nested children.
|
||||
*
|
||||
* @return String $name
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
if ($this->name) {
|
||||
return $this->name;
|
||||
|
@ -2,14 +2,17 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use SilverStripe\Core\Validation\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Constraints\Email as EmailConstraint;
|
||||
use SilverStripe\Validation\EmailValidator;
|
||||
|
||||
/**
|
||||
* Text input field with validation for correct email format according to the relevant RFC.
|
||||
*/
|
||||
class EmailField extends TextField
|
||||
{
|
||||
private static array $field_validators = [
|
||||
EmailValidator::class,
|
||||
];
|
||||
|
||||
protected $inputType = 'email';
|
||||
|
||||
public function Type()
|
||||
@ -17,27 +20,6 @@ class EmailField extends TextField
|
||||
return 'email text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates for RFC compliant email addresses.
|
||||
*
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
$this->value = trim($this->value ?? '');
|
||||
|
||||
$message = _t('SilverStripe\\Forms\\EmailField.VALIDATION', 'Please enter an email address');
|
||||
$result = ConstraintValidator::validate(
|
||||
$this->value,
|
||||
new EmailConstraint(message: $message, mode: EmailConstraint::VALIDATION_MODE_STRICT),
|
||||
$this->getName()
|
||||
);
|
||||
$validator->getResult()->combineAnd($result);
|
||||
$isValid = $result->isValid();
|
||||
|
||||
return $this->extendValidationResult($isValid, $validator);
|
||||
}
|
||||
|
||||
public function getSchemaValidation()
|
||||
{
|
||||
$rules = parent::getSchemaValidation();
|
||||
|
@ -106,7 +106,7 @@ class FieldGroup extends CompositeField
|
||||
* In some cases the FieldGroup doesn't have a title, but we still want
|
||||
* the ID / name to be set. This code, generates the ID from the nested children
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
if ($this->name) {
|
||||
return $this->name;
|
||||
|
@ -15,6 +15,7 @@ use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\View\AttributesHTML;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Validation\FieldValidatorsTrait;
|
||||
|
||||
/**
|
||||
* Represents a field in a form.
|
||||
@ -44,6 +45,7 @@ class FormField extends RequestHandler
|
||||
{
|
||||
use AttributesHTML;
|
||||
use FormMessage;
|
||||
use FieldValidatorsTrait;
|
||||
|
||||
/** @see $schemaDataType */
|
||||
const SCHEMA_DATA_TYPE_STRING = 'String';
|
||||
@ -424,12 +426,10 @@ class FormField extends RequestHandler
|
||||
|
||||
/**
|
||||
* Returns the field name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
return $this->name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -443,12 +443,20 @@ class FormField extends RequestHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field value.
|
||||
* Alias of getValue()
|
||||
*
|
||||
* @see FormField::setSubmittedValue()
|
||||
* @return mixed
|
||||
*/
|
||||
public function Value()
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field value.
|
||||
*/
|
||||
public function getValue(): mixed
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
@ -1231,15 +1239,21 @@ 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;
|
||||
$result = $this->validate();
|
||||
if (!$result->isValid()) {
|
||||
$isValid = false;
|
||||
$validator->getResult()->combineAnd($result);
|
||||
}
|
||||
return $this->extendValidationResult($isValid, $validator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,7 @@ class SelectionGroup_Item extends CompositeField
|
||||
return $this;
|
||||
}
|
||||
|
||||
function getValue()
|
||||
function getValue(): mixed
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Forms;
|
||||
|
||||
use SilverStripe\Validation\StringValidator;
|
||||
|
||||
/**
|
||||
* Text input field.
|
||||
*/
|
||||
@ -14,6 +16,10 @@ class TextField extends FormField implements TippableFieldInterface
|
||||
|
||||
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TEXT;
|
||||
|
||||
private static array $field_validators = [
|
||||
StringValidator::class => [null, 'getMaxLength'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Tip|null A tip to render beside the input
|
||||
*/
|
||||
@ -117,31 +123,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;
|
||||
}
|
||||
|
@ -8,12 +8,19 @@ use SilverStripe\ORM\DB;
|
||||
* Represents a signed 8 byte integer field. Do note PHP running as 32-bit might not work with Bigint properly, as it
|
||||
* would convert the value to a float when queried from the database since the value is a 64-bit one.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage model
|
||||
* @see Int
|
||||
* BigInt is always signed i.e. can be negative
|
||||
*/
|
||||
class DBBigInt extends DBInt
|
||||
{
|
||||
/**
|
||||
* The minimum value for a signed 64-bit integer - this is the same as PHP_INT_MIN on 64-bit systems.
|
||||
*/
|
||||
public const MIN_VALUE = -9223372036854775808;
|
||||
|
||||
/**
|
||||
* The maximum value for a signed 64-bit integer - this is the same as PHP_INT_MAX on 64-bit systems.
|
||||
*/
|
||||
public const MAX_VALUE = 9223372036854775807;
|
||||
|
||||
public function requireField(): void
|
||||
{
|
||||
@ -28,4 +35,14 @@ class DBBigInt extends DBInt
|
||||
$values = ['type' => 'bigint', 'parts' => $parts];
|
||||
DB::require_field($this->tableName, $this->name, $values);
|
||||
}
|
||||
|
||||
public function getMinValue(): int
|
||||
{
|
||||
return DBBigInt::MIN_VALUE;
|
||||
}
|
||||
|
||||
public function getMaxValue(): int
|
||||
{
|
||||
return DBBigInt::MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Validation\DateValidator;
|
||||
|
||||
/**
|
||||
* Represents a date field.
|
||||
@ -33,6 +34,7 @@ class DBDate extends DBField
|
||||
{
|
||||
/**
|
||||
* Standard ISO format string for date in CLDR standard format
|
||||
* This is equivalent to php date format "Y-m-d" e.g. 2024-08-31
|
||||
*/
|
||||
public const ISO_DATE = 'y-MM-dd';
|
||||
|
||||
@ -42,13 +44,14 @@ class DBDate extends DBField
|
||||
*/
|
||||
public const ISO_LOCALE = 'en_US';
|
||||
|
||||
private static array $field_validators = [
|
||||
DateValidator::class,
|
||||
];
|
||||
|
||||
public function setValue(mixed $value, null|array|ModelData $record = null, bool $markChanged = true): static
|
||||
{
|
||||
$value = $this->parseDate($value);
|
||||
if ($value === false) {
|
||||
throw new InvalidArgumentException(
|
||||
"Invalid date: '$value'. Use " . DBDate::ISO_DATE . " to prevent this error."
|
||||
);
|
||||
if ($value !== null) {
|
||||
$value = $this->parseDate($value);
|
||||
}
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
@ -58,15 +61,10 @@ class DBDate extends DBField
|
||||
* Parse timestamp or iso8601-ish date into standard iso8601 format
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string|null|false Formatted date, null if empty but valid, or false if invalid
|
||||
* @return mixed Formatted date, or the original value if it couldn't be parsed
|
||||
*/
|
||||
protected function parseDate(mixed $value): string|null|false
|
||||
{
|
||||
// Skip empty values
|
||||
if (empty($value) && !is_numeric($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine value to parse
|
||||
if (is_array($value)) {
|
||||
$source = $value; // parse array
|
||||
@ -74,19 +72,18 @@ class DBDate extends DBField
|
||||
$source = $value; // parse timestamp
|
||||
} else {
|
||||
// Convert US date -> iso, fix y2k, etc
|
||||
$value = $this->fixInputDate($value);
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
$source = strtotime($value ?? ''); // convert string to timestamp
|
||||
$fixedValue = $this->fixInputDate($value);
|
||||
// convert string to timestamp
|
||||
$source = strtotime($fixedValue ?? '');
|
||||
}
|
||||
if ($value === false) {
|
||||
return false;
|
||||
if (!$source) {
|
||||
// Unable to parse date, keep as is so that the validator can catch it later
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Format as iso8601
|
||||
$formatter = $this->getInternalFormatter();
|
||||
return $formatter->format($source);
|
||||
$ret = $formatter->format($source);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -560,20 +557,12 @@ class DBDate extends DBField
|
||||
*/
|
||||
protected function fixInputDate($value)
|
||||
{
|
||||
// split
|
||||
[$year, $month, $day, $time] = $this->explodeDateString($value);
|
||||
|
||||
if ((int)$year === 0 && (int)$month === 0 && (int)$day === 0) {
|
||||
return null;
|
||||
if (!checkdate((int) $month, (int) $day, (int) $year)) {
|
||||
// Keep invalid dates as they are so that the validator can catch them later
|
||||
return $value;
|
||||
}
|
||||
// Validate date
|
||||
if (!checkdate($month ?? 0, $day ?? 0, $year ?? 0)) {
|
||||
throw new InvalidArgumentException(
|
||||
"Invalid date: '$value'. Use " . DBDate::ISO_DATE . " to prevent this error."
|
||||
);
|
||||
}
|
||||
|
||||
// Convert to y-m-d
|
||||
// Convert to Y-m-d
|
||||
return sprintf('%d-%02d-%02d%s', $year, $month, $day, $time);
|
||||
}
|
||||
|
||||
@ -591,11 +580,8 @@ class DBDate extends DBField
|
||||
$value ?? '',
|
||||
$matches
|
||||
)) {
|
||||
throw new InvalidArgumentException(
|
||||
"Invalid date: '$value'. Use " . DBDate::ISO_DATE . " to prevent this error."
|
||||
);
|
||||
return [0, 0, 0, ''];
|
||||
}
|
||||
|
||||
$parts = [
|
||||
$matches['first'],
|
||||
$matches['second'],
|
||||
@ -605,11 +591,6 @@ class DBDate extends DBField
|
||||
if ($parts[0] < 1000 && $parts[2] > 1000) {
|
||||
$parts = array_reverse($parts ?? []);
|
||||
}
|
||||
if ($parts[0] < 1000 && (int)$parts[0] !== 0) {
|
||||
throw new InvalidArgumentException(
|
||||
"Invalid date: '$value'. Use " . DBDate::ISO_DATE . " to prevent this error."
|
||||
);
|
||||
}
|
||||
$parts[] = $matches['time'];
|
||||
return $parts;
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\View\TemplateGlobalProvider;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Validation\DatetimeValidator;
|
||||
use SilverStripe\Validation\DateValidator;
|
||||
|
||||
/**
|
||||
* Represents a date-time field.
|
||||
@ -39,6 +41,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
|
||||
/**
|
||||
* Standard ISO format string for date and time in CLDR standard format,
|
||||
* with a whitespace separating date and time (common database representation, e.g. in MySQL).
|
||||
* This is equivalent to php date format "Y-m-d H:i:s" e.g. 2024-08-31 09:30:00
|
||||
*/
|
||||
public const ISO_DATETIME = 'y-MM-dd HH:mm:ss';
|
||||
|
||||
@ -48,10 +51,16 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider
|
||||
*/
|
||||
public const ISO_DATETIME_NORMALISED = 'y-MM-dd\'T\'HH:mm:ss';
|
||||
|
||||
private static array $field_validators = [
|
||||
DatetimeValidator::class,
|
||||
// disable parent validator
|
||||
DateValidator::class => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Flag idicating if this field is considered immutable
|
||||
* when this is enabled setting the value of this field will return a new field instance
|
||||
* instead updatin the old one
|
||||
* instead updating the old one
|
||||
*/
|
||||
protected bool $immutable = false;
|
||||
|
||||
|
@ -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 = [
|
||||
|
29
src/ORM/FieldType/DBEmail.php
Normal file
29
src/ORM/FieldType/DBEmail.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?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 = [
|
||||
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\Validation\FieldValidatorsTrait;
|
||||
use SilverStripe\Validation\FieldValidationInterface;
|
||||
|
||||
/**
|
||||
* Single field in the database.
|
||||
@ -41,8 +43,9 @@ use SilverStripe\Model\ModelData;
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
abstract class DBField extends ModelData implements DBIndexable
|
||||
abstract class DBField extends ModelData implements DBIndexable, FieldValidationInterface
|
||||
{
|
||||
use FieldValidatorsTrait;
|
||||
|
||||
/**
|
||||
* Raw value of this field
|
||||
@ -99,6 +102,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
|
||||
@ -161,7 +166,7 @@ abstract class DBField extends ModelData implements DBIndexable
|
||||
*
|
||||
* If you try an alter the name a warning will be thrown.
|
||||
*/
|
||||
public function setName(?string $name): static
|
||||
public function setName(string $name): static
|
||||
{
|
||||
if ($this->name && $this->name !== $name) {
|
||||
user_error("DBField::setName() shouldn't be called once a DBField already has a name."
|
||||
|
@ -8,26 +8,56 @@ use SilverStripe\Model\List\ArrayList;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Model\List\SS_List;
|
||||
use SilverStripe\Model\ArrayData;
|
||||
use SilverStripe\Validation\IntValidator;
|
||||
use SilverStripe\Model\ModelData;
|
||||
|
||||
/**
|
||||
* Represents a signed 32 bit integer field.
|
||||
*
|
||||
* Ints are always signed i.e. they can be negative
|
||||
*/
|
||||
class DBInt extends DBField
|
||||
{
|
||||
/**
|
||||
* The minimum value for a signed 32-bit integer.
|
||||
*/
|
||||
public const MIN_VALUE = -2147483648;
|
||||
|
||||
/**
|
||||
* The maximum value for a signed 32-bit integer.
|
||||
*/
|
||||
public const MAX_VALUE = 2147483647;
|
||||
|
||||
private static array $field_validators = [
|
||||
IntValidator::class => ['getMinValue', 'getMaxValue']
|
||||
];
|
||||
|
||||
/**
|
||||
* Raw value of this field
|
||||
*
|
||||
* Set to 0 by default, which is a change of the default value of null for DBField
|
||||
*/
|
||||
protected mixed $value = 0;
|
||||
|
||||
public function __construct(?string $name = null, int $defaultVal = 0)
|
||||
{
|
||||
$this->defaultVal = is_int($defaultVal) ? $defaultVal : 0;
|
||||
|
||||
$this->defaultVal = $defaultVal;
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure int values are always returned.
|
||||
* This is for mis-configured databases that return strings.
|
||||
*/
|
||||
public function getValue(): ?int
|
||||
public function setValue(mixed $value, null|array|ModelData $record = null, bool $markChanged = true): static
|
||||
{
|
||||
return (int) $this->value;
|
||||
if (is_null($value)) {
|
||||
// Convert null to 0
|
||||
// Methods such as DataObject::dbObject() can set this to null e.g. when a value has
|
||||
// not been explicity set on a new record.
|
||||
$value = 0;
|
||||
} elseif (is_string($this->value) && preg_match('/^-?\d+$/', $this->value)) {
|
||||
// Cast int like strings as ints to handle mis-configured databases that return strings
|
||||
$value = (int) $value;
|
||||
}
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,4 +118,20 @@ class DBInt extends DBField
|
||||
|
||||
return (int)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum int value for this field.
|
||||
*/
|
||||
public function getMinValue(): int
|
||||
{
|
||||
return DBInt::MIN_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum int value for this field.
|
||||
*/
|
||||
public function getMaxValue(): int
|
||||
{
|
||||
return DBInt::MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\Validation\TimeValidator;
|
||||
|
||||
/**
|
||||
* Represents a column in the database with the type 'Time'.
|
||||
@ -26,17 +27,17 @@ class DBTime extends DBField
|
||||
{
|
||||
/**
|
||||
* Standard ISO format string for time in CLDR standard format
|
||||
* This is equivalent to php date format "H:i:s" e.g. 09:30:00
|
||||
*/
|
||||
public const ISO_TIME = 'HH:mm:ss';
|
||||
|
||||
private static array $field_validators = [
|
||||
TimeValidator::class,
|
||||
];
|
||||
|
||||
public function setValue(mixed $value, null|array|ModelData $record = null, bool $markChanged = true): static
|
||||
{
|
||||
$value = $this->parseTime($value);
|
||||
if ($value === false) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid date passed. Use ' . $this->getISOFormat() . ' to prevent this error.'
|
||||
);
|
||||
}
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
@ -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,10 @@ use SilverStripe\ORM\DB;
|
||||
*/
|
||||
class DBVarchar extends DBString
|
||||
{
|
||||
private static array $field_validators = [
|
||||
StringValidator::class => [null, 'getSize'],
|
||||
];
|
||||
|
||||
private static array $casting = [
|
||||
'Initial' => 'Text',
|
||||
'URL' => 'Text',
|
||||
|
35
src/Validation/AbstractSymfonyValidator.php
Normal file
35
src/Validation/AbstractSymfonyValidator.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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 = parent::validateValue();
|
||||
if (!$result->isValid()) {
|
||||
return $result;
|
||||
}
|
||||
$constraintClass = $this->getConstraintClass();
|
||||
$constraint = new $constraintClass(message: $this->getMessage());
|
||||
$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;
|
||||
}
|
28
src/Validation/BooleanValidator.php
Normal file
28
src/Validation/BooleanValidator.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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::create();
|
||||
if (!in_array($this->value, self::VALID_VALUES, true)) {
|
||||
$message = _t(__CLASS__ . '.INVALID', 'Invalid value');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
27
src/Validation/CompositeValidator.php
Normal file
27
src/Validation/CompositeValidator.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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::create();
|
||||
foreach ($this->children as $child) {
|
||||
$result->combineAnd($child->validate());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
24
src/Validation/DateValidator.php
Normal file
24
src/Validation/DateValidator.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
|
||||
* - it follows the ISO format y-MM-dd i.e. DBDate::ISO_DATE
|
||||
*/
|
||||
class DateValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
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 = parent::validateValue();
|
||||
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, value: $this->value);
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
27
src/Validation/EnumValidator.php
Normal file
27
src/Validation/EnumValidator.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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::create();
|
||||
if (!in_array($this->value, $this->allowedValues)) {
|
||||
$message = _t(__CLASS__ . '.NOTALLOWED', 'Not an allowed value');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
12
src/Validation/FieldValidationInterface.php
Normal file
12
src/Validation/FieldValidationInterface.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Validation\ValidationInterface;
|
||||
|
||||
interface FieldValidationInterface extends ValidationInterface
|
||||
{
|
||||
public function getName(): string;
|
||||
|
||||
public function getValue(): mixed;
|
||||
}
|
39
src/Validation/FieldValidator.php
Normal file
39
src/Validation/FieldValidator.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\ValidationInterface;
|
||||
|
||||
/**
|
||||
* Abstract class that can be used as a validator for FormFields and DBFields
|
||||
*/
|
||||
abstract class FieldValidator implements ValidationInterface
|
||||
{
|
||||
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();
|
||||
$validationResult = $this->validateValue($result);
|
||||
if (!$validationResult->isValid()) {
|
||||
$result->combineAnd($validationResult);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner validatation method that that is implemented by subclasses
|
||||
*/
|
||||
abstract protected function validateValue(): ValidationResult;
|
||||
}
|
110
src/Validation/FieldValidatorsTrait.php
Normal file
110
src/Validation/FieldValidatorsTrait.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use RuntimeException;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Validation\FieldValidationInterface;
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Forms\FormField;
|
||||
|
||||
trait FieldValidatorsTrait
|
||||
{
|
||||
/**
|
||||
* FieldValidators configuration for the field, which is either a FormField or DBField
|
||||
*
|
||||
* Each item in the array can be one of the following
|
||||
* a) MyFieldValidator::class,
|
||||
* b) MyFieldValidator::class => [null, 'getMyArg'],
|
||||
* c) MyFieldValidator::class => null,
|
||||
*
|
||||
* a) Will create a FieldValidator and pass the name and value of the field as args to the constructor
|
||||
* b) Will create a FieldValidator and pass the name, value, make a pass additional args, calling each
|
||||
* non-null value on the field e.g. it will skip the first arg and call $field->getMyArg() for the second arg
|
||||
* c) Will disable a previously set FieldValidator. This is useful to disable a FieldValidator that was set
|
||||
* on a parent class
|
||||
*
|
||||
* You may only have a single instance of a FieldValidator class per field
|
||||
*/
|
||||
private static array $field_validators = [];
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*/
|
||||
public function validate(): ValidationResult
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
$fieldValidators = $this->getFieldValidators();
|
||||
foreach ($fieldValidators as $fieldValidator) {
|
||||
$validationResult = $fieldValidator->validate();
|
||||
if (!$validationResult->isValid()) {
|
||||
$result->combineAnd($validationResult);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get FieldValidators based on `field_validators` configuration
|
||||
*/
|
||||
private function getFieldValidators(): array
|
||||
{
|
||||
$fieldValidators = [];
|
||||
// Used to disable a validator that was previously set with an int index
|
||||
$disabledClasses = [];
|
||||
$interface = FieldValidationInterface::class;
|
||||
// temporary check, will make FormField implement FieldValidationInterface in a future PR
|
||||
$tmp = FormField::class;
|
||||
if (!is_a($this, $interface) && !is_a($this, $tmp)) {
|
||||
$class = get_class($this);
|
||||
throw new RuntimeException("Class $class does not implement interface $interface");
|
||||
}
|
||||
/** @var FieldValidationInterface|Configurable $this */
|
||||
$name = $this->getName();
|
||||
$value = $this->getValue();
|
||||
// Field name is required for FieldValidators when called ValidationResult::addFieldMessage()
|
||||
if ($name === '') {
|
||||
throw new RuntimeException('Field name is blank');
|
||||
}
|
||||
$config = $this->config()->get('field_validators');
|
||||
foreach ($config as $indexOrClass => $classOrArgCallsOrDisable) {
|
||||
$class = '';
|
||||
$argCalls = [];
|
||||
$disable = false;
|
||||
if (is_int($indexOrClass)) {
|
||||
$class = $classOrArgCallsOrDisable;
|
||||
} else {
|
||||
$class = $indexOrClass;
|
||||
$argCalls = $classOrArgCallsOrDisable;
|
||||
$disable = $classOrArgCallsOrDisable === null;
|
||||
}
|
||||
if ($disable) {
|
||||
$disabledClasses[$class] = true;
|
||||
continue;
|
||||
}
|
||||
if (!is_a($class, FieldValidator::class, true)) {
|
||||
throw new RuntimeException("Class $class is not a FieldValidator");
|
||||
}
|
||||
if (!is_array($argCalls)) {
|
||||
throw new RuntimeException("argCalls for FieldValidator $class is not an array");
|
||||
}
|
||||
$args = [$name, $value];
|
||||
foreach ($argCalls as $i => $argCall) {
|
||||
if (!is_string($argCall) && !is_null($argCall)) {
|
||||
throw new RuntimeException("argCall $i for FieldValidator $class is not a string or null");
|
||||
}
|
||||
if ($argCall) {
|
||||
$args[] = call_user_func([$this, $argCall]);
|
||||
} else {
|
||||
$args[] = null;
|
||||
}
|
||||
}
|
||||
$fieldValidators[$class] = Injector::inst()->createWithArgs($class, $args);
|
||||
}
|
||||
foreach (array_keys($disabledClasses) as $class) {
|
||||
unset($fieldValidators[$class]);
|
||||
}
|
||||
return array_values($fieldValidators);
|
||||
}
|
||||
}
|
45
src/Validation/IntValidator.php
Normal file
45
src/Validation/IntValidator.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?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 = parent::validateValue();
|
||||
if (!$result->isValid()) {
|
||||
return $result;
|
||||
}
|
||||
if (!is_int($this->value)) {
|
||||
$message = _t(__CLASS__ . '.NOTINT', 'Not an integer');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
} elseif ($this->value < $this->minValue) {
|
||||
$message = _t(__CLASS__ . '.TOOSMALL', 'Value is too small');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
} elseif ($this->value > $this->maxValue) {
|
||||
$message = _t(__CLASS__ . '.TOOLARGE', 'Value is too large');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
20
src/Validation/NumericValidator.php
Normal file
20
src/Validation/NumericValidator.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
use SilverStripe\Validation\FieldValidator;
|
||||
|
||||
class NumericValidator extends FieldValidator
|
||||
{
|
||||
protected function validateValue(): ValidationResult
|
||||
{
|
||||
$result = ValidationResult::create();
|
||||
if (!is_numeric($this->value)) {
|
||||
$message = _t(__CLASS__ . '.NOTNUMERIC', 'Must be a number');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
60
src/Validation/StringValidator.php
Normal file
60
src/Validation/StringValidator.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?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::create();
|
||||
// Allow blank values
|
||||
if (!$this->value) {
|
||||
return $result;
|
||||
}
|
||||
if (!is_string($this->value)) {
|
||||
$message = _t(__CLASS__ . '.INVALID', 'Must be a string');
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
}
|
||||
$len = mb_strlen($this->value);
|
||||
if (!is_null($this->minLength) && $len < $this->minLength) {
|
||||
$message = _t(
|
||||
__CLASS__ . '.TOOSHORT',
|
||||
'Must have at least {minLength} characters',
|
||||
['minLength' => $this->minLength]
|
||||
);
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
}
|
||||
if (!is_null($this->maxLength) && $len > $this->maxLength) {
|
||||
$message = _t(
|
||||
__CLASS__ . '.TOOLONG',
|
||||
'Can not have more than {maxLength} characters',
|
||||
['maxLength' => $this->maxLength]
|
||||
);
|
||||
$result->addFieldError($this->name, $message, value: $this->value);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
24
src/Validation/TimeValidator.php
Normal file
24
src/Validation/TimeValidator.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 H:i:s
|
||||
* - it follows the ISO format 'HH:mm:ss' i.e. DBTime::ISO_TIME
|
||||
*/
|
||||
class TimeValidator extends AbstractSymfonyValidator
|
||||
{
|
||||
protected function getConstraintClass(): string
|
||||
{
|
||||
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');
|
||||
}
|
||||
}
|
10
src/Validation/ValidationInterface.php
Normal file
10
src/Validation/ValidationInterface.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Validation;
|
||||
|
||||
use SilverStripe\Core\Validation\ValidationResult;
|
||||
|
||||
interface ValidationInterface
|
||||
{
|
||||
public function validate(): ValidationResult;
|
||||
}
|
Loading…
Reference in New Issue
Block a user