1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Validoo; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Validator Class |
7
|
|
|
* @author Alessandro Manno <[email protected]> |
8
|
|
|
* @author Chiara Ferrazza <[email protected]> |
9
|
|
|
* @copyright (c) 2016, Facile.it |
10
|
|
|
* @license https://github.com/facile-it/validoo/blob/master/LICENSE MIT Licence |
11
|
|
|
* @link https://github.com/facile-it/validoo |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* TODO: Exception handling for rules with parameters |
16
|
|
|
* TODO: unit tests for numeric, float, alpha_numeric, max_length, min_length, exact_length |
17
|
|
|
* TODO: add protection filters for several input vulnerabilities. |
18
|
|
|
*/ |
19
|
|
|
class Validator |
20
|
|
|
{ |
21
|
|
|
|
22
|
|
|
/** @var array */ |
23
|
|
|
private $errors = []; |
24
|
|
|
/** @var array */ |
25
|
|
|
private $namings = []; |
26
|
|
|
/** @var array */ |
27
|
|
|
private $customErrorsWithInputName = []; |
28
|
|
|
/** @var array */ |
29
|
|
|
private $customErrors = []; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Constructor is not allowed because Validoo uses its own |
33
|
|
|
* static method to instantiate the validation |
34
|
|
|
*/ |
35
|
46 |
|
private function __construct($errors, $namings) |
36
|
|
|
{ |
37
|
46 |
|
$this->errors = $errors; |
38
|
46 |
|
$this->namings = $namings; |
39
|
46 |
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @return bool |
43
|
|
|
*/ |
44
|
44 |
|
public function isSuccess() |
45
|
|
|
{ |
46
|
44 |
|
return empty($this->errors); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @param array $errors_array |
51
|
|
|
*/ |
52
|
1 |
|
public function customErrors(array $errors_array) |
53
|
|
|
{ |
54
|
1 |
|
foreach ($errors_array as $key => $value) { |
55
|
|
|
// handle input.rule eg (name.required) |
56
|
1 |
|
if (preg_match("#^(.+?)\.(.+?)$#", $key, $matches)) { |
57
|
|
|
// $this->customErrorsWithInputName[name][required] = error message |
58
|
|
|
$this->customErrorsWithInputName[(string)$matches[1]][(string)$matches[2]] = $value; |
59
|
|
|
} else { |
60
|
1 |
|
$this->customErrors[(string)$key] = $value; |
61
|
|
|
} |
62
|
|
|
} |
63
|
1 |
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @return string |
67
|
|
|
*/ |
68
|
3 |
|
protected function getDefaultLang(): string |
69
|
|
|
{ |
70
|
3 |
|
return "en"; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @param string $lang |
75
|
|
|
* @return null |
76
|
|
|
*/ |
77
|
3 |
|
protected function getErrorFilePath(string $lang) |
|
|
|
|
78
|
|
|
{ |
79
|
3 |
|
return null; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param string|null $lang |
84
|
|
|
* @return array|mixed |
85
|
|
|
*/ |
86
|
3 |
|
protected function getDefaultErrorTexts(string $lang = null) |
87
|
|
|
{ |
88
|
|
|
/* handle default error text file */ |
89
|
3 |
|
$default_error_texts = []; |
90
|
3 |
|
if (file_exists(__DIR__ . "/errors/" . $lang . ".php")) { |
91
|
3 |
|
$default_error_texts = include(__DIR__ . "/errors/" . $lang . ".php"); |
92
|
|
|
} |
93
|
3 |
|
return $default_error_texts; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @param string|null $lang |
98
|
|
|
* @return array|mixed |
99
|
|
|
*/ |
100
|
3 |
|
protected function getCustomErrorTexts(string $lang = null) |
101
|
|
|
{ |
102
|
|
|
/* handle error text file for custom validators */ |
103
|
3 |
|
$custom_error_texts = []; |
104
|
3 |
|
if (file_exists($this->getErrorFilePath($lang))) { |
105
|
|
|
$custom_error_texts = include($this->getErrorFilePath($lang)); |
106
|
|
|
} |
107
|
3 |
|
return $custom_error_texts; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @param string $input_name |
112
|
|
|
* @return mixed|string |
113
|
|
|
*/ |
114
|
3 |
|
protected function handleNaming(string $input_name) |
115
|
|
|
{ |
116
|
3 |
|
if (isset($this->namings[$input_name])) { |
117
|
|
|
$named_input = $this->namings[$input_name]; |
118
|
|
|
} else { |
119
|
3 |
|
$named_input = $input_name; |
120
|
|
|
} |
121
|
3 |
|
return $named_input; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @param array $params |
126
|
|
|
* @return array |
127
|
|
|
*/ |
128
|
3 |
|
protected function handleParameterNaming(array $params) |
129
|
|
|
{ |
130
|
3 |
|
foreach ($params as $key => $param) { |
131
|
1 |
|
if (preg_match("#^:([a-zA-Z0-9_]+)$#", $param, $param_type)) { |
132
|
1 |
|
if (isset($this->namings[(string)$param_type[1]])) { |
133
|
1 |
|
$params[$key] = $this->namings[(string)$param_type[1]]; |
134
|
|
|
} else { |
135
|
1 |
|
$params[$key] = $param_type[1]; |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
} |
139
|
3 |
|
return $params; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @param string|null $lang |
144
|
|
|
* @return array |
145
|
|
|
* @throws ValidooException |
146
|
|
|
*/ |
147
|
3 |
|
public function getErrors(string $lang = null): array |
148
|
|
|
{ |
149
|
3 |
|
if (null === $lang) |
|
|
|
|
150
|
3 |
|
$lang = $this->getDefaultLang(); |
151
|
|
|
|
152
|
3 |
|
$error_results = []; |
153
|
3 |
|
$default_error_texts = $this->getDefaultErrorTexts($lang); |
154
|
3 |
|
$custom_error_texts = $this->getCustomErrorTexts($lang); |
155
|
|
|
|
156
|
3 |
|
foreach ($this->errors as $input_name => $results) { |
157
|
3 |
|
foreach ($results as $rule => $result) { |
158
|
3 |
|
$named_input = $this->handleNaming($input_name); |
159
|
|
|
/** |
160
|
|
|
* if parameters are input name they should be named as well |
161
|
|
|
*/ |
162
|
3 |
|
$result['params'] = $this->handleParameterNaming($result['params']); |
163
|
|
|
// if there is a custom message with input name, apply it |
164
|
3 |
|
if (isset($this->customErrorsWithInputName[(string)$input_name][(string)$rule])) { |
165
|
|
|
$error_message = $this->customErrorsWithInputName[(string)$input_name][(string)$rule]; |
166
|
|
|
} // if there is a custom message for the rule, apply it |
167
|
3 |
|
else if (isset($this->customErrors[(string)$rule])) { |
168
|
1 |
|
$error_message = $this->customErrors[(string)$rule]; |
169
|
|
|
} // if there is a custom validator try to fetch from its error file |
170
|
2 |
|
else if (isset($custom_error_texts[(string)$rule])) { |
171
|
|
|
$error_message = $custom_error_texts[(string)$rule]; |
172
|
|
|
} // if none try to fetch from default error file |
173
|
2 |
|
else if (isset($default_error_texts[(string)$rule])) { |
174
|
2 |
|
$error_message = $default_error_texts[(string)$rule]; |
175
|
|
|
} else { |
176
|
|
|
throw new ValidooException(ValidooException::NO_ERROR_TEXT, $rule); |
177
|
|
|
} |
178
|
|
|
/** |
179
|
|
|
* handle :params(..) |
180
|
|
|
*/ |
181
|
3 |
|
if (preg_match_all("#:params\((.+?)\)#", $error_message, $param_indexes)) |
|
|
|
|
182
|
1 |
|
foreach ($param_indexes[1] as $param_index) { |
183
|
1 |
|
$error_message = str_replace(":params(" . $param_index . ")", $result['params'][$param_index], $error_message); |
184
|
|
|
} |
185
|
3 |
|
$error_results[] = str_replace(":attribute", $named_input, $error_message); |
186
|
|
|
} |
187
|
|
|
} |
188
|
3 |
|
return $error_results; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @param string $input_name |
193
|
|
|
* @param string|null $rule_name |
194
|
|
|
* @return bool |
195
|
|
|
*/ |
196
|
|
|
public function has(string $input_name, string $rule_name = null): bool |
197
|
|
|
{ |
198
|
|
|
if (null !== $rule_name) |
|
|
|
|
199
|
|
|
return isset($this->errors[$input_name][$rule_name]); |
200
|
|
|
return isset($this->errors[$input_name]); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* @return array |
205
|
|
|
*/ |
206
|
|
|
final public function getResults(): array |
207
|
|
|
{ |
208
|
|
|
return $this->errors; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Gets the parameter names of a rule |
213
|
|
|
* @param $rule |
214
|
|
|
* @return mixed |
215
|
|
|
*/ |
216
|
46 |
|
private static function getParams($rule) |
217
|
|
|
{ |
218
|
46 |
|
if (preg_match("#^([a-zA-Z0-9_]+)\((.+?)\)$#", $rule, $matches)) { |
219
|
|
|
return [ |
220
|
6 |
|
'rule' => $matches[1], |
221
|
6 |
|
'params' => explode(",", $matches[2]) |
222
|
|
|
]; |
223
|
|
|
} |
224
|
|
|
return [ |
225
|
40 |
|
'rule' => $rule, |
226
|
|
|
'params' => [] |
227
|
|
|
]; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Handle parameter with input name |
232
|
|
|
* eg: equals(:name) |
233
|
|
|
* @param mixed $params |
234
|
|
|
* @return mixed |
235
|
|
|
*/ |
236
|
46 |
|
private static function getParamValues($params, $inputs) |
237
|
|
|
{ |
238
|
46 |
|
foreach ($params as $key => $param) { |
239
|
6 |
|
if (preg_match("#^:([a-zA-Z0-9_]+)$#", $param, $param_type)) { |
240
|
6 |
|
$params[$key] = @$inputs[(string)$param_type[1]]; |
241
|
|
|
} |
242
|
|
|
} |
243
|
46 |
|
return $params; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @param mixed $inputs |
248
|
|
|
* @param array $rules |
249
|
|
|
* @param array|null $naming |
250
|
|
|
* @return Validator |
251
|
|
|
* @throws ValidooException |
252
|
|
|
*/ |
253
|
46 |
|
public static function validate($inputs, array $rules, array $naming = null): self |
254
|
|
|
{ |
255
|
46 |
|
$errors = null; |
256
|
46 |
|
foreach ($rules as $input => $input_rules) { |
257
|
46 |
|
if (is_array($input_rules)) { |
258
|
46 |
|
foreach ($input_rules as $rule => $closure) { |
259
|
46 |
|
if (!isset($inputs[(string)$input])) |
|
|
|
|
260
|
7 |
|
$input_value = null; |
261
|
|
|
else |
|
|
|
|
262
|
39 |
|
$input_value = $inputs[(string)$input]; |
263
|
|
|
/** |
264
|
|
|
* if the key of the $input_rules is numeric that means |
265
|
|
|
* it's neither an anonymous nor an user function. |
266
|
|
|
*/ |
267
|
46 |
|
if (is_numeric($rule)) { |
268
|
44 |
|
$rule = $closure; |
269
|
|
|
} |
270
|
46 |
|
$rule_and_params = static::getParams($rule); |
|
|
|
|
271
|
46 |
|
$params = $real_params = $rule_and_params['params']; |
272
|
46 |
|
$rule = $rule_and_params['rule']; |
273
|
46 |
|
$params = static::getParamValues($params, $inputs); |
|
|
|
|
274
|
46 |
|
array_unshift($params, $input_value); |
275
|
|
|
/** |
276
|
|
|
* Handle anonymous functions |
277
|
|
|
*/ |
278
|
46 |
|
if (@get_class($closure) == 'Closure') { |
279
|
2 |
|
$refl_func = new \ReflectionFunction($closure); |
280
|
2 |
|
$validation = $refl_func->invokeArgs($params); |
281
|
|
|
} /** |
282
|
|
|
* handle class methods |
283
|
44 |
|
*/ else if (@method_exists(get_called_class(), $rule)) { |
284
|
44 |
|
$refl = new \ReflectionMethod(get_called_class(), $rule); |
285
|
44 |
|
if ($refl->isStatic()) { |
286
|
44 |
|
$refl->setAccessible(true); |
287
|
44 |
|
$validation = $refl->invokeArgs(null, $params); |
288
|
|
|
} else { |
289
|
44 |
|
throw new ValidooException(ValidooException::STATIC_METHOD, $rule); |
290
|
|
|
} |
291
|
|
|
} else { |
|
|
|
|
292
|
|
|
throw new ValidooException(ValidooException::UNKNOWN_RULE, $rule); |
293
|
|
|
} |
294
|
46 |
|
if ($validation == false) { |
295
|
27 |
|
$errors[(string)$input][(string)$rule]['result'] = false; |
296
|
46 |
|
$errors[(string)$input][(string)$rule]['params'] = $real_params; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
} else { |
300
|
46 |
|
throw new ValidooException(ValidooException::ARRAY_EXPECTED, $input); |
301
|
|
|
} |
302
|
|
|
} |
303
|
46 |
|
return new self($errors, $naming); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* @param null $input |
308
|
|
|
* @return bool |
309
|
|
|
*/ |
310
|
9 |
|
protected static function required($input = null): bool |
311
|
|
|
{ |
312
|
9 |
|
return (!is_null($input) && (trim($input) != '')); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* @param $input |
317
|
|
|
* @return bool |
318
|
|
|
*/ |
319
|
|
|
protected static function numeric($input): bool |
320
|
|
|
{ |
321
|
|
|
return is_numeric($input); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* @param $input |
326
|
|
|
* @return bool |
327
|
|
|
*/ |
328
|
4 |
|
protected static function email($input): bool |
329
|
|
|
{ |
330
|
4 |
|
return filter_var($input, FILTER_VALIDATE_EMAIL); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* @param $input |
335
|
|
|
* @return bool |
336
|
|
|
*/ |
337
|
11 |
|
protected static function integer($input): bool |
338
|
|
|
{ |
339
|
11 |
|
return is_int($input) || ($input == (string)(int)$input); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* @param $input |
344
|
|
|
* @return bool |
345
|
|
|
*/ |
346
|
|
|
protected static function float($input): bool |
347
|
|
|
{ |
348
|
|
|
return is_float($input) || ($input == (string)(float)$input); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* @param $input |
353
|
|
|
* @return bool |
354
|
|
|
*/ |
355
|
3 |
|
protected static function alpha($input): bool |
356
|
|
|
{ |
357
|
3 |
|
return (preg_match("#^[a-zA-ZÀ-ÿ]+$#", $input) == 1); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* @param $input |
362
|
|
|
* @return bool |
363
|
|
|
*/ |
364
|
|
|
protected static function alpha_numeric($input): bool |
|
|
|
|
365
|
|
|
{ |
366
|
|
|
return (preg_match("#^[a-zA-ZÀ-ÿ0-9]+$#", $input) == 1); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* @param $input |
371
|
|
|
* @return bool |
372
|
|
|
*/ |
373
|
4 |
|
protected static function ip($input): bool |
374
|
|
|
{ |
375
|
4 |
|
return filter_var($input, FILTER_VALIDATE_IP); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/* |
379
|
|
|
* TODO: need improvements for tel and urn urls. |
380
|
|
|
* check out url.test.php for the test result |
381
|
|
|
* urn syntax: http://www.faqs.org/rfcs/rfc2141.html |
382
|
|
|
* |
383
|
|
|
*/ |
384
|
|
|
/** |
385
|
|
|
* @param $input |
386
|
|
|
* @return bool |
387
|
|
|
*/ |
388
|
9 |
|
protected static function url($input): bool |
389
|
|
|
{ |
390
|
9 |
|
return filter_var($input, FILTER_VALIDATE_URL); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* @param $input |
395
|
|
|
* @param $length |
396
|
|
|
* @return bool |
397
|
|
|
*/ |
398
|
|
|
protected static function max_length($input, $length): bool |
|
|
|
|
399
|
|
|
{ |
400
|
|
|
return (strlen($input) <= $length); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* @param $input |
405
|
|
|
* @param $length |
406
|
|
|
* @return bool |
407
|
|
|
*/ |
408
|
|
|
protected static function min_length($input, $length): bool |
|
|
|
|
409
|
|
|
{ |
410
|
|
|
return (strlen($input) >= $length); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* @param $input |
415
|
|
|
* @param $length |
416
|
|
|
* @return bool |
417
|
|
|
*/ |
418
|
|
|
protected static function exact_length($input, $length): bool |
|
|
|
|
419
|
|
|
{ |
420
|
|
|
return (strlen($input) == $length); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* @param $input |
425
|
|
|
* @param $param |
426
|
|
|
* @return bool |
427
|
|
|
*/ |
428
|
4 |
|
protected static function equals($input, $param): bool |
429
|
|
|
{ |
430
|
4 |
|
return ($input == $param); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* @param $input |
435
|
|
|
* @return bool |
436
|
|
|
*/ |
437
|
|
|
protected static function is_path($input): bool |
|
|
|
|
438
|
|
|
{ |
439
|
|
|
return is_dir($input); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* @param $input |
444
|
|
|
* @return bool |
445
|
|
|
*/ |
446
|
|
|
protected static function is_filename($input): bool |
|
|
|
|
447
|
|
|
{ |
448
|
|
|
return preg_match('/^[A-Za-z0-9-_]+[.]{1}[A-Za-z]+$/', $input); |
449
|
|
|
} |
450
|
|
|
} |
451
|
|
|
|
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.