2
2
3
3
namespace SilverStripe \Core \Validation \FieldValidation ;
4
4
5
+ use Exception ;
6
+ use InvalidArgumentException ;
5
7
use SilverStripe \Core \Validation \FieldValidation \FieldValidator ;
6
8
use SilverStripe \Core \Validation \ValidationResult ;
7
9
8
10
/**
9
11
* Validates that a value is a valid date, which means that it follows the equivalent formats:
10
12
* - PHP date format Y-m-d
11
- * - SO format y-MM-dd i.e. DBDate::ISO_DATE
13
+ * - ISO format y-MM-dd i.e. DBDate::ISO_DATE
12
14
*
13
15
* Blank string values are allowed
14
16
*/
15
17
class DateFieldValidator extends FieldValidator
16
18
{
19
+ /**
20
+ * Minimum value as a date string
21
+ */
22
+ private ?string $ minValue ;
23
+
24
+ /**
25
+ * Minimum value as a unix timestamp
26
+ */
27
+ private ?int $ minTimestamp ;
28
+
29
+ /**
30
+ * Maximum value as a date string
31
+ */
32
+ private ?string $ maxValue ;
33
+
34
+ /**
35
+ * Maximum value as a unix timestamp
36
+ */
37
+ private ?int $ maxTimestamp ;
38
+
39
+ /**
40
+ * Converter to convert date strings to another format for display in error messages
41
+ *
42
+ * @var callable
43
+ */
44
+ private $ converter ;
45
+
46
+ public function __construct (
47
+ string $ name ,
48
+ mixed $ value ,
49
+ ?string $ minValue = null ,
50
+ ?string $ maxValue = null ,
51
+ ?callable $ converter = null ,
52
+ ) {
53
+ // Convert Y-m-d args to timestamps
54
+ // Intermiediate variables are used to prevent "must not be accessed before initialization" PHP errors
55
+ // when reading properties in the constructor
56
+ $ minTimestamp = null ;
57
+ $ maxTimestamp = null ;
58
+ if (!is_null ($ minValue )) {
59
+ $ minTimestamp = $ this ->dateToTimestamp ($ minValue );
60
+ }
61
+ if (!is_null ($ maxValue )) {
62
+ $ maxTimestamp = $ this ->dateToTimestamp ($ maxValue );
63
+ }
64
+ if (!is_null ($ minTimestamp ) && !is_null ($ maxTimestamp ) && $ minTimestamp < $ maxTimestamp ) {
65
+ throw new InvalidArgumentException ('maxValue cannot be less than minValue ' );
66
+ }
67
+ $ this ->minValue = $ minValue ;
68
+ $ this ->maxValue = $ maxValue ;
69
+ $ this ->minTimestamp = $ minTimestamp ;
70
+ $ this ->maxTimestamp = $ maxTimestamp ;
71
+ $ this ->converter = $ converter ;
72
+ parent ::__construct ($ name , $ value );
73
+ }
74
+
17
75
protected function validateValue (): ValidationResult
18
76
{
19
77
$ result = ValidationResult::create ();
20
78
// Allow empty strings
21
79
if ($ this ->value === '' ) {
22
80
return $ result ;
23
81
}
24
- // Not using symfony/validator because it was allowing d-m-Y format strings
25
- $ date = date_parse_from_format ($ this ->getFormat (), $ this ->value ?? '' );
26
- if ($ date === false || $ date ['error_count ' ] > 0 || $ date ['warning_count ' ] > 0 ) {
82
+ // Validate value is a valid date
83
+ try {
84
+ $ timestamp = $ this ->dateToTimestamp ($ this ->value ?? '' );
85
+ } catch (Exception ) {
27
86
$ result ->addFieldError ($ this ->name , $ this ->getMessage ());
87
+ return $ result ;
88
+ }
89
+ // Validate value is within range
90
+ if (!is_null ($ this ->minTimestamp ) && $ timestamp < $ this ->minTimestamp ) {
91
+ $ minValue = $ this ->minValue ;
92
+ if (!is_null ($ this ->converter )) {
93
+ $ minValue = call_user_func ($ this ->converter , $ this ->minValue ) ?: $ this ->minValue ;
94
+ }
95
+ $ message = _t (
96
+ __CLASS__ . '.TOOSMALL ' ,
97
+ 'Value cannot be less than {minValue} ' ,
98
+ ['minValue ' => $ minValue ]
99
+ );
100
+ $ result ->addFieldError ($ this ->name , $ message );
101
+ } elseif (!is_null ($ this ->maxTimestamp ) && $ timestamp > $ this ->maxTimestamp ) {
102
+ $ maxValue = $ this ->maxValue ;
103
+ if (!is_null ($ this ->converter )) {
104
+ $ maxValue = call_user_func ($ this ->converter , $ this ->maxValue ) ?: $ this ->maxValue ;
105
+ }
106
+ $ message = _t (
107
+ __CLASS__ . '.TOOLARGE ' ,
108
+ 'Value cannot be greater than {maxValue} ' ,
109
+ ['maxValue ' => $ maxValue ]
110
+ );
111
+ $ result ->addFieldError ($ this ->name , $ message );
28
112
}
29
113
return $ result ;
30
114
}
@@ -38,4 +122,17 @@ protected function getMessage(): string
38
122
{
39
123
return _t (__CLASS__ . '.INVALID ' , 'Invalid date ' );
40
124
}
125
+
126
+ /**
127
+ * Parse a date string into a unix timestamp using the format specified by getFormat()
128
+ */
129
+ private function dateToTimestamp (string $ date ): int
130
+ {
131
+ // Not using symfony/validator because it was allowing d-m-Y format strings
132
+ $ date = date_parse_from_format ($ this ->getFormat (), $ date );
133
+ if ($ date === false || $ date ['error_count ' ] > 0 || $ date ['warning_count ' ] > 0 ) {
134
+ throw new InvalidArgumentException ('Invalid date ' );
135
+ }
136
+ return mktime ($ date ['hour ' ], $ date ['minute ' ], $ date ['second ' ], $ date ['month ' ], $ date ['day ' ], $ date ['year ' ]);
137
+ }
41
138
}
0 commit comments