|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace GeminiLabs\SiteReviews\Modules; |
|
4
|
|
|
|
|
5
|
|
|
use BadMethodCallException; |
|
6
|
|
|
use GeminiLabs\SiteReviews\Helper; |
|
7
|
|
|
use GeminiLabs\SiteReviews\Modules\Validator\ValidationRules; |
|
8
|
|
|
use InvalidArgumentException; |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* Much of the code in this class is derived from \Illuminate\Validation\Validator (5.3) |
|
12
|
|
|
*/ |
|
13
|
|
|
class Validator |
|
14
|
|
|
{ |
|
15
|
|
|
use ValidationRules; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* @var array |
|
19
|
|
|
*/ |
|
20
|
|
|
public $errors = []; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* The data under validation. |
|
24
|
|
|
* @var array |
|
25
|
|
|
*/ |
|
26
|
|
|
protected $data = []; |
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* The failed validation rules. |
|
30
|
|
|
* @var array |
|
31
|
|
|
*/ |
|
32
|
|
|
protected $failedRules = []; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* The rules to be applied to the data. |
|
36
|
|
|
* @var array |
|
37
|
|
|
*/ |
|
38
|
|
|
protected $rules = []; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* The size related validation rules. |
|
42
|
|
|
* @var array |
|
43
|
|
|
*/ |
|
44
|
|
|
protected $sizeRules = [ |
|
45
|
|
|
'Between', 'Max', 'Min', |
|
46
|
|
|
]; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* The validation rules that imply the field is required. |
|
50
|
|
|
* @var array |
|
51
|
|
|
*/ |
|
52
|
|
|
protected $implicitRules = [ |
|
53
|
|
|
'Required', |
|
54
|
|
|
]; |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* The numeric related validation rules. |
|
58
|
|
|
* @var array |
|
59
|
|
|
*/ |
|
60
|
|
|
protected $numericRules = [ |
|
61
|
|
|
'Numeric', |
|
62
|
|
|
]; |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* Run the validator's rules against its data. |
|
66
|
|
|
* @param mixed $data |
|
67
|
|
|
* @return array |
|
68
|
|
|
*/ |
|
69
|
|
|
public function validate( $data, array $rules = [] ) |
|
70
|
|
|
{ |
|
71
|
|
|
$this->normalizeData( $data ); |
|
72
|
|
|
$this->setRules( $rules ); |
|
73
|
|
|
foreach( $this->rules as $attribute => $rules ) { |
|
74
|
|
|
foreach( $rules as $rule ) { |
|
75
|
|
|
$this->validateAttribute( $attribute, $rule ); |
|
76
|
|
|
if( $this->shouldStopValidating( $attribute ))break; |
|
77
|
|
|
} |
|
78
|
|
|
} |
|
79
|
|
|
return $this->errors; |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* Validate a given attribute against a rule. |
|
84
|
|
|
* @param string $attribute |
|
85
|
|
|
* @param string $rule |
|
86
|
|
|
* @return void |
|
87
|
|
|
* @throws BadMethodCallException |
|
88
|
|
|
*/ |
|
89
|
|
|
public function validateAttribute( $attribute, $rule ) |
|
90
|
|
|
{ |
|
91
|
|
|
list( $rule, $parameters ) = $this->parseRule( $rule ); |
|
92
|
|
|
if( $rule == '' )return; |
|
93
|
|
|
$value = $this->getValue( $attribute ); |
|
94
|
|
|
$this->validateRequired( $attribute, $value ) || in_array( $rule, $this->implicitRules ); |
|
95
|
|
|
if( !method_exists( $this, $method = 'validate'.$rule )) { |
|
96
|
|
|
throw new BadMethodCallException( "Method [$method] does not exist." ); |
|
97
|
|
|
} |
|
98
|
|
|
if( !$this->$method( $attribute, $value, $parameters )) { |
|
99
|
|
|
$this->addFailure( $attribute, $rule, $parameters ); |
|
100
|
|
|
} |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* Add an error message to the validator's collection of errors. |
|
105
|
|
|
* @param string $attribute |
|
106
|
|
|
* @param string $rule |
|
107
|
|
|
* @return void |
|
108
|
|
|
*/ |
|
109
|
|
|
protected function addError( $attribute, $rule, array $parameters ) |
|
110
|
|
|
{ |
|
111
|
|
|
$message = $this->getMessage( $attribute, $rule, $parameters ); |
|
112
|
|
|
$this->errors[$attribute]['errors'][] = $message; |
|
113
|
|
|
if( !isset( $this->errors[$attribute]['value'] )) { |
|
114
|
|
|
$this->errors[$attribute]['value'] = $this->getValue( $attribute ); |
|
115
|
|
|
} |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
/** |
|
119
|
|
|
* Add a failed rule and error message to the collection. |
|
120
|
|
|
* @param string $attribute |
|
121
|
|
|
* @param string $rule |
|
122
|
|
|
* @return void |
|
123
|
|
|
*/ |
|
124
|
|
|
protected function addFailure( $attribute, $rule, array $parameters ) |
|
125
|
|
|
{ |
|
126
|
|
|
$this->addError( $attribute, $rule, $parameters ); |
|
127
|
|
|
$this->failedRules[$attribute][$rule] = $parameters; |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
/** |
|
131
|
|
|
* Get the data type of the given attribute. |
|
132
|
|
|
* @param string $attribute |
|
133
|
|
|
* @return string |
|
134
|
|
|
*/ |
|
135
|
|
|
protected function getAttributeType( $attribute ) |
|
136
|
|
|
{ |
|
137
|
|
|
return $this->hasRule( $attribute, $this->numericRules ) |
|
138
|
|
|
? 'numeric' |
|
139
|
|
|
: 'string'; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* Get the validation message for an attribute and rule. |
|
144
|
|
|
* @param string $attribute |
|
145
|
|
|
* @param string $rule |
|
146
|
|
|
* @return string|null |
|
147
|
|
|
*/ |
|
148
|
|
|
protected function getMessage( $attribute, $rule, array $parameters ) |
|
149
|
|
|
{ |
|
150
|
|
|
if( in_array( $rule, $this->sizeRules )) { |
|
151
|
|
|
return $this->getSizeMessage( $attribute, $rule, $parameters ); |
|
152
|
|
|
} |
|
153
|
|
|
$lowerRule = glsr( Helper::class )->snakeCase( $rule ); |
|
154
|
|
|
return $this->translator( $lowerRule, $rule, $attribute, $parameters ); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Get a rule and its parameters for a given attribute. |
|
159
|
|
|
* @param string $attribute |
|
160
|
|
|
* @param string|array $rules |
|
161
|
|
|
* @return array|null |
|
162
|
|
|
*/ |
|
163
|
|
|
protected function getRule( $attribute, $rules ) |
|
164
|
|
|
{ |
|
165
|
|
|
if( !array_key_exists( $attribute, $this->rules ))return; |
|
166
|
|
|
$rules = (array) $rules; |
|
167
|
|
|
foreach( $this->rules[$attribute] as $rule ) { |
|
168
|
|
|
list( $rule, $parameters ) = $this->parseRule( $rule ); |
|
169
|
|
|
if( in_array( $rule, $rules )) { |
|
170
|
|
|
return [$rule, $parameters]; |
|
171
|
|
|
} |
|
172
|
|
|
} |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Get the size of an attribute. |
|
177
|
|
|
* @param string $attribute |
|
178
|
|
|
* @param mixed $value |
|
179
|
|
|
* @return mixed |
|
180
|
|
|
*/ |
|
181
|
|
|
protected function getSize( $attribute, $value ) |
|
182
|
|
|
{ |
|
183
|
|
|
$hasNumeric = $this->hasRule( $attribute, $this->numericRules ); |
|
184
|
|
|
if( is_numeric( $value ) && $hasNumeric ) { |
|
185
|
|
|
return $value; |
|
186
|
|
|
} |
|
187
|
|
|
elseif( is_array( $value )) { |
|
188
|
|
|
return count( $value ); |
|
189
|
|
|
} |
|
190
|
|
|
return mb_strlen( $value ); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* Get the proper error message for an attribute and size rule. |
|
195
|
|
|
* @param string $attribute |
|
196
|
|
|
* @param string $rule |
|
197
|
|
|
* @return string|null |
|
198
|
|
|
*/ |
|
199
|
|
|
protected function getSizeMessage( $attribute, $rule, array $parameters ) |
|
200
|
|
|
{ |
|
201
|
|
|
$lowerRule = glsr( Helper::class )->snakeCase( $rule ); |
|
202
|
|
|
$type = $this->getAttributeType( $attribute ); |
|
203
|
|
|
$lowerRule .= '.'.$type; |
|
204
|
|
|
return $this->translator( $lowerRule, $rule, $attribute, $parameters ); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Get the value of a given attribute. |
|
209
|
|
|
* @param string $attribute |
|
210
|
|
|
* @return mixed |
|
211
|
|
|
*/ |
|
212
|
|
|
protected function getValue( $attribute ) |
|
213
|
|
|
{ |
|
214
|
|
|
if( isset( $this->data[$attribute] )) { |
|
215
|
|
|
return $this->data[$attribute]; |
|
216
|
|
|
} |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
/** |
|
220
|
|
|
* Determine if the given attribute has a rule in the given set. |
|
221
|
|
|
* @param string $attribute |
|
222
|
|
|
* @param string|array $rules |
|
223
|
|
|
* @return bool |
|
224
|
|
|
*/ |
|
225
|
|
|
protected function hasRule( $attribute, $rules ) |
|
226
|
|
|
{ |
|
227
|
|
|
return !is_null( $this->getRule( $attribute, $rules )); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
/** |
|
231
|
|
|
* Normalize the provided data to an array. |
|
232
|
|
|
* @param mixed $data |
|
233
|
|
|
* @return $this |
|
234
|
|
|
*/ |
|
235
|
|
|
protected function normalizeData( $data ) |
|
236
|
|
|
{ |
|
237
|
|
|
$this->data = is_object( $data ) |
|
238
|
|
|
? get_object_vars( $data ) |
|
239
|
|
|
: $data; |
|
240
|
|
|
return $this; |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
/** |
|
244
|
|
|
* Parse a parameter list. |
|
245
|
|
|
* @param string $rule |
|
246
|
|
|
* @param string $parameter |
|
247
|
|
|
* @return array |
|
248
|
|
|
*/ |
|
249
|
|
|
protected function parseParameters( $rule, $parameter ) |
|
250
|
|
|
{ |
|
251
|
|
|
if( strtolower( $rule ) == 'regex' ) { |
|
252
|
|
|
return [$parameter]; |
|
253
|
|
|
} |
|
254
|
|
|
return str_getcsv( $parameter ); |
|
255
|
|
|
} |
|
256
|
|
|
|
|
257
|
|
|
/** |
|
258
|
|
|
* Extract the rule name and parameters from a rule. |
|
259
|
|
|
* @param string $rule |
|
260
|
|
|
* @return array |
|
261
|
|
|
*/ |
|
262
|
|
|
protected function parseRule( $rule ) |
|
263
|
|
|
{ |
|
264
|
|
|
$parameters = []; |
|
265
|
|
|
// example: {rule}:{parameters} |
|
|
|
|
|
|
266
|
|
|
if( strpos( $rule, ':' ) !== false ) { |
|
267
|
|
|
list( $rule, $parameter ) = explode( ':', $rule, 2 ); |
|
268
|
|
|
// example: {parameter1,parameter2,...} |
|
|
|
|
|
|
269
|
|
|
$parameters = $this->parseParameters( $rule, $parameter ); |
|
270
|
|
|
} |
|
271
|
|
|
$rule = ucwords( str_replace( ['-', '_'], ' ', trim( $rule ))); |
|
272
|
|
|
$rule = str_replace( ' ', '', $rule ); |
|
273
|
|
|
return [$rule, $parameters]; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
/** |
|
277
|
|
|
* Set the validation rules. |
|
278
|
|
|
* @return $this |
|
279
|
|
|
*/ |
|
280
|
|
|
protected function setRules( array $rules ) |
|
281
|
|
|
{ |
|
282
|
|
|
foreach( $rules as $key => $rule ) { |
|
283
|
|
|
$rules[$key] = is_string( $rule ) |
|
284
|
|
|
? explode( '|', $rule ) |
|
285
|
|
|
: $rule; |
|
286
|
|
|
} |
|
287
|
|
|
$this->rules = $rules; |
|
288
|
|
|
return $this; |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
/** |
|
292
|
|
|
* Check if we should stop further validations on a given attribute. |
|
293
|
|
|
* @param string $attribute |
|
294
|
|
|
* @return bool |
|
295
|
|
|
*/ |
|
296
|
|
|
protected function shouldStopValidating( $attribute ) |
|
297
|
|
|
{ |
|
298
|
|
|
return $this->hasRule( $attribute, $this->implicitRules ) |
|
299
|
|
|
&& isset( $this->failedRules[$attribute] ) |
|
300
|
|
|
&& array_intersect( array_keys( $this->failedRules[$attribute] ), $this->implicitRules ); |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
/** |
|
304
|
|
|
* Returns a translated message for the attribute |
|
305
|
|
|
* @param string $key |
|
306
|
|
|
* @param string $rule |
|
307
|
|
|
* @param string $attribute |
|
308
|
|
|
* @return string|null |
|
309
|
|
|
*/ |
|
310
|
|
|
protected function translator( $key, $rule, $attribute, array $parameters ) |
|
311
|
|
|
{ |
|
312
|
|
|
$strings = [ |
|
313
|
|
|
'accepted' => _x( 'The :attribute must be accepted.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ), |
|
314
|
|
|
'between.numeric' => _x( 'The :attribute must be between :min and :max.', ':attribute, :min, and :max are placeholders and should not be translated.', 'site-reviews' ), |
|
315
|
|
|
'between.string' => _x( 'The :attribute must be between :min and :max characters.', ':attribute, :min, and :max are placeholders and should not be translated.', 'site-reviews' ), |
|
316
|
|
|
'email' => _x( 'The :attribute must be a valid email address.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ), |
|
317
|
|
|
'max.numeric' => _x( 'The :attribute may not be greater than :max.', ':attribute and :max are placeholders and should not be translated.', 'site-reviews' ), |
|
318
|
|
|
'max.string' => _x( 'The :attribute may not be greater than :max characters.', ':attribute and :max are placeholders and should not be translated.', 'site-reviews' ), |
|
319
|
|
|
'min.numeric' => _x( 'The :attribute must be at least :min.', ':attribute and :min are placeholders and should not be translated.', 'site-reviews' ), |
|
320
|
|
|
'min.string' => _x( 'The :attribute must be at least :min characters.', ':attribute and :min are placeholders and should not be translated.', 'site-reviews' ), |
|
321
|
|
|
'regex' => _x( 'The :attribute format is invalid.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ), |
|
322
|
|
|
'required' => _x( 'The :attribute field is required.', ':attribute is a placeholder and should not be translated.', 'site-reviews' ), |
|
323
|
|
|
]; |
|
324
|
|
|
$message = isset( $strings[$key] ) |
|
325
|
|
|
? $strings[$key] |
|
326
|
|
|
: false; |
|
327
|
|
|
if( !$message )return; |
|
328
|
|
|
$message = str_replace( ':attribute', $attribute, $message ); |
|
329
|
|
|
if( method_exists( $this, $replacer = 'replace'.$rule )) { |
|
330
|
|
|
$message = $this->$replacer( $message, $parameters ); |
|
331
|
|
|
} |
|
332
|
|
|
return $message; |
|
333
|
|
|
} |
|
334
|
|
|
} |
|
335
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.