Completed
Pull Request — master (#3325)
by Emanuele
11:19
created

Data_Validator   F

Complexity

Total Complexity 180

Size/Duplication

Total Lines 1245
Duplicated Lines 17.75 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 86.53%

Importance

Changes 0
Metric Value
dl 221
loc 1245
rs 0.8
c 0
b 0
f 0
ccs 437
cts 505
cp 0.8653
wmc 180
lcom 1
cbo 1

39 Methods

Rating   Name   Duplication   Size   Complexity  
A _validate_valid_ipv6() 0 12 3
A _validate_length() 0 13 3
A __isset() 0 3 1
A _validate_without() 0 18 4
A validation_errors() 0 8 2
A _validate_limits() 0 25 6
A _validate_notequal() 0 14 3
B _validate_php_syntax() 0 50 10
A _validate_isarray() 0 12 3
A _sanitation_gmail_normalize() 0 25 3
A _validate_valid_ip() 0 12 3
A __get() 0 3 2
A _validate_valid_url() 0 12 3
A _validate_required() 0 10 4
A input_processing() 0 6 2
B _validate_valid_color() 0 34 8
A _validate_alpha_numeric() 0 13 3
A _validate_valid_email() 0 19 4
A _validate_max_length() 0 13 3
A _validate_alpha_dash() 0 12 3
A _sanitation_cleanhtml() 0 6 2
A _validate_alpha() 0 13 3
A _validate_numeric() 0 12 3
A validate() 0 15 3
B _validate_recursive() 0 52 8
C _get_error_messages() 0 40 13
A is_valid() 0 28 3
B _validate_integer() 0 19 7
A _validate_min_length() 0 13 3
A _validate_float() 0 12 3
A _validate_contains() 0 14 3
A validation_data() 0 6 3
C _validate() 0 56 13
A sanitation_rules() 0 13 3
A validation_rules() 0 11 3
A text_replacements() 0 6 2
A _sanitize_recursive() 0 43 5
B _validate_boolean() 0 28 11
C _sanitize() 0 74 16

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Data_Validator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Data_Validator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Used to validate and transform user supplied data from forms etc
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * @version 1.1.5
11
 *
12
 */
13
14
/**
15
 * Class used to validate and transform data
16
 *
17
 * Initiate
18
 *    $validation = new Data_Validator();
19
 *
20
 * Set validation rules
21
 *    $validation->validation_rules(array(
22
 *      'username' => 'required|alpha_numeric|max_length[10]|min_length[6]',
23
 *      'email'    => 'required|valid_email'
24
 *    ));
25
 *
26
 * Set optional sanitation rules
27
 *    $validation->sanitation_rules(array(
28
 *      'username' => 'trim|strtoupper',
29
 *      'email'    => 'trim|gmail_normalize'
30
 *    ));
31
 *
32
 * Set optional variable name substitutions
33
 *    $validation->text_replacements(array(
34
 *      'username' => $txt['someThing'],
35
 *      'email'    => $txt['someEmail']
36
 *    ));
37
 *
38
 * Set optional special processing tags
39
 *    $validation->input_processing(array(
40
 *      'somefield'    => 'csv',
41
 *      'anotherfield' => 'array'
42
 *    ));
43
 *
44
 * Run the validation
45
 *    $validation->validate($data);
46
 * $data must be an array with keys matching the validation rule e.g. $data['username'], $data['email']
47
 *
48
 * Get the results
49
 *    $validation->validation_errors(optional array of fields to return errors on)
50
 *    $validation->validation_data()
51
 *    $validation->username
52
 *
53
 * Use it inline with the static method
54
 * $_POST['username'] = ' username '
55
 * if (Data_Validator::is_valid($_POST, array('username' => 'required|alpha_numeric'), array('username' => 'trim|strtoupper')))
56
 *    $username = $_POST['username'] // now = 'USERNAME'
57
 *
58
 * Current validation can be one or a combination of:
59
 *    max_length[x], min_length[x], length[x],
60
 *    alpha, alpha_numeric, alpha_dash
61
 *    numeric, integer, boolean, float, notequal[x,y,z], isarray, limits[min,max]
62
 *    valid_url, valid_ip, valid_ipv6, valid_email, valid_color
63
 *    php_syntax, contains[x,y,x], required, without[x,y,z]
64
 */
65
class Data_Validator
66
{
67
	/**
68
	 * Validation rules
69
	 * @var mixed[]
70
	 */
71
	protected $_validation_rules = array();
72
73
	/**
74
	 * Sanitation rules
75
	 * @var mixed[]
76
	 */
77
	protected $_sanitation_rules = array();
78
79
	/**
80
	 * Text substitutions for field names in the error messages
81
	 * @var mixed[]
82
	 */
83
	protected $_replacements = array();
84
85
	/**
86
	 * Holds validation errors
87
	 * @var mixed[]
88
	 */
89
	protected $_validation_errors = array();
90
91
	/**
92
	 * Holds our data
93
	 * @var mixed[]
94
	 */
95
	protected $_data = array();
96
97
	/**
98
	 * Strict data processing,
99
	 * if true drops data for which no sanitation rule was set
100
	 * @var boolean
101
	 */
102
	protected $_strict = false;
103
104
	/**
105
	 * Holds any special processing that is required for certain fields
106
	 * csv or array
107
	 * @var string[]
108
	 */
109
	protected $_datatype = array();
110
111
	/**
112
	 * Allow reading otherwise inaccessible data values
113
	 *
114
	 * @param string $property key name of array value to return
115
	 */
116 5
	public function __get($property)
117
	{
118 5
		return array_key_exists($property, $this->_data) ? $this->_data[$property] : null;
119
	}
120
121
	/**
122
	 * Allow testing data values for empty/isset
123
	 *
124
	 * @param string $property key name of array value to return
125
	 */
126
	public function __isset($property)
127
	{
128
		return isset($this->_data[$property]);
129
	}
130
131
	/**
132
	 * Shorthand static method for simple inline validation
133
	 *
134
	 * @param mixed[]|object $data generally $_POST data for this method
135
	 * @param mixed[] $validation_rules associative array of field => rules
136
	 * @param mixed[] $sanitation_rules associative array of field => rules
137
	 */
138 68
	public static function is_valid(&$data = array(), $validation_rules = array(), $sanitation_rules = array())
139
	{
140 68
		$validator = new Data_Validator();
141
142
		// Set the rules
143 68
		$validator->sanitation_rules($sanitation_rules);
144 68
		$validator->validation_rules($validation_rules);
145
146
		// Run the test
147 68
		$result = $validator->validate($data);
148
149
		// Replace the data
150 68
		if (!empty($sanitation_rules))
151 68
		{
152
			// Handle cases where we have an object
153 1
			if (is_object($data))
154 1
			{
155
				$data = array_replace((array) $data, $validator->validation_data());
156
				$data = (object) $data;
157
			}
158
			else
159
			{
160 1
				$data = array_replace($data, $validator->validation_data());
161
			}
162 1
		}
163
164
		// Return true or false on valid data
165 68
		return $result;
166
	}
167
168
	/**
169
	 * Set the validation rules that will be run against the data
170
	 *
171
	 * @param mixed[] $rules associative array of field => rule|rule|rule
172
	 */
173 74
	public function validation_rules($rules = array())
174
	{
175
		// If its not an array, make it one
176 74
		if (!is_array($rules))
0 ignored issues
show
introduced by
The condition is_array($rules) is always true.
Loading history...
177 74
			$rules = array($rules);
178
179
		// Set the validation rules
180 74
		if (!empty($rules))
181 74
			$this->_validation_rules = $rules;
182
		else
183 2
			return $this->_validation_rules;
184 72
	}
185
186
	/**
187
	 * Sets the sanitation rules used to clean data
188
	 *
189
	 * @param mixed[] $rules associative array of field => rule|rule|rule
190
	 * @param boolean $strict
191
	 */
192 76
	public function sanitation_rules($rules = array(), $strict = false)
193
	{
194
		// If its not an array, make it one
195 76
		if (!is_array($rules))
0 ignored issues
show
introduced by
The condition is_array($rules) is always true.
Loading history...
196 76
			$rules = array($rules);
197
198
		// Set the sanitation rules
199 76
		$this->_strict = $strict;
200
201 76
		if (!empty($rules))
202 76
			$this->_sanitation_rules = $rules;
203
		else
204 67
			return $this->_sanitation_rules;
205 9
	}
206
207
	/**
208
	 * Field Name Replacements
209
	 * @param mixed[] $replacements associative array of field => txt string key
210
	 */
211
	public function text_replacements($replacements = array())
212
	{
213
		if (!empty($replacements))
214
			$this->_replacements = $replacements;
215
		else
216
			return $this->_replacements;
217
	}
218
219
	/**
220
	 * Set special processing conditions for fields, such as (and only)
221
	 * csv or array
222
	 *
223
	 * @param string[] $datatype csv or array processing for the field
224
	 */
225 2
	public function input_processing($datatype = array())
226
	{
227 2
		if (!empty($datatype))
228 2
			$this->_datatype = $datatype;
229
		else
230
			return $this->_datatype;
231 2
	}
232
233
	/**
234
	 * Run the sanitation and validation on the data
235
	 *
236
	 * @param mixed[]|object $input associative array or object of data to process name => value
237
	 */
238 77
	public function validate($input)
239
	{
240
		// If its an object, convert it to an array
241 77
		if (is_object($input))
242 77
			$input = (array) $input;
243
244
		// @todo this won't work, $input[$field] will be undefined
245 77
		if (!is_array($input))
0 ignored issues
show
introduced by
The condition is_array($input) is always true.
Loading history...
246 77
			$input[$input] = array($input);
247
248
		// Clean em
249 77
		$this->_data = $this->_sanitize($input, $this->_sanitation_rules);
250
251
		// Check em
252 77
		return $this->_validate($this->_data, $this->_validation_rules);
253
	}
254
255
	/**
256
	 * Return any errors found, either in the raw or nicely formatted
257
	 *
258
	 * @param mixed[]|string|boolean $raw
259
	 *    - true returns the raw error array,
260
	 *    - array returns just error messages of those fields
261
	 *    - string returns just that error message
262
	 *    - default is all error message(s)
263
	 */
264 2
	public function validation_errors($raw = false)
265
	{
266
		// Return the array
267 2
		if ($raw === true)
268 2
			return $this->_validation_errors;
269
		// Otherwise return the formatted text string(s)
270
		else
271 1
			return $this->_get_error_messages($raw);
0 ignored issues
show
Bug introduced by
It seems like $raw can also be of type string; however, parameter $keys of Data_Validator::_get_error_messages() does only seem to accept array<mixed,mixed>|boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

271
			return $this->_get_error_messages(/** @scrutinizer ignore-type */ $raw);
Loading history...
272
	}
273
274
	/**
275
	 * Return the validation data, all or a specific key
276
	 * @param integer|string|null $key int or string
277
	 */
278 4
	public function validation_data($key = null)
279
	{
280 4
		if ($key === null)
281 4
			return $this->_data;
282
283 2
		return isset($this->_data[$key]) ? $this->_data[$key] : null;
284
	}
285
286
	/**
287
	 * Performs data validation against the provided rule
288
	 *
289
	 * @param mixed[] $input
290
	 * @param mixed[] $ruleset
291
	 */
292 77
	private function _validate($input, $ruleset)
293
	{
294
		// No errors ... yet ;)
295 77
		$this->_validation_errors = array();
296
297
		// For each field, run our rules against the data
298 77
		foreach ($ruleset as $field => $rules)
299
		{
300
			// Special processing required on this field like csv or array?
301 72
			if (isset($this->_datatype[$field]) && in_array($this->_datatype[$field], array('csv', 'array')))
302 72
				$this->_validate_recursive($input, $field, $rules);
303
			else
304
			{
305
				// Get rules for this field
306 72
				$rules = explode('|', $rules);
307 72
				foreach ($rules as $rule)
308
				{
309 72
					$validation_parameters = null;
310 72
					$validation_parameters_function = array();
311
312
					// Were any parameters provided for the rule, e.g. min_length[6]
313 72
					if (preg_match('~(.*)\[(.*)\]~', $rule, $match))
314 72
					{
315 5
						$validation_method = '_validate_' . $match[1];
316 5
						$validation_parameters = $match[2];
317 5
						$validation_function = $match[1];
318 5
						$validation_parameters_function = explode(',', $match[2]);
319 5
					}
320
					// Or just a predefined rule e.g. valid_email
321
					else
322
					{
323 71
						$validation_method = '_validate_' . $rule;
324 71
						$validation_function = $rule;
325
					}
326
327
					// Defined method to use?
328 72
					if (is_callable(array($this, $validation_method)))
329 72
						$result = $this->{$validation_method}($field, $input, $validation_parameters);
330
					// Maybe even a custom function set up like a defined one, addons can do this.
331
					elseif (is_callable($validation_function) && strpos($validation_function, 'validate_') === 0 && isset($input[$field]))
332
						$result = call_user_func_array($validation_function, array_merge((array) $field, (array) $input[$field], $validation_parameters_function));
333
					else
334
						$result = array(
335
							'field' => $validation_method,
336
							'input' => isset($input[$field]) ? $input[$field] : null,
337
							'function' => '_validate_invalid_function',
338
							'param' => $validation_parameters
339
						);
340
341 72
					if (is_array($result))
342 72
						$this->_validation_errors[] = $result;
343 72
				}
344
			}
345 77
		}
346
347 77
		return count($this->_validation_errors) === 0 ? true : false;
348
	}
349
350
	/**
351
	 * Used when a field contains csv or array of data
352
	 *
353
	 * -Will convert field to individual elements and run a separate validation on that group
354
	 * using the rules defined to the parent node
355
	 *
356
	 * @param mixed[] $input
357
	 * @param string $field
358
	 * @param string $rules
359
	 */
360 2
	private function _validate_recursive($input, $field, $rules)
361
	{
362 2
		if (!isset($input[$field]))
363 2
			return;
364
365
		// Start a new instance of the validator to work on this sub data (csv/array)
366 2
		$sub_validator = new Data_Validator();
367
368 2
		$fields = array();
369 2
		$validation_rules = array();
370
371 2
		if ($this->_datatype[$field] === 'array')
372 2
		{
373
			// Convert the array to individual values, they all use the same rules
374 1
			foreach ($input[$field] as $key => $value)
375
			{
376 1
				$validation_rules[$key] = $rules;
377 1
				$fields[$key] = $value;
378 1
			}
379 1
		}
380
		// CSV is much the same process as array
381 2
		elseif ($this->_datatype[$field] === 'csv')
382
		{
383
			// Blow it up!
384 2
			$temp = explode(',', $input[$field]);
385 2
			foreach ($temp as $key => $value)
386
			{
387 2
				$validation_rules[$key] = $rules;
388 2
				$fields[$key] = $value;
389 2
			}
390 2
		}
391
392
		// Validate each "new" field
393 2
		$sub_validator->validation_rules($validation_rules);
394 2
		$result = $sub_validator->validate($fields);
395
396
		// If its not valid, then just take the first error and use it for the original field
397 2
		if (!$result)
398 2
		{
399 2
			$errors = $sub_validator->validation_errors(true);
400 2
			foreach ($errors as $error)
401
			{
402 2
				$this->_validation_errors[] = array(
403 2
					'field' => $field,
404 2
					'input' => $error['input'],
405 2
					'function' => $error['function'],
406 2
					'param' => $error['param'],
407
				);
408 2
			}
409 2
		}
410
411 2
		return $result;
412
	}
413
414
	/**
415
	 * Data sanitation is a good thing
416
	 *
417
	 * @param mixed[] $input
418
	 * @param mixed[] $ruleset
419
	 * @return mixed
420
	 */
421 77
	private function _sanitize($input, $ruleset)
422
	{
423
		// For each field, run our set of rules against the data
424 77
		foreach ($ruleset as $field => $rules)
425
		{
426
			// Data for which we don't have rules
427 9
			if (!array_key_exists($field, $input))
428 9
			{
429
				if ($this->_strict)
430
					unset($input[$field]);
431
432
				continue;
433
			}
434
435
			// Is this a special processing field like csv or array?
436 9
			if (isset($this->_datatype[$field]) && in_array($this->_datatype[$field], array('csv', 'array')))
437 9
				$input[$field] = $this->_sanitize_recursive($input, $field, $rules);
438
			else
439
			{
440
				// Rules for which we do have data
441 9
				$rules = explode('|', $rules);
442 9
				foreach ($rules as $rule)
443
				{
444 9
					$sanitation_parameters = null;
445 9
					$sanitation_parameters_function = array();
446
447
					// Were any parameters provided for the rule, e.g. Util::htmlspecialchars[ENT_QUOTES]
448 9
					if (preg_match('~(.*)\[(.*)\]~', $rule, $match))
449 9
					{
450 2
						$sanitation_method = '_sanitation_' . $match[1];
451 2
						$sanitation_parameters = $match[2];
452 2
						$sanitation_function = $match[1];
453 2
						$sanitation_parameters_function = explode(',', defined($match[2]) ? constant($match[2]) : $match[2]);
454 2
					}
455
					// Or just a predefined rule e.g. trim
456
					else
457
					{
458 7
						$sanitation_method = '_sanitation_' . $rule;
459 7
						$sanitation_function = $rule;
460
					}
461
462
					// Defined method to use?
463 9
					if (is_callable(array($this, $sanitation_method)))
464 9
						$input[$field] = $this->{$sanitation_method}($input[$field], $sanitation_parameters);
465
					// One of our static methods or even a built in php function like strtoupper, intval, etc?
466 9
					elseif (is_callable($sanitation_function))
467 9
						$input[$field] = call_user_func_array($sanitation_function, array_merge((array) $input[$field], $sanitation_parameters_function));
468
					// Or even a language construct?
469
					elseif (in_array($sanitation_function, array('empty', 'array', 'isset')))
470
					{
471
						// could be done as methods instead ...
472
						switch ($sanitation_function)
473
						{
474
							case 'empty':
475
								$input[$field] = empty($input[$field]);
476
								break;
477
							case 'array':
478
								$input[$field] = is_array($input[$field]) ? $input[$field] : array($input[$field]);
479
								break;
480
							case 'isset':
481
								$input[$field] = isset($input[$field]);
482
								break;
483
						}
484
					}
485
					else
486
					{
487
						// @todo fatal_error or other ? being asked to do something we don't know?
488
						// results in returning $input[$field] = $input[$field];
489
					}
490 9
				}
491
			}
492 77
		}
493
494 77
		return $input;
495
	}
496
497
	/**
498
	 * When the input field is an array or csv, this will build a new validator
499
	 * as if the fields were individual ones, each checked against the base rule
500
	 *
501
	 * @param mixed[] $input
502
	 * @param string $field
503
	 * @param string $rules
504
	 */
505 1
	private function _sanitize_recursive($input, $field, $rules)
506
	{
507
		// create a new instance to run against this sub data
508 1
		$validator = new Data_Validator();
509
510 1
		$fields = array();
511 1
		$sanitation_rules = array();
512
513 1
		if ($this->_datatype[$field] === 'array')
514 1
		{
515
			// Convert the array to individual values, they all use the same rules
516
			foreach ($input[$field] as $key => $value)
517
			{
518
				$sanitation_rules[$key] = $rules;
519
				$fields[$key] = $value;
520
			}
521
522
			// Sanitize each "new" field
523
			$validator->sanitation_rules($sanitation_rules);
524
			$validator->validate($fields);
525
526
			// Take the individual results and replace them in the original array
527
			$input[$field] = array_replace($input[$field], $validator->validation_data());
528
		}
529 1
		elseif ($this->_datatype[$field] === 'csv')
530
		{
531
			// Break up the CSV data so we have an array
532 1
			$temp = explode(',', $input[$field]);
533 1
			foreach ($temp as $key => $value)
534
			{
535 1
				$sanitation_rules[$key] = $rules;
536 1
				$fields[$key] = $value;
537 1
			}
538
539
			// Sanitize each "new" field
540 1
			$validator->sanitation_rules($sanitation_rules);
541 1
			$validator->validate($fields);
542
543
			// Put it back together with clean data
544 1
			$input[$field] = implode(',', $validator->validation_data());
545 1
		}
546
547 1
		return $input[$field];
548
	}
549
550
	/**
551
	 * Process any errors and return the error strings
552
	 *
553
	 * @param mixed[]|boolean $keys
554
	 */
555 1
	private function _get_error_messages($keys)
556
	{
557 1
		global $txt;
558
559 1
		if (empty($this->_validation_errors))
560 1
			return false;
561
562 1
		loadLanguage('Validation');
563 1
		$result = array();
564
565
		// Just want specific errors then it must be an array
566 1
		if (!empty($keys) && !is_array($keys))
567 1
			$keys = array($keys);
568
569 1
		foreach ($this->_validation_errors as $error)
570
		{
571
			// Field name substitution supplied?
572 1
			$field = isset($this->_replacements[$error['field']]) ? $this->_replacements[$error['field']] : $error['field'];
573
574
			// Just want specific field errors returned?
575 1
			if (!empty($keys) && is_array($keys) && !in_array($error['field'], $keys))
576 1
				continue;
577
578
			// Set the error message for this validation failure
579 1
			if (isset($error['error']))
580 1
				$result[] = sprintf($txt[$error['error']], $field, $error['error_msg']);
581
			// Use our error text based on the function name itself
582 1
			elseif (isset($txt[$error['function']]))
583
			{
584 1
				if (!empty($error['param']))
585 1
					$result[] = sprintf($txt[$error['function']], $field, $error['param']);
586
				else
587 1
					$result[] = sprintf($txt[$error['function']], $field, $error['input']);
588 1
			}
589
			// can't find the function text, so set a generic one
590
			else
591 1
				$result[] = sprintf($txt['_validate_generic'], $field);
592 1
		}
593
594 1
		return empty($result) ? false : $result;
595
	}
596
597
	/**
598
	 * Contains ... Verify that a value is one of those provided (case insensitive)
599
	 *
600
	 * Usage: '[key]' => 'contains[value, value, value]'
601
	 *
602
	 * @param string $field
603
	 * @param mixed[] $input
604
	 * @param string|null $validation_parameters array or null
605
	 */
606 3
	protected function _validate_contains($field, $input, $validation_parameters = null)
607
	{
608 3
		$validation_parameters = array_map('trim', explode(',', strtolower($validation_parameters)));
609 3
		$input[$field] = isset($input[$field]) ? $input[$field] : '';
610 3
		$value = trim(strtolower($input[$field]));
611
612 3
		if (in_array($value, $validation_parameters))
613 3
			return;
614
615
		return array(
616 1
			'field' => $field,
617 1
			'input' => $input[$field],
618 1
			'function' => __FUNCTION__,
619 1
			'param' => implode(',', $validation_parameters)
620 1
		);
621
	}
622
623
	/**
624
	 * NotEqual ... Verify that a value does equal any values in list (case insensitive)
625
	 *
626
	 * Usage: '[key]' => 'notequal[value, value, value]'
627
	 *
628
	 * @param string $field
629
	 * @param mixed[] $input
630
	 * @param string|null $validation_parameters array or null
631
	 */
632 3
	protected function _validate_notequal($field, $input, $validation_parameters = null)
633
	{
634 3
		$validation_parameters = explode(',', trim(strtolower($validation_parameters)));
635 3
		$input[$field] = isset($input[$field]) ? $input[$field] : '';
636 3
		$value = trim(strtolower($input[$field]));
637
638 3
		if (!in_array($value, $validation_parameters))
639 3
			return;
640
641
		return array(
642 1
			'field' => $field,
643 1
			'input' => $input[$field],
644 1
			'function' => __FUNCTION__,
645 1
			'param' => implode(',', $validation_parameters)
646 1
		);
647
	}
648
649
	/**
650
	 * Limits ... Verify that a value is within the defined limits
651
	 *
652
	 * Usage: '[key]' => 'limits[min, max]'
653
	 * >= min and <= max
654
	 * Limits may be specified one sided
655
	 *  - limits[,10] means <=10 with no lower bound check
656
	 *  - limits[10,] means >= 10 with no upper bound
657
	 *
658
	 * @param string $field
659
	 * @param mixed[] $input
660
	 * @param string|null $validation_parameters array or null
661
	 */
662 2
	protected function _validate_limits($field, $input, $validation_parameters = null)
663
	{
664 2
		$validation_parameters = explode(',', $validation_parameters);
665 2
		$validation_parameters = array_filter($validation_parameters, 'strlen');
666 2
		$input[$field] = isset($input[$field]) ? $input[$field] : '';
667 2
		$value = $input[$field];
668
669
		// Lower bound ?
670 2
		$passmin = true;
671 2
		if (isset($validation_parameters[0]))
672 2
			$passmin = $value >= $validation_parameters[0];
673
674
		// Upper bound ?
675 2
		$passmax = true;
676 2
		if (isset($validation_parameters[1]))
677 2
			$passmax = $value <= $validation_parameters[1];
678
679 2
		if ($passmax && $passmin)
680 2
			return;
681
682
		return array(
683 2
			'field' => $field,
684 2
			'input' => $input[$field],
685 2
			'function' => __FUNCTION__,
686 2
			'param' => implode(',', $validation_parameters)
687 2
		);
688
	}
689
690
	/**
691
	 * Without ... Verify that a value does contain any characters/values in list
692
	 *
693
	 * Usage: '[key]' => 'without[value, value, value]'
694
	 *
695
	 * @param string $field
696
	 * @param mixed[] $input
697
	 * @param mixed[]|null $validation_parameters array or null
698
	 */
699 2
	protected function _validate_without($field, $input, $validation_parameters = null)
700
	{
701 2
		$validation_parameters = explode(',', $validation_parameters);
0 ignored issues
show
Bug introduced by
It seems like $validation_parameters can also be of type array<mixed,mixed>; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

701
		$validation_parameters = explode(',', /** @scrutinizer ignore-type */ $validation_parameters);
Loading history...
702 2
		$input[$field] = isset($input[$field]) ? $input[$field] : '';
703 2
		$value = $input[$field];
704
705 2
		foreach ($validation_parameters as $dummy => $check)
706
		{
707 2
			if (strpos($value, $check) !== false)
708 2
				return array(
709 2
					'field' => $field,
710 2
					'input' => $input[$field],
711 2
					'function' => __FUNCTION__,
712 2
					'param' => implode(',', $validation_parameters)
713 2
				);
714 2
		}
715
716 2
		return;
717
	}
718
719
	/**
720
	 * required ... Check if the specified key is present and not empty
721
	 *
722
	 * Usage: '[key]' => 'required'
723
	 *
724
	 * @param string $field
725
	 * @param mixed[] $input
726
	 * @param mixed[]|null $validation_parameters array or null
727
	 */
728 4
	protected function _validate_required($field, $input, $validation_parameters = null)
729
	{
730 4
		if (isset($input[$field]) && trim($input[$field]) !== '')
731 4
			return;
732
733
		return array(
734 1
			'field' => $field,
735 1
			'input' => isset($input[$field]) ? $input[$field] : '',
736 1
			'function' => __FUNCTION__,
737
			'param' => $validation_parameters
738 1
		);
739
	}
740
741
	/**
742
	 * valid_email .... Determine if the provided email is valid
743
	 *
744
	 * Usage: '[key]' => 'valid_email'
745
	 *
746
	 * @param string $field
747
	 * @param mixed[] $input
748
	 * @param mixed[]|null $validation_parameters array or null
749
	 */
750 2
	protected function _validate_valid_email($field, $input, $validation_parameters = null)
751
	{
752 2
		if (!isset($input[$field]))
753 2
			return;
754
755
		// Quick check, no @ in the email
756 2
		if (strrpos($input[$field], '@') === false)
757 2
			$valid = false;
758
		else
759 2
			$valid = filter_var($input[$field], FILTER_VALIDATE_EMAIL) !== false;
760
761
		if ($valid)
762 2
			return;
763
764
		return array(
765 1
			'field' => $field,
766 1
			'input' => $input[$field],
767 1
			'function' => __FUNCTION__,
768
			'param' => $validation_parameters
769 1
		);
770
	}
771
772
	/**
773
	 * max_length ... Determine if the provided value length is less or equal to a specific value
774
	 *
775
	 * Usage: '[key]' => 'max_length[x]'
776
	 *
777
	 * @param string $field
778
	 * @param mixed[] $input
779
	 * @param mixed[]|null $validation_parameters array or null
780
	 */
781 2
	protected function _validate_max_length($field, $input, $validation_parameters = null)
782
	{
783 2
		if (!isset($input[$field]))
784 2
			return;
785
786 2
		if (Util::strlen($input[$field]) <= (int) $validation_parameters)
787 2
			return;
788
789
		return array(
790 1
			'field' => $field,
791 1
			'input' => $input[$field],
792 1
			'function' => __FUNCTION__,
793
			'param' => $validation_parameters
794 1
		);
795
	}
796
797
	/**
798
	 * min_length Determine if the provided value length is greater than or equal to a specific value
799
	 *
800
	 * Usage: '[key]' => 'min_length[x]'
801
	 *
802
	 * @param string $field
803
	 * @param mixed[] $input
804
	 * @param mixed[]|null $validation_parameters array or null
805
	 */
806 1
	protected function _validate_min_length($field, $input, $validation_parameters = null)
807
	{
808 1
		if (!isset($input[$field]))
809 1
			return;
810
811 1
		if (Util::strlen($input[$field]) >= (int) $validation_parameters)
812 1
			return;
813
814
		return array(
815 1
			'field' => $field,
816 1
			'input' => $input[$field],
817 1
			'function' => __FUNCTION__,
818
			'param' => $validation_parameters
819 1
		);
820
	}
821
822
	/**
823
	 * length ... Determine if the provided value length matches a specific value
824
	 *
825
	 * Usage: '[key]' => 'exact_length[x]'
826
	 *
827
	 * @param string $field
828
	 * @param mixed[] $input
829
	 * @param mixed[]|null $validation_parameters array or null
830
	 */
831 1
	protected function _validate_length($field, $input, $validation_parameters = null)
832
	{
833 1
		if (!isset($input[$field]))
834 1
			return;
835
836 1
		if (Util::strlen($input[$field]) == (int) $validation_parameters)
837 1
			return;
838
839
		return array(
840 1
			'field' => $field,
841 1
			'input' => $input[$field],
842 1
			'function' => __FUNCTION__,
843
			'param' => $validation_parameters
844 1
		);
845
	}
846
847
	/**
848
	 * alpha ... Determine if the provided value contains only alpha characters
849
	 *
850
	 * Usage: '[key]' => 'alpha'
851
	 *
852
	 * @param string $field
853
	 * @param mixed[] $input
854
	 * @param mixed[]|null $validation_parameters array or null
855
	 */
856 1
	protected function _validate_alpha($field, $input, $validation_parameters = null)
857
	{
858 1
		if (!isset($input[$field]))
859 1
			return;
860
861
		// A character with the Unicode property of letter (any kind of letter from any language)
862 1
		if (!preg_match('~^(\p{L})+$~iu', $input[$field]))
863 1
		{
864
			return array(
865 1
				'field' => $field,
866 1
				'input' => $input[$field],
867 1
				'function' => __FUNCTION__,
868
				'param' => $validation_parameters
869 1
			);
870
		}
871 1
	}
872
873
	/**
874
	 * alpha_numeric ... Determine if the provided value contains only alpha-numeric characters
875
	 *
876
	 * Usage: '[key]' => 'alpha_numeric'
877
	 * Allows letters, numbers dash and underscore characters
878
	 *
879
	 * @param string $field
880
	 * @param mixed[] $input
881
	 * @param mixed[]|null $validation_parameters array or null
882
	 */
883 1
	protected function _validate_alpha_numeric($field, $input, $validation_parameters = null)
884
	{
885 1
		if (!isset($input[$field]))
886 1
			return;
887
888
		// A character with the Unicode property of letter or number (any kind of letter or numeric 0-9 from any language)
889 1
		if (!preg_match('~^([-_\p{L}\p{Nd}])+$~iu', $input[$field]))
890 1
		{
891
			return array(
892 1
				'field' => $field,
893 1
				'input' => $input[$field],
894 1
				'function' => __FUNCTION__,
895
				'param' => $validation_parameters
896 1
			);
897
		}
898 1
	}
899
900
	/**
901
	 * alpha_dash ... Determine if the provided value contains only alpha characters plus dashed and underscores
902
	 *
903
	 * Usage: '[key]' => 'alpha_dash'
904
	 *
905
	 * @param string $field
906
	 * @param mixed[] $input
907
	 * @param mixed[]|null $validation_parameters array or null
908
	 */
909 1
	protected function _validate_alpha_dash($field, $input, $validation_parameters = null)
910
	{
911 1
		if (!isset($input[$field]))
912 1
			return;
913
914 1
		if (!preg_match('~^([-_\p{L}])+$~iu', $input[$field]))
915 1
		{
916
			return array(
917 1
				'field' => $field,
918 1
				'input' => $input[$field],
919 1
				'function' => __FUNCTION__,
920
				'param' => $validation_parameters
921 1
			);
922
		}
923 1
	}
924
925
	/**
926
	 * isarray ... Determine if the provided value exists and is an array
927
	 *
928
	 * Usage: '[key]' => 'isarray'
929
	 *
930
	 * @param string $field
931
	 * @param mixed[] $input
932
	 * @param mixed[]|null $validation_parameters array or null
933
	 */
934 2
	protected function _validate_isarray($field, $input, $validation_parameters = null)
935
	{
936 2
		if (!isset($input[$field]))
937 2
			return;
938
939 2
		if (!is_array($input[$field]))
940 2
		{
941
			return array(
942
				'field' => $field,
943
				'input' => $input[$field],
944
				'function' => __FUNCTION__,
945
				'param' => $validation_parameters
946
			);
947
		}
948 2
	}
949
950
	/**
951
	 * numeric ... Determine if the provided value is a valid number or numeric string
952
	 *
953
	 * Usage: '[key]' => 'numeric'
954
	 *
955
	 * @param string $field
956
	 * @param mixed[] $input
957
	 * @param mixed[]|null $validation_parameters array or null
958
	 */
959 1
	protected function _validate_numeric($field, $input, $validation_parameters = null)
960
	{
961 1
		if (!isset($input[$field]))
962 1
			return;
963
964 1
		if (!is_numeric($input[$field]))
965 1
		{
966
			return array(
967 1
				'field' => $field,
968 1
				'input' => $input[$field],
969 1
				'function' => __FUNCTION__,
970
				'param' => $validation_parameters
971 1
			);
972
		}
973 1
	}
974
975
	/**
976
	 * integer ... Determine if the provided value is a valid integer
977
	 *
978
	 * Usage: '[key]' => 'integer'
979
	 *
980
	 * @param string $field
981
	 * @param mixed[] $input
982
	 * @param mixed[]|null $validation_parameters array or null
983
	 */
984 21
	protected function _validate_integer($field, $input, $validation_parameters = null)
985
	{
986 21
		if (!isset($input[$field]))
987 21
			return;
988
989 20
		$filter = filter_var($input[$field], FILTER_VALIDATE_INT);
990
991 20
		if ($filter === false && version_compare(PHP_VERSION, 5.4, '<') && ($input[$field] === '+0' || $input[$field] === '-0'))
992 20
		{
993
			$filter = true;
994
		}
995
996 20
		if ($filter === false)
997 20
		{
998
			return array(
999 12
				'field' => $field,
1000 12
				'input' => $input[$field],
1001 12
				'function' => __FUNCTION__,
1002
				'param' => $validation_parameters
1003 12
			);
1004
		}
1005 9
	}
1006
1007
	/**
1008
	 * boolean ... Determine if the provided value is a boolean
1009
	 *
1010
	 * Usage: '[key]' => 'boolean'
1011
	 *
1012
	 * @param string $field
1013
	 * @param mixed[] $input
1014
	 * @param mixed[]|null $validation_parameters array or null
1015
	 */
1016 28
	protected function _validate_boolean($field, $input, $validation_parameters = null)
1017
	{
1018 28
		if (!isset($input[$field]))
1019 28
			return;
1020
1021 27
		$filter = filter_var($input[$field], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
1022
1023
		// Fixed in php 7 and later in php 5.6.27 https://bugs.php.net/bug.php?id=67167
1024 27
		if (version_compare(PHP_VERSION, '5.6.27', '>='))
1025 27
		{
1026 27
			$filter = $filter;
1027 27
		}
1028 27
		if (version_compare(PHP_VERSION, 5.4, '<') && $filter === null && ($input[$field] === false || $input[$field] === ''))
1029 27
		{
1030
			$filter = false;
1031
		}
1032 27
		if ($filter === false && is_object($input[$field]) && method_exists($input[$field], '__tostring') === false)
1033 27
		{
1034
			$filter = null;
1035
		}
1036
1037 27
		if ($filter === null)
1038 27
		{
1039
			return array(
1040 15
				'field' => $field,
1041 15
				'input' => $input[$field],
1042 15
				'function' => __FUNCTION__,
1043
				'param' => $validation_parameters
1044 15
			);
1045
		}
1046 13
	}
1047
1048
	/**
1049
	 * float ... Determine if the provided value is a valid float
1050
	 *
1051
	 * Usage: '[key]' => 'float'
1052
	 *
1053
	 * @param string $field
1054
	 * @param mixed[] $input
1055
	 * @param mixed[]|null $validation_parameters array or null
1056
	 */
1057 21
	protected function _validate_float($field, $input, $validation_parameters = null)
1058
	{
1059 21
		if (!isset($input[$field]))
1060 21
			return;
1061
1062 20
		if (filter_var($input[$field], FILTER_VALIDATE_FLOAT) === false)
1063 20
		{
1064
			return array(
1065 9
				'field' => $field,
1066 9
				'input' => $input[$field],
1067 9
				'function' => __FUNCTION__,
1068
				'param' => $validation_parameters
1069 9
			);
1070
		}
1071 12
	}
1072
1073
	/**
1074
	 * valid_url ... Determine if the provided value is a valid-ish URL
1075
	 *
1076
	 * Usage: '[key]' => 'valid_url'
1077
	 *
1078
	 * @param string $field
1079
	 * @param mixed[] $input
1080
	 * @param mixed[]|null $validation_parameters array or null
1081
	 */
1082 1
	protected function _validate_valid_url($field, $input, $validation_parameters = null)
1083
	{
1084 1
		if (!isset($input[$field]))
1085 1
			return;
1086
1087 1
		if (!preg_match('`^(https{0,1}?:(//([a-z0-9\-._~%]+)(:[0-9]+)?(/[a-z0-9\-._~%!$&\'()*+,;=:@]+)*/?))(\?[a-z0-9\-._~%!$&\'()*+,;=:@/?]*)?(\#[a-z0-9\-._~%!$&\'()*+,;=:@/?]*)?$`', $input[$field], $matches))
1088 1
		{
1089
			return array(
1090 1
				'field' => $field,
1091 1
				'input' => $input[$field],
1092 1
				'function' => __FUNCTION__,
1093
				'param' => $validation_parameters
1094 1
			);
1095
		}
1096 1
	}
1097
1098
	/**
1099
	 * valid_ipv6 ... Determine if the provided value is a valid IPv6 address
1100
	 *
1101
	 * Usage: '[key]' => 'valid_ipv6'
1102
	 *
1103
	 * @param string $field
1104
	 * @param mixed[] $input
1105
	 * @param mixed[]|null $validation_parameters array or null
1106
	 */
1107 1
	protected function _validate_valid_ipv6($field, $input, $validation_parameters = null)
1108
	{
1109 1
		if (!isset($input[$field]))
1110 1
			return;
1111
1112 1
		if (filter_var($input[$field], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false)
1113 1
		{
1114
			return array(
1115 1
				'field' => $field,
1116 1
				'input' => $input[$field],
1117 1
				'function' => __FUNCTION__,
1118
				'param' => $validation_parameters
1119 1
			);
1120
		}
1121 1
	}
1122
1123
	/**
1124
	 * valid_ip ... Determine if the provided value is a valid IP4 address
1125
	 *
1126
	 * Usage: '[key]' => 'valid_ip'
1127
	 *
1128
	 * @param string $field
1129
	 * @param mixed[] $input
1130
	 * @param mixed[]|null $validation_parameters array or null
1131
	 */
1132 1
	protected function _validate_valid_ip($field, $input, $validation_parameters = null)
1133
	{
1134 1
		if (!isset($input[$field]))
1135 1
			return;
1136
1137 1
		if (filter_var($input[$field], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false)
1138 1
		{
1139
			return array(
1140 1
				'field' => $field,
1141 1
				'input' => $input[$field],
1142 1
				'function' => __FUNCTION__,
1143
				'param' => $validation_parameters
1144 1
			);
1145
		}
1146 1
	}
1147
1148
	/**
1149
	 * Validate PHP syntax of an input.
1150
	 *
1151
	 * This approach to validation has been inspired by Compuart.
1152
	 *
1153
	 * Usage: '[key]' => 'php_syntax'
1154
	 *
1155
	 * @uses ParseError
1156
	 * @param string $field
1157
	 * @param mixed[] $input
1158
	 * @param mixed[]|null $validation_parameters array or null
1159
	 */
1160 1
	protected function _validate_php_syntax($field, $input, $validation_parameters = null)
1161
	{
1162 1
		if (!isset($input[$field]))
1163 1
			return;
1164
1165
		// Check the depth.
1166 1
		$level = 0;
1167 1
		$tokens = @token_get_all($input[$field]);
1168 1
		foreach ($tokens as $token)
1169
		{
1170 1
			if ($token === '{' || (isset($token[1]) && $token[1] === '${'))
1171 1
				$level++;
1172 1
			elseif ($token === '}')
1173
				$level--;
1174 1
		}
1175
1176 1
		if (!empty($level))
1177 1
			$result = false;
1178
		else
1179
		{
1180
			// Check the validity of the syntax.
1181 1
			ob_start();
1182 1
			$errorReporting = error_reporting(0);
1183
			try
1184
			{
1185 1
				$result = @eval('
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1186
					if (false)
1187
					{
1188 1
						' . preg_replace('~^(?:\s*<\\?(?:php)?|\\?>\s*$)~u', '', $input[$field]) . '
1189
					}
1190 1
				');
1191
			}
1192 1
			catch (ParseError $e)
1193
			{
1194
				$result = false;
1195
			}
1196 1
			error_reporting($errorReporting);
1197 1
			@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_end_clean(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1197
			/** @scrutinizer ignore-unhandled */ @ob_end_clean();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1198
		}
1199
1200 1
		if ($result === false)
1201 1
		{
1202 1
			$errorMsg = error_get_last();
1203
1204
			return array(
1205 1
				'field' => $field,
1206 1
				'input' => $input[$field],
1207 1
				'error' => '_validate_php_syntax',
1208 1
				'error_msg' => $errorMsg['message'],
1209
				'param' => $validation_parameters
1210 1
			);
1211
		}
1212 1
	}
1213
1214
	/**
1215
	 * Checks if the input is a valid css-like color
1216
	 *
1217
	 * Usage: '[key]' => 'valid_color'
1218
	 *
1219
	 * @param string $field
1220
	 * @param mixed[] $input
1221
	 * @param mixed[]|null $validation_parameters array or null
1222
	 */
1223 1
	protected function _validate_valid_color($field, $input, $validation_parameters = null)
1224
	{
1225 1
		if (!isset($input[$field]))
1226 1
			return;
1227
1228
		// A color can be a name: there are 140 valid, but a similar list is too long, so let's just use the basic 17
1229 1
		if (in_array(strtolower($input[$field]), array('aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow')))
1230 1
			return true;
1231
1232
		// An hex code
1233 1
		if (preg_match('~^#([a-f0-9]{3}|[a-f0-9]{6})$~i', $input[$field]) === 1)
1234 1
			return true;
1235
1236
		// RGB
1237 1
		if (preg_match('~^rgb\(\d{1,3},\d{1,3},\d{1,3}\)$~i', str_replace(' ', '', $input[$field])) === 1)
1238 1
			return true;
1239
1240
		// RGBA
1241 1
		if (preg_match('~^rgba\(\d{1,3},\d{1,3},\d{1,3},(0|0\.\d+|1(\.0*)|\.\d+)\)$~i', str_replace(' ', '', $input[$field])) === 1)
1242 1
			return true;
1243
1244
		// HSL
1245 1
		if (preg_match('~^hsl\(\d{1,3},\d{1,3}%,\d{1,3}%\)$~i', str_replace(' ', '', $input[$field])) === 1)
1246 1
			return true;
1247
1248
		// HSLA
1249 1
		if (preg_match('~^hsla\(\d{1,3},\d{1,3}%,\d{1,3}%,(0|0\.\d+|1(\.0*)|\.\d+)\)$~i', str_replace(' ', '', $input[$field])) === 1)
1250 1
			return true;
1251
1252
		return array(
1253 1
			'field' => $field,
1254 1
			'input' => $input[$field],
1255 1
			'function' => __FUNCTION__,
1256
			'param' => $validation_parameters
1257 1
		);
1258
	}
1259
1260
	/**
1261
	 * gmail_normalize ... Used to normalize a gmail address as many resolve to the same address
1262
	 *
1263
	 * - Gmail user can use @googlemail.com instead of @gmail.com
1264
	 * - Gmail ignores all characters after a + (plus sign) in the username
1265
	 * - Gmail ignores all . (dots) in username
1266
	 * - [email protected], [email protected], [email protected] and [email protected] are same email address.
1267
	 *
1268
	 * @param string $input
1269
	 */
1270
	protected function _sanitation_gmail_normalize($input)
1271
	{
1272
		if (!isset($input))
1273
			return;
1274
1275
		$at_index = strrpos($input, '@');
1276
1277
		// Time to do some checking on the local@domain parts
1278
		$local_name = substr($input, 0, $at_index);
1279
		$domain_name = strtolower(substr($input, $at_index + 1));
1280
1281
		// Gmail address?
1282
		if (in_array($domain_name, array('gmail.com', 'googlemail.com')))
1283
		{
1284
			// Gmail ignores all . (dot) in username
1285
			$local_name = str_replace('.', '', $local_name);
1286
1287
			// Gmail ignores all characters after a + (plus sign) in username
1288
			$temp = explode('+', $local_name);
1289
			$local_name = $temp[0];
1290
1291
			// @todo should we force gmail.com or use $domain_name, force is safest but perhaps most confusing
1292
		}
1293
1294
		return $local_name . '@' . $domain_name;
1295
	}
1296
1297
	/**
1298
	 * Uses Util::htmlspecialchars to sanitize any html in the input
1299
	 *
1300
	 * @param string $input
1301
	 */
1302
	protected function _sanitation_cleanhtml($input)
1303
	{
1304
		if (!isset($input))
1305
			return;
1306
1307
		return Util::htmlspecialchars($input);
1308
	}
1309
}
1310