• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Wixel / GUMP / 19773035152

28 Nov 2025 08:12PM UTC coverage: 85.111%. Remained the same
19773035152

push

github

sn
Cleaning up the README file and formatting / linting the PHP code with PHP-CS-Fixer

8 of 9 new or added lines in 1 file covered. (88.89%)

7 existing lines in 1 file now uncovered.

383 of 450 relevant lines covered (85.11%)

47.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.11
/gump.class.php
1
<?php
2

3
use GUMP\ArrayHelpers;
4
use GUMP\EnvHelpers;
5

6
/**
7
 * GUMP - A Fast PHP Data Validation & Filtering Library
8
 *
9
 * GUMP is a standalone PHP data validation and filtering library that makes validating
10
 * any data easy and painless without the reliance on a framework. Supports 76 validators,
11
 * 16 filters, internationalization (19 languages), and custom validators/filters.
12
 *
13
 * @package GUMP
14
 * @version 1.x
15
 * @author Sean Nieuwoudt <sean@wixel.net>
16
 * @copyright 2013-2025 Sean Nieuwoudt
17
 * @license MIT
18
 * @link https://github.com/wixel/gump
19
 *
20
 * @since 1.0
21
 */
22
class GUMP
23
{
24
    /**
25
     * Singleton instance of GUMP.
26
     *
27
     * @var self|null
28
     */
29
    protected static $instance = null;
30

31
    /**
32
     * Contains readable field names that have been manually set.
33
     *
34
     * @var array
35
     */
36
    protected static $fields = [];
37

38
    /**
39
     * Custom validators.
40
     *
41
     * @var array
42
     */
43
    protected static $validation_methods = [];
44

45
    /**
46
     * Custom validators error messages.
47
     *
48
     * @var array
49
     */
50
    protected static $validation_methods_errors = [];
51

52
    /**
53
     * Customer filters.
54
     *
55
     * @var array
56
     */
57
    protected static $filter_methods = [];
58

59
    // ** ------------------------- Instance Helper ---------------------------- ** //
60

61
    /**
62
     * Function to create and return previously created instance
63
     *
64
     * @return GUMP
65
     */
66
    public static function get_instance()
67
    {
68
        if (self::$instance === null) {
55✔
69
            self::$instance = new static();
1✔
70
        }
71

72
        return self::$instance;
55✔
73
    }
74

75
    // ** ------------------------- Configuration -------------------------------- ** //
76

77
    /**
78
     * Rules delimiter.
79
     *
80
     * @var string
81
     */
82
    public static $rules_delimiter = '|';
83

84
    /**
85
     * Rules-parameters delimiter.
86
     *
87
     * @var string
88
     */
89
    public static $rules_parameters_delimiter = ',';
90

91
    /**
92
     * Rules parameters array delimiter.
93
     *
94
     * @var string
95
     */
96
    public static $rules_parameters_arrays_delimiter = ';';
97

98
    /**
99
     * Characters that will be replaced to spaces during field name conversion (street_name => Street Name).
100
     *
101
     * @var array
102
     */
103
    public static $field_chars_to_spaces = ['_', '-'];
104

105
    // ** ------------------------- Validation Data ------------------------------- ** //
106

107
    /**
108
     * Basic HTML tags allowed in the basic_tags filter.
109
     *
110
     * @var string
111
     */
112
    public static $basic_tags = '<br><p><a><strong><b><i><em><img><blockquote><code><dd><dl><hr><h1><h2><h3><h4><h5><h6><label><ul><li><span><sub><sup>';
113

114
    /**
115
     * English noise words used in the noise_words filter.
116
     *
117
     * @var string
118
     */
119
    public static $en_noise_words = "about,after,all,also,an,and,another,any,are,as,at,be,because,been,before,
120
                                     being,between,both,but,by,came,can,come,could,did,do,each,for,from,get,
121
                                     got,has,had,he,have,her,here,him,himself,his,how,if,in,into,is,it,its,it's,like,
122
                                     make,many,me,might,more,most,much,must,my,never,now,of,on,only,or,other,
123
                                     our,out,over,said,same,see,should,since,some,still,such,take,than,that,
124
                                     the,their,them,then,there,these,they,this,those,through,to,too,under,up,
125
                                     very,was,way,we,well,were,what,where,which,while,who,with,would,you,your,a,
126
                                     b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,$,1,2,3,4,5,6,7,8,9,0,_";
127

128
    /**
129
     * Regex pattern for alpha characters including international characters.
130
     *
131
     * @var string
132
     */
133
    private static $alpha_regex = 'a-zÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖßÙÚÛÜÝŸÑàáâãäåçèéêëìíîïðòóôõöùúûüýÿñ';
134

135
    /**
136
     * Values that are considered TRUE in boolean validation and filtering.
137
     *
138
     * @var array
139
     */
140
    public static $trues = ['1', 1, 'true', true, 'yes', 'on'];
141

142
    /**
143
     * Values that are considered FALSE in boolean validation and filtering.
144
     *
145
     * @var array
146
     */
147
    public static $falses = ['0', 0, 'false', false, 'no', 'off'];
148

149
    /**
150
     * Language for error messages.
151
     *
152
     * @var string
153
     */
154
    protected $lang;
155

156
    /**
157
     * Custom field-rule messages.
158
     *
159
     * @var array
160
     */
161
    protected $fields_error_messages = [];
162

163
    /**
164
     * Set of validation rules for execution.
165
     *
166
     * @var array
167
     */
168
    protected $validation_rules = [];
169

170
    /**
171
     * Set of filters rules for execution.
172
     *
173
     * @var array
174
     */
175
    protected $filter_rules = [];
176

177
    /**
178
     * Errors.
179
     *
180
     * @var array
181
     */
182
    protected $errors = [];
183

184
    // ** ------------------------- Validation Helpers ---------------------------- ** //
185

186
    /**
187
     * GUMP constructor.
188
     *
189
     * @param string $lang
190
     * @throws Exception when language is not supported
191
     */
192
    public function __construct(string $lang = 'en')
193
    {
194
        $lang_file_location = __DIR__.DIRECTORY_SEPARATOR.'lang'.DIRECTORY_SEPARATOR.$lang.'.php';
465✔
195

196
        if (!EnvHelpers::file_exists($lang_file_location)) {
465✔
197
            throw new Exception(sprintf("'%s' language is not supported.", $lang));
1✔
198
        }
199

200
        $this->lang = $lang;
465✔
201
    }
465✔
202

203
    /**
204
     * Shorthand method for inline validation.
205
     *
206
     * @param array $data The data to be validated
207
     * @param array $validators The GUMP validators
208
     * @param array $fields_error_messages
209
     * @return mixed True(boolean) or the array of error messages
210
     * @throws Exception If validation rule does not exist
211
     */
212
    public static function is_valid(array $data, array $validators, array $fields_error_messages = [])
213
    {
214
        $gump = self::get_instance();
54✔
215
        $gump->validation_rules($validators);
54✔
216
        $gump->set_fields_error_messages($fields_error_messages);
54✔
217

218
        if ($gump->run($data) === false) {
54✔
219
            return $gump->get_readable_errors();
31✔
220
        }
221

222
        return true;
23✔
223
    }
224

225
    /**
226
     * Shorthand method for running only the data filters.
227
     *
228
     * @param array $data
229
     * @param array $filters
230
     * @return mixed
231
     * @throws Exception If filter does not exist
232
     */
233
    public static function filter_input(array $data, array $filters)
234
    {
235
        $gump = self::get_instance();
1✔
236

237
        return $gump->filter($data, $filters);
1✔
238
    }
239

240
    /**
241
     * Magic method to generate the validation error messages.
242
     *
243
     * @return string
244
     * @throws Exception
245
     */
246
    public function __toString()
247
    {
248
        return $this->get_readable_errors(true);
1✔
249
    }
250

251
    /**
252
     * An empty value for us is: null, empty string or empty array
253
     *
254
     * @param mixed $value
255
     * @return bool
256
     */
257
    public static function is_empty($value)
258
    {
259
        return (is_null($value) || $value === '' || (is_array($value) && count($value) === 0));
347✔
260
    }
261

262
    /**
263
     * Adds a custom validation rule using a callback function.
264
     *
265
     * @param string $rule
266
     * @param callable $callback
267
     * @param string $error_message
268
     *
269
     * @return void
270
     * @throws Exception when validator with the same name already exists
271
     */
272
    public static function add_validator(string $rule, callable $callback, string $error_message)
273
    {
274
        if (method_exists(__CLASS__, self::validator_to_method($rule)) || isset(self::$validation_methods[$rule])) {
16✔
275
            throw new Exception(sprintf("'%s' validator is already defined.", $rule));
1✔
276
        }
277

278
        self::$validation_methods[$rule] = $callback;
16✔
279
        self::$validation_methods_errors[$rule] = $error_message;
16✔
280
    }
16✔
281

282
    /**
283
     * Adds a custom filter using a callback function.
284
     *
285
     * @param string $rule
286
     * @param callable $callback
287
     *
288
     * @return void
289
     * @throws Exception when filter with the same name already exists
290
     */
291
    public static function add_filter(string $rule, callable $callback)
292
    {
293
        if (method_exists(__CLASS__, self::filter_to_method($rule)) || isset(self::$filter_methods[$rule])) {
5✔
294
            throw new Exception(sprintf("'%s' filter is already defined.", $rule));
1✔
295
        }
296

297
        self::$filter_methods[$rule] = $callback;
5✔
298
    }
5✔
299

300
    /**
301
     * Checks if a validator exists.
302
     *
303
     * @param string $rule
304
     *
305
     * @return bool
306
     */
307
    public static function has_validator(string $rule)
308
    {
309
        return method_exists(__CLASS__, self::validator_to_method($rule)) || isset(self::$validation_methods[$rule]);
6✔
310
    }
311

312
    /**
313
     * Checks if a filter exists.
314
     *
315
     * @param string $filter
316
     *
317
     * @return bool
318
     */
319
    public static function has_filter(string $filter)
320
    {
321
        return method_exists(__CLASS__, self::filter_to_method($filter))
7✔
322
            || isset(self::$filter_methods[$filter])
3✔
323
            || function_exists($filter);
7✔
324
    }
325

326
    /**
327
     * Helper method to extract an element from an array safely
328
     *
329
     * @param  mixed $key
330
     * @param  array $array
331
     * @param  mixed $default
332
     *
333
     * @return mixed
334
     */
335
    public static function field($key, array $array, $default = null)
336
    {
337
        if (isset($array[$key])) {
2✔
338
            return $array[$key];
1✔
339
        }
340

341
        return $default;
1✔
342
    }
343

344
    /**
345
     * Getter/Setter for the validation rules.
346
     *
347
     * @param array $rules
348
     * @return array
349
     */
350
    public function validation_rules(array $rules = [])
351
    {
352
        if (empty($rules)) {
62✔
353
            return $this->validation_rules;
61✔
354
        }
355

356
        $this->validation_rules = $rules;
61✔
357

358
        return $this->validation_rules;
61✔
359
    }
360

361
    /**
362
     * Set field-rule specific error messages.
363
     *
364
     * @param array $fields_error_messages
365
     * @return array
366
     */
367
    public function set_fields_error_messages(array $fields_error_messages)
368
    {
369
        return $this->fields_error_messages = $fields_error_messages;
58✔
370
    }
371

372
    /**
373
     * Getter/Setter for the filter rules.
374
     *
375
     * @param array $rules
376
     * @return array
377
     */
378
    public function filter_rules(array $rules = [])
379
    {
380
        if (empty($rules)) {
62✔
381
            return $this->filter_rules;
61✔
382
        }
383

384
        $this->filter_rules = $rules;
6✔
385

386
        return $this->filter_rules;
6✔
387
    }
388

389
    /**
390
     * Run the filtering and validation after each other.
391
     *
392
     * @param array  $data
393
     * @param bool   $check_fields
394
     *
395
     * @return array|bool
396
     * @throws Exception
397
     */
398
    public function run(array $data, $check_fields = false)
399
    {
400
        $data = $this->filter($data, $this->filter_rules());
60✔
401

402
        $validated = $this->validate($data, $this->validation_rules());
60✔
403

404
        if ($check_fields === true) {
60✔
405
            $this->check_fields($data);
1✔
406
        }
407

408
        if ($validated !== true) {
60✔
409
            return false;
33✔
410
        }
411

412
        return $data;
27✔
413
    }
414

415
    /**
416
     * Ensure that the field counts match the validation rule counts.
417
     *
418
     * @param array $data
419
     */
420
    private function check_fields(array $data)
421
    {
422
        $ruleset = $this->validation_rules();
1✔
423
        $mismatch = array_diff_key($data, $ruleset);
1✔
424
        $fields = array_keys($mismatch);
1✔
425

426
        foreach ($fields as $field) {
1✔
427
            $this->errors[] = $this->generate_error_array($field, $data[$field], 'mismatch');
1✔
428
        }
429
    }
1✔
430

431
    /**
432
     * Sanitize the input data.
433
     *
434
     * @param array $input
435
     * @param array $fields
436
     * @param bool $utf8_encode
437
     *
438
     * @return array
439
     */
440
    public function sanitize(array $input, array $fields = [], bool $utf8_encode = true)
441
    {
442
        if (empty($fields)) {
3✔
443
            $fields = array_keys($input);
1✔
444
        }
445

446
        $return = [];
3✔
447

448
        foreach ($fields as $field) {
3✔
449
            if (!isset($input[$field])) {
3✔
450
                continue;
1✔
451
            }
452

453
            $value = $input[$field];
3✔
454
            if (is_array($value)) {
3✔
455
                $value = $this->sanitize($value, [], $utf8_encode);
1✔
456
            }
457
            if (is_string($value)) {
3✔
458
                if (strpos($value, "\r") !== false) {
3✔
459
                    $value = trim($value);
3✔
460
                }
461

462
                if (function_exists('iconv') && function_exists('mb_detect_encoding') && $utf8_encode) {
3✔
463
                    $current_encoding = mb_detect_encoding($value);
3✔
464

465
                    if ($current_encoding !== 'UTF-8' && $current_encoding !== 'UTF-16') {
3✔
466
                        $value = iconv($current_encoding, 'UTF-8', $value);
3✔
467
                    }
468
                }
469

470
                $value = static::polyfill_filter_var_string($value);
3✔
471
            }
472

473
            $return[$field] = $value;
3✔
474
        }
475

476
        return $return;
3✔
477
    }
478

479
    /**
480
     * Return the error array from the last validation run.
481
     *
482
     * @return array
483
     */
484
    public function errors()
485
    {
486
        return $this->errors;
4✔
487
    }
488

489
    /**
490
     * Perform data validation against the provided ruleset.
491
     *
492
     * @param array $input Input data.
493
     * @param array $ruleset Validation rules.
494
     *
495
     * @return bool|array Returns bool true when no errors. Returns array when errors.
496
     * @throws Exception
497
     */
498
    public function validate(array $input, array $ruleset)
499
    {
500
        $this->errors = [];
356✔
501

502
        foreach ($ruleset as $field => $rawRules) {
356✔
503
            $input[$field] = ArrayHelpers::data_get($input, $field);
356✔
504

505
            $rules = $this->parse_rules($rawRules);
356✔
506
            $is_required = $this->field_has_required_rules($rules);
356✔
507

508
            if (!$is_required && self::is_empty($input[$field])) {
356✔
509
                continue;
40✔
510
            }
511

512
            foreach ($rules as $rule) {
316✔
513
                $parsed_rule = $this->parse_rule($rule);
316✔
514
                $result = $this->foreach_call_validator($parsed_rule['rule'], $field, $input, $parsed_rule['param']);
316✔
515

516
                if (is_array($result)) {
315✔
517
                    $this->errors[] = $result;
164✔
518
                    break; // exit on first error
315✔
519
                }
520
            }
521
        }
522

523
        return (count($this->errors) > 0) ? $this->errors : true;
355✔
524
    }
525

526
    /**
527
     * Parses filters and validators rules group.
528
     *
529
     * @param string|array $rules
530
     * @return array
531
     */
532
    private function parse_rules($rules)
533
    {
534
        // v2
535
        if (is_array($rules)) {
437✔
536
            $rules_names = [];
12✔
537
            foreach ($rules as $key => $value) {
12✔
538
                $rules_names[] = is_numeric($key) ? $value : $key;
12✔
539
            }
540

541
            return array_map(function ($value, $key) use ($rules) {
542
                if ($value === $key) {
12✔
543
                    return [ $key ];
8✔
544
                }
545

546
                return [$key, $value];
11✔
547
            }, $rules, $rules_names);
12✔
548
        }
549

550
        return explode(self::$rules_delimiter, $rules);
428✔
551
    }
552

553
    /**
554
     * Parses filters and validators individual rules.
555
     *
556
     * @param string|array $rule
557
     * @return array
558
     */
559
    private function parse_rule($rule)
560
    {
561
        // v2
562
        if (is_array($rule)) {
397✔
563
            return [
564
                'rule' => $rule[0],
11✔
565
                'param' => $this->parse_rule_params($rule[1] ?? []),
11✔
566
            ];
567
        }
568

569
        $result = [
570
            'rule' => $rule,
389✔
571
            'param' => [],
572
        ];
573

574
        if (strpos($rule, self::$rules_parameters_delimiter) !== false) {
389✔
575
            list($rule, $param) = explode(self::$rules_parameters_delimiter, $rule);
98✔
576

577
            $result['rule'] = $rule;
98✔
578
            $result['param'] = $this->parse_rule_params($param);
98✔
579
        }
580

581
        return $result;
389✔
582
    }
583

584
    /**
585
     * Parse rule parameters.
586
     *
587
     * @param string|array $param
588
     * @return array|string|null
589
     */
590
    private function parse_rule_params($param)
591
    {
592
        if (is_array($param)) {
108✔
593
            return $param;
11✔
594
        }
595

596
        if (strpos($param, self::$rules_parameters_arrays_delimiter) !== false) {
101✔
597
            return explode(self::$rules_parameters_arrays_delimiter, $param);
39✔
598
        }
599

600
        return [ $param ];
65✔
601
    }
602

603
    /**
604
     * Checks if array of rules contains a required type of validator.
605
     *
606
     * @param array $rules
607
     * @return bool
608
     */
609
    private function field_has_required_rules(array $rules)
610
    {
611
        $require_type_of_rules = ['required', 'required_file'];
356✔
612

613
        // v2 format (using arrays for definition of rules)
614
        if (is_array($rules) && is_array($rules[0])) {
356✔
615
            $found = array_filter($rules, function ($item) use ($require_type_of_rules) {
616
                return in_array($item[0], $require_type_of_rules);
10✔
617
            });
10✔
618

619
            return count($found) > 0;
10✔
620
        }
621

622
        $found = array_values(array_intersect($require_type_of_rules, $rules));
355✔
623

624
        return count($found) > 0;
355✔
625
    }
626

627
    /**
628
     * Helper to convert validator rule name to validator rule method name.
629
     *
630
     * @param string $rule
631
     * @return string
632
     */
633
    private static function validator_to_method(string $rule)
634
    {
635
        return sprintf('validate_%s', $rule);
323✔
636
    }
637

638
    /**
639
     * Helper to convert filter rule name to filter rule method name.
640
     *
641
     * @param string $rule
642
     * @return string
643
     */
644
    private static function filter_to_method(string $rule)
645
    {
646
        return sprintf('filter_%s', $rule);
94✔
647
    }
648

649
    /**
650
     * Calls call_validator.
651
     *
652
     * @param string $rule
653
     * @param string $field
654
     * @param mixed $input
655
     * @param array $rule_params
656
     * @return array|bool
657
     * @throws Exception
658
     */
659
    private function foreach_call_validator(string $rule, string $field, array $input, array $rule_params = [])
660
    {
661
        $is_required_kind_of_rule = $this->field_has_required_rules([$rule]);
316✔
662

663
        // Fixes #315
664
        if ($is_required_kind_of_rule && is_array($input[$field]) && count($input[$field]) === 0) {
316✔
665
            $result = $this->call_validator($rule, $field, $input, $rule_params, $input[$field]);
2✔
666

667
            return is_array($result) ? $result : true;
2✔
668
        }
669

670
        $values = is_array($input[$field]) ? $input[$field] : [ $input[$field] ];
314✔
671

672
        foreach ($values as $value) {
314✔
673
            $result = $this->call_validator($rule, $field, $input, $rule_params, $value);
314✔
674

675
            if (is_array($result)) {
313✔
676
                return $result;
313✔
677
            }
678
        }
679

680
        return true;
164✔
681
    }
682

683
    /**
684
     * Calls a validator.
685
     *
686
     * @param string $rule
687
     * @param string $field
688
     * @param mixed $input
689
     * @param array $rule_params
690
     * @return array|bool
691
     * @throws Exception
692
     */
693
    private function call_validator(string $rule, string $field, array $input, array $rule_params = [], $value = null)
694
    {
695
        $method = self::validator_to_method($rule);
316✔
696

697
        // use native validations
698
        if (is_callable([$this, $method])) {
316✔
699
            $result = $this->$method($field, $input, $rule_params, $value);
306✔
700

701
            // is_array check for backward compatibility
702
            return (is_array($result) || $result === false)
306✔
703
                ? $this->generate_error_array($field, $input[$field], $rule, $rule_params)
154✔
704
                : true;
306✔
705
        }
706

707
        // use custom validations
708
        if (isset(self::$validation_methods[$rule])) {
14✔
709
            $result = call_user_func(self::$validation_methods[$rule], $field, $input, $rule_params, $value);
13✔
710

711
            return ($result === false)
13✔
712
                ? $this->generate_error_array($field, $input[$field], $rule, $rule_params)
11✔
713
                : true;
13✔
714
        }
715

716
        throw new Exception(sprintf("'%s' validator does not exist.", $rule));
1✔
717
    }
718

719
    /**
720
     * Calls a filter.
721
     *
722
     * @param string $rule
723
     * @param mixed $value
724
     * @param array $rule_params
725
     * @return mixed
726
     * @throws Exception
727
     */
728
    private function call_filter(string $rule, $value, array $rule_params = [])
729
    {
730
        $method = self::filter_to_method($rule);
86✔
731

732
        // use native filters
733
        if (is_callable([$this, $method])) {
86✔
734
            return $this->$method($value, $rule_params);
82✔
735
        }
736

737
        // use custom filters
738
        if (isset(self::$filter_methods[$rule])) {
6✔
739
            return call_user_func(self::$filter_methods[$rule], $value, $rule_params);
3✔
740
        }
741

742
        // use php functions as filters
743
        if (function_exists($rule)) {
3✔
744
            return call_user_func($rule, $value, ...$rule_params);
2✔
745
        }
746

747
        throw new Exception(sprintf("'%s' filter does not exist.", $rule));
1✔
748
    }
749

750
    /**
751
     * Generates error array.
752
     *
753
     * @param string $field
754
     * @param mixed $value
755
     * @param string $rule
756
     * @param array $rule_params
757
     * @return array
758
     */
759
    private function generate_error_array(string $field, $value, string $rule, array $rule_params = [])
760
    {
761
        return [
762
            'field' => $field,
165✔
763
            'value' => $value,
165✔
764
            'rule' => $rule,
165✔
765
            'params' => $rule_params,
165✔
766
        ];
767
    }
768

769
    /**
770
     * Set a readable name for a specified field names.
771
     *
772
     * @param string $field
773
     * @param string $readable_name
774
     */
775
    public static function set_field_name(string $field, string $readable_name)
776
    {
777
        self::$fields[$field] = $readable_name;
5✔
778
    }
5✔
779

780
    /**
781
     * Set readable name for specified fields in an array.
782
     *
783
     * @param array $array
784
     */
785
    public static function set_field_names(array $array)
786
    {
787
        foreach ($array as $field => $readable_name) {
1✔
788
            self::set_field_name($field, $readable_name);
1✔
789
        }
790
    }
1✔
791

792
    /**
793
     * Set a custom error message for a validation rule.
794
     *
795
     * @param string $rule
796
     * @param string $message
797
     */
798
    public static function set_error_message(string $rule, string $message)
799
    {
800
        self::$validation_methods_errors[$rule] = $message;
4✔
801
    }
4✔
802

803
    /**
804
     * Set custom error messages for validation rules in an array.
805
     *
806
     * @param array $array
807
     */
808
    public static function set_error_messages(array $array)
809
    {
810
        foreach ($array as $rule => $message) {
3✔
811
            self::set_error_message($rule, $message);
3✔
812
        }
813
    }
3✔
814

815
    /**
816
     * Get all error messages.
817
     *
818
     * @return array
819
     */
820
    protected function get_messages()
821
    {
822
        $lang_file = __DIR__.DIRECTORY_SEPARATOR.'lang'.DIRECTORY_SEPARATOR.$this->lang.'.php';
49✔
823
        $messages = include $lang_file;
49✔
824

825
        return array_merge($messages, self::$validation_methods_errors);
49✔
826
    }
827

828
    /**
829
     * Get error message.
830
     *
831
     * @param array $messages
832
     * @param string $field
833
     * @param string $rule
834
     * @return mixed|null
835
     * @throws Exception
836
     */
837
    private function get_error_message(array $messages, string $field, string $rule)
838
    {
839
        $custom_error_message = $this->get_custom_error_message($field, $rule);
48✔
840
        if ($custom_error_message !== null) {
48✔
841
            return $custom_error_message;
4✔
842
        }
843

844
        if (isset($messages[$rule])) {
45✔
845
            return $messages[$rule];
44✔
846
        }
847

848
        throw new Exception(sprintf("'%s' validator does not have an error message.", $rule));
1✔
849
    }
850

851
    /**
852
     * Get custom error message for field and rule.
853
     *
854
     * @param string $field
855
     * @param string $rule
856
     * @return string|null
857
     */
858
    private function get_custom_error_message(string $field, string $rule)
859
    {
860
        $rule_name = str_replace('validate_', '', $rule);
48✔
861

862
        return $this->fields_error_messages[$field][$rule_name] ?? null;
48✔
863
    }
864

865
    /**
866
     * Process error message string.
867
     *
868
     * @param string $field
869
     * @param array $params
870
     * @param string $message
871
     * @param callable|null $transformer
872
     * @return string
873
     */
874
    private function process_error_message($field, array $params, string $message, ?callable $transformer = null)
875
    {
876
        // if field name is explicitly set, use it
877
        if (array_key_exists($field, self::$fields)) {
47✔
878
            $field = self::$fields[$field];
5✔
879
        } else {
880
            $field = ucwords(str_replace(self::$field_chars_to_spaces, chr(32), $field));
43✔
881
        }
882

883
        // if param is a field (i.e. equalsfield validator)
884
        if (isset($params[0]) && array_key_exists($params[0], self::$fields)) {
47✔
885
            $params[0] = self::$fields[$params[0]];
2✔
886
        }
887

888
        $replace = [
889
            '{field}' => $field,
47✔
890
            '{param}' => implode(', ', $params),
47✔
891
        ];
892

893
        foreach ($params as $key => $value) {
47✔
894
            $replace[sprintf('{param[%s]}', $key)] = $value;
8✔
895
        }
896

897
        // for get_readable_errors() <span>
898
        if ($transformer) {
47✔
899
            $replace = $transformer($replace);
37✔
900
        }
901

902
        return strtr($message, $replace);
47✔
903
    }
904

905
    /**
906
     * Process the validation errors and return human readable error messages.
907
     *
908
     * @param bool   $convert_to_string = false
909
     * @param string $field_class
910
     * @param string $error_class
911
     * @return array|string
912
     * @throws Exception if validator doesn't have an error message to set
913
     */
914
    public function get_readable_errors(bool $convert_to_string = false, string $field_class = 'gump-field', string $error_class = 'gump-error-message')
915
    {
916
        if (empty($this->errors)) {
39✔
917
            return $convert_to_string ? '' : [];
2✔
918
        }
919

920
        $messages = $this->get_messages();
37✔
921
        $result = [];
37✔
922

923
        $transformer = static function ($replace) use ($field_class) {
924
            $replace['{field}'] = sprintf('<span class="%s">%s</span>', $field_class, $replace['{field}']);
37✔
925

926
            return $replace;
37✔
927
        };
37✔
928

929
        foreach ($this->errors as $error) {
37✔
930
            $message = $this->get_error_message($messages, $error['field'], $error['rule']);
37✔
931
            $result[] = $this->process_error_message($error['field'], $error['params'], $message, $transformer);
37✔
932
        }
933

934
        if ($convert_to_string) {
37✔
935
            return array_reduce($result, static function ($prev, $next) use ($error_class) {
936
                return sprintf('%s<span class="%s">%s</span>', $prev, $error_class, $next);
2✔
937
            });
2✔
938
        }
939

940
        return $result;
35✔
941
    }
942

943
    /**
944
     * Process the validation errors and return an array of errors with field names as keys.
945
     *
946
     * @return array
947
     * @throws Exception
948
     */
949
    public function get_errors_array()
950
    {
951
        $messages = $this->get_messages();
12✔
952
        $result = [];
12✔
953

954
        foreach ($this->errors as $error) {
12✔
955
            $message = $this->get_error_message($messages, $error['field'], $error['rule']);
11✔
956
            $result[$error['field']] = $this->process_error_message($error['field'], $error['params'], $message);
10✔
957
        }
958

959
        return $result;
11✔
960
    }
961

962
    /**
963
     * Filter the input data according to the specified filter set.
964
     *
965
     * @param mixed  $input
966
     * @param array  $filterset
967
     * @return mixed
968
     * @throws Exception
969
     */
970
    public function filter(array $input, array $filterset)
971
    {
972
        foreach ($filterset as $field => $filters) {
141✔
973
            if (!array_key_exists($field, $input)) {
86✔
974
                continue;
1✔
975
            }
976

977
            $filters = $this->parse_rules($filters);
86✔
978

979
            foreach ($filters as $filter) {
86✔
980
                $parsed_rule = $this->parse_rule($filter);
86✔
981

982
                if (is_array($input[$field])) {
86✔
983
                    $input_array = &$input[$field];
2✔
984
                } else {
985
                    $input_array = [&$input[$field]];
86✔
986
                }
987

988
                foreach ($input_array as &$value) {
86✔
989
                    $value = $this->call_filter($parsed_rule['rule'], $value, $parsed_rule['param']);
86✔
990
                }
991

992
                unset($input_array, $value);
85✔
993
            }
994
        }
995

996
        return $input;
140✔
997
    }
998

999
    // ** ------------------------- Filters --------------------------------------- ** //
1000

1001
    /**
1002
     * Replace noise words in a string (http://tax.cchgroup.com/help/Avoiding_noise_words_in_your_search.htm).
1003
     *
1004
     * @param string $value
1005
     * @param array  $params
1006
     *
1007
     * @return string
1008
     */
1009
    protected function filter_noise_words($value, array $params = [])
1010
    {
1011
        $value = preg_replace('/\s\s+/u', chr(32), $value);
2✔
1012

1013
        $value = " $value ";
2✔
1014

1015
        $words = explode(',', self::$en_noise_words);
2✔
1016

1017
        foreach ($words as $word) {
2✔
1018
            $word = trim($word);
2✔
1019

1020
            $word = " $word "; // Normalize
2✔
1021

1022
            if (stripos($value, $word) !== false) {
2✔
1023
                $value = str_ireplace($word, chr(32), $value);
2✔
1024
            }
1025
        }
1026

1027
        return trim($value);
2✔
1028
    }
1029

1030
    /**
1031
     * Remove all known punctuation from a string.
1032
     *
1033
     * @param string $value
1034
     * @param array  $params
1035
     *
1036
     * @return string
1037
     */
1038
    protected function filter_rmpunctuation($value, array $params = [])
1039
    {
1040
        return preg_replace("/(?![.=$'€%-])\p{P}/u", '', $value);
1✔
1041
    }
1042

1043
    /**
1044
     * Sanitize the string by urlencoding characters.
1045
     *
1046
     * @param string $value
1047
     * @param array  $params
1048
     *
1049
     * @return string
1050
     */
1051
    protected function filter_urlencode($value, array $params = [])
1052
    {
1053
        return filter_var($value, FILTER_SANITIZE_ENCODED);
1✔
1054
    }
1055

1056
    /**
1057
     * Sanitize the string by converting HTML characters to their HTML entities.
1058
     *
1059
     * @param string $value
1060
     * @param array  $params
1061
     *
1062
     * @return string
1063
     */
1064
    protected function filter_htmlencode($value, array $params = [])
1065
    {
1066
        return filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
1✔
1067
    }
1068

1069
    /**
1070
     * Sanitize the string by removing illegal characters from emails.
1071
     *
1072
     * @param string $value
1073
     * @param array  $params
1074
     *
1075
     * @return string
1076
     */
1077
    protected function filter_sanitize_email($value, array $params = [])
1078
    {
1079
        return filter_var($value, FILTER_SANITIZE_EMAIL);
2✔
1080
    }
1081

1082
    /**
1083
     * Sanitize the string by removing illegal characters from numbers.
1084
     *
1085
     * @param string $value
1086
     * @param array  $params
1087
     *
1088
     * @return string
1089
     */
1090
    protected function filter_sanitize_numbers($value, array $params = [])
1091
    {
1092
        return filter_var($value, FILTER_SANITIZE_NUMBER_INT);
5✔
1093
    }
1094

1095
    /**
1096
     * Sanitize the string by removing illegal characters from float numbers.
1097
     *
1098
     * @param string $value
1099
     * @param array  $params
1100
     *
1101
     * @return string
1102
     */
1103
    protected function filter_sanitize_floats($value, array $params = [])
1104
    {
1105
        return filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
4✔
1106
    }
1107

1108
    /**
1109
     * Sanitize the string by removing any script tags.
1110
     *
1111
     * @param string $value
1112
     * @param array  $params
1113
     *
1114
     * @return string
1115
     */
1116
    protected function filter_sanitize_string($value, array $params = [])
1117
    {
1118
        return self::polyfill_filter_var_string($value);
7✔
1119
    }
1120

1121
    /**
1122
     * Implemented to replace FILTER_SANITIZE_STRING behaviour deprecated in php8.1
1123
     *
1124
     * @param mixed $value
1125
     * @return string
1126
     */
1127
    private static function polyfill_filter_var_string($value)
1128
    {
1129
        $str = preg_replace('/\x00|<[^>]*>?/', '', $value);
10✔
1130

1131
        return (string)str_replace(["'", '"'], ['&#39;', '&#34;'], $str);
10✔
1132
    }
1133

1134
    /**
1135
     * Converts ['1', 1, 'true', true, 'yes', 'on'] to true, anything else is false ('on' is useful for form checkboxes).
1136
     *
1137
     * @param mixed $value
1138
     * @param array $params
1139
     *
1140
     * @return bool
1141
     */
1142
    protected function filter_boolean($value, array $params = [])
1143
    {
1144
        if (in_array($value, self::$trues, true)) {
11✔
1145
            return true;
7✔
1146
        }
1147

1148
        return false;
4✔
1149
    }
1150

1151
    /**
1152
     * Filter out all HTML tags except the defined basic tags.
1153
     *
1154
     * @param string $value
1155
     * @param array  $params
1156
     *
1157
     * @return string
1158
     */
1159
    protected function filter_basic_tags($value, array $params = [])
1160
    {
1161
        return strip_tags($value, self::$basic_tags);
1✔
1162
    }
1163

1164
    /**
1165
     * Convert the provided numeric value to a whole number.
1166
     *
1167
     * @param string $value
1168
     * @param array  $params
1169
     *
1170
     * @return string
1171
     */
1172
    protected function filter_whole_number($value, array $params = [])
1173
    {
1174
        return intval($value);
4✔
1175
    }
1176

1177
    /**
1178
     * Convert MS Word special characters to web safe characters. ([“ ”] => ", [‘ ’] => ', [–] => -, […] => ...)
1179
     *
1180
     * @param string $value
1181
     * @param array  $params
1182
     *
1183
     * @return string
1184
     */
1185
    protected function filter_ms_word_characters($value, array $params = [])
1186
    {
1187
        return str_replace(['“', '”', '‘', '’', '–', '…'], ['"', '"', "'", "'", '-', '...'], $value);
4✔
1188
    }
1189

1190
    /**
1191
     * Converts to lowercase.
1192
     *
1193
     * @param string $value
1194
     * @param array  $params
1195
     *
1196
     * @return string
1197
     */
1198
    protected function filter_lower_case($value, array $params = [])
1199
    {
1200
        return mb_strtolower($value);
2✔
1201
    }
1202

1203
    /**
1204
     * Converts to uppercase.
1205
     *
1206
     * @param string $value
1207
     * @param array  $params
1208
     *
1209
     * @return string
1210
     */
1211
    protected function filter_upper_case($value, array $params = [])
1212
    {
1213
        return mb_strtoupper($value);
35✔
1214
    }
1215

1216
    /**
1217
     * Converts value to url-web-slugs.
1218
     *
1219
     * @see https://stackoverflow.com/questions/40641973/php-to-convert-string-to-slug
1220
     * @see http://cubiq.org/the-perfect-php-clean-url-generator
1221
     *
1222
     * @param string $value
1223
     * @param array  $params
1224
     *
1225
     * @return string
1226
     */
1227
    protected function filter_slug($value, array $params = [])
1228
    {
1229
        $delimiter = '-';
3✔
1230

1231
        return mb_strtolower(trim(preg_replace('/[\s-]+/', $delimiter, preg_replace('/[^A-Za-z0-9-]+/', $delimiter, preg_replace('/[&]/', 'and', preg_replace('/[\']/', '', iconv('UTF-8', 'ASCII//TRANSLIT', $value))))), $delimiter));
3✔
1232
    }
1233

1234
    /**
1235
     * Remove spaces from the beginning and end of strings.
1236
     *
1237
     * @param string $value
1238
     * @param array  $params
1239
     *
1240
     * @return string
1241
     */
1242
    protected function filter_trim($value, array $params = [])
1243
    {
1244
        return trim($value);
7✔
1245
    }
1246

1247
    // ** ------------------------- Validators ------------------------------------ ** //
1248

1249
    /**
1250
     * Ensures the specified key value exists and is not empty (not null, not empty string, not empty array).
1251
     *
1252
     * @param string $field
1253
     * @param array $input
1254
     * @param array $params
1255
     * @param mixed $value
1256
     *
1257
     * @return bool
1258
     */
1259
    protected function validate_required($field, array $input, array $params = [], $value = null)
1260
    {
1261
        return isset($value) && !self::is_empty($value);
40✔
1262
    }
1263

1264
    /**
1265
     * Verify that a value is contained within the pre-defined value set.
1266
     *
1267
     * @example_parameter one;two;use array format if one of the values contains semicolons
1268
     *
1269
     * @param string $field
1270
     * @param array  $input
1271
     * @param array $params
1272
     *
1273
     * @return bool
1274
     */
1275
    protected function validate_contains($field, array $input, array $params = [], $value = null)
1276
    {
1277
        $value = mb_strtolower(trim($input[$field]));
14✔
1278

1279
        $params = array_map(static function ($value) {
1280
            return mb_strtolower(trim($value));
14✔
1281
        }, $params);
14✔
1282

1283
        return in_array($value, $params, true);
14✔
1284
    }
1285

1286
    /**
1287
     * Verify that a value is contained within the pre-defined value set. Error message will NOT show the list of possible values.
1288
     *
1289
     * @example_parameter value1;value2
1290
     *
1291
     * @param string $field
1292
     * @param array $input
1293
     * @param array $params
1294
     * @param mixed $value
1295
     *
1296
     * @return bool
1297
     */
1298
    protected function validate_contains_list($field, array $input, array $params = [], $value = null)
1299
    {
1300
        return $this->validate_contains($field, $input, $params);
3✔
1301
    }
1302

1303
    /**
1304
     * Verify that a value is contained within the pre-defined value set. Error message will NOT show the list of possible values.
1305
     *
1306
     * @example_parameter value1;value2
1307
     *
1308
     * @param string $field
1309
     * @param array $input
1310
     * @param array $params
1311
     * @param mixed $value
1312
     *
1313
     * @return bool
1314
     */
1315
    protected function validate_doesnt_contain_list($field, array $input, array $params = [], $value = null)
1316
    {
1317
        return !$this->validate_contains($field, $input, $params);
2✔
1318
    }
1319

1320
    /**
1321
     * Determine if the provided value is a valid boolean. Returns true for: yes/no, on/off, 1/0, true/false. In strict mode (optional) only true/false will be valid which you can combine with boolean filter.
1322
     *
1323
     * @example_parameter strict
1324
     *
1325
     * @param string $field
1326
     * @param array $input
1327
     * @param array $params
1328
     * @param mixed $value
1329
     *
1330
     * @return bool
1331
     */
1332
    protected function validate_boolean($field, array $input, array $params = [], $value = null)
1333
    {
1334
        if (isset($params[0]) && $params[0] === 'strict') {
29✔
1335
            return in_array($input[$field], [true, false], true);
9✔
1336
        }
1337

1338
        $booleans = [];
20✔
1339
        foreach (self::$trues as $true) {
20✔
1340
            $booleans[] = $true;
20✔
1341
        }
1342
        foreach (self::$falses as $false) {
20✔
1343
            $booleans[] = $false;
20✔
1344
        }
1345

1346
        return in_array($input[$field], $booleans, true);
20✔
1347
    }
1348

1349
    /**
1350
     * Determine if the provided email has valid format.
1351
     *
1352
     * @param string $field
1353
     * @param array $input
1354
     * @param array $params
1355
     * @param mixed $value individual value (in case of array)
1356
     *
1357
     * @return bool
1358
     */
1359
    protected function validate_valid_email($field, array $input, array $params = [], $value = null)
1360
    {
1361
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
8✔
1362
    }
1363

1364
    /**
1365
     * Determine if the provided value length is less or equal to a specific value.
1366
     *
1367
     * @example_parameter 240
1368
     *
1369
     * @param string $field
1370
     * @param array $input
1371
     * @param array $params
1372
     * @param mixed $value
1373
     *
1374
     * @return bool
1375
     */
1376
    protected function validate_max_len($field, array $input, array $params = [], $value = null)
1377
    {
1378
        return mb_strlen($value) <= (int)$params[0];
27✔
1379
    }
1380

1381
    /**
1382
     * Determine if the provided value length is more or equal to a specific value.
1383
     *
1384
     * @example_parameter 4
1385
     *
1386
     * @param string $field
1387
     * @param array $input
1388
     * @param array $params
1389
     * @param mixed $value
1390
     *
1391
     * @return bool
1392
     */
1393
    protected function validate_min_len($field, array $input, array $params = [], $value = null)
1394
    {
1395
        return mb_strlen($value) >= (int)$params[0];
31✔
1396
    }
1397

1398
    /**
1399
     * Determine if the provided value length matches a specific value.
1400
     *
1401
     * @example_parameter 5
1402
     *
1403
     * @param string $field
1404
     * @param array  $input
1405
     * @param array  $params
1406
     * @param mixed  $value
1407
     *
1408
     * @return bool
1409
     */
1410
    protected function validate_exact_len($field, array $input, array $params = [], $value = null)
1411
    {
1412
        return mb_strlen($value) == (int)$params[0];
6✔
1413
    }
1414

1415
    /**
1416
     * Determine if the provided value length is between min and max values.
1417
     *
1418
     * @example_parameter 3;11
1419
     *
1420
     * @param string $field
1421
     * @param array $input
1422
     * @param array $params
1423
     * @param mixed $value
1424
     *
1425
     * @return bool
1426
     */
1427
    protected function validate_between_len($field, array $input, array $params = [], $value = null)
1428
    {
1429
        return $this->validate_min_len($field, $input, [$params[0]], $value)
24✔
1430
            && $this->validate_max_len($field, $input, [$params[1]], $value);
24✔
1431
    }
1432

1433
    /**
1434
     * Determine if the provided value contains only alpha characters.
1435
     *
1436
     * @param string $field
1437
     * @param array  $input
1438
     * @param array  $params
1439
     * @param mixed  $value
1440
     * @return bool
1441
     */
1442
    protected function validate_alpha($field, array $input, array $params = [], $value = null)
1443
    {
1444
        return preg_match('/^(['.self::$alpha_regex.'])+$/i', $value) > 0;
14✔
1445
    }
1446

1447
    /**
1448
     * Determine if the provided value contains only alpha-numeric characters.
1449
     *
1450
     * @param string $field
1451
     * @param array  $input
1452
     * @param array  $params
1453
     *
1454
     * @return bool
1455
     */
1456
    protected function validate_alpha_numeric($field, array $input, array $params = [], $value = null)
1457
    {
1458
        return preg_match('/^(['.self::$alpha_regex.'0-9])+$/i', $value) > 0;
2✔
1459
    }
1460

1461
    /**
1462
     * Determine if the provided value contains only alpha characters with dashed and underscores.
1463
     *
1464
     * @param string $field
1465
     * @param array  $input
1466
     * @param array  $params
1467
     *
1468
     * @return bool
1469
     */
1470
    protected function validate_alpha_dash($field, array $input, array $params = [], $value = null)
1471
    {
1472
        return preg_match('/^(['.self::$alpha_regex.'_-])+$/i', $value) > 0;
3✔
1473
    }
1474

1475
    /**
1476
     * Determine if the provided value contains only alpha numeric characters with dashed and underscores.
1477
     *
1478
     * @param string $field
1479
     * @param array  $input
1480
     * @param array  $params
1481
     *
1482
     * @return bool
1483
     */
1484
    protected function validate_alpha_numeric_dash($field, array $input, array $params = [], $value = null)
1485
    {
1486
        return preg_match('/^(['.self::$alpha_regex.'0-9_-])+$/i', $value) > 0;
5✔
1487
    }
1488

1489
    /**
1490
     * Determine if the provided value contains only alpha numeric characters with spaces.
1491
     *
1492
     * @param string $field
1493
     * @param array  $input
1494
     * @param array  $params
1495
     *
1496
     * @return bool
1497
     */
1498
    protected function validate_alpha_numeric_space($field, array $input, array $params = [], $value = null)
1499
    {
1500
        return preg_match('/^(['.self::$alpha_regex.'\s0-9])+$/i', $value) > 0;
2✔
1501
    }
1502

1503
    /**
1504
     * Determine if the provided value contains only alpha characters with spaces.
1505
     *
1506
     * @param string $field
1507
     * @param array  $input
1508
     * @param array  $params
1509
     *
1510
     * @return bool
1511
     */
1512
    protected function validate_alpha_space($field, array $input, array $params = [], $value = null)
1513
    {
1514
        return preg_match('/^(['.self::$alpha_regex.'\s])+$/i', $value) > 0;
5✔
1515
    }
1516

1517
    /**
1518
     * Determine if the provided value is a valid number or numeric string.
1519
     *
1520
     * @param string $field
1521
     * @param array  $input
1522
     * @param array  $params
1523
     *
1524
     * @return bool
1525
     */
1526
    protected function validate_numeric($field, array $input, array $params = [], $value = null)
1527
    {
1528
        return is_numeric($value);
23✔
1529
    }
1530

1531
    /**
1532
     * Determine if the provided value is a valid integer.
1533
     *
1534
     * @param string $field
1535
     * @param array  $input
1536
     * @param array  $params
1537
     *
1538
     * @return bool
1539
     */
1540
    protected function validate_integer($field, array $input, array $params = [], $value = null)
1541
    {
1542
        return !(filter_var($value, FILTER_VALIDATE_INT) === false || is_bool($value) || is_null($value));
14✔
1543
    }
1544

1545
    /**
1546
     * Determine if the provided value is a valid float.
1547
     *
1548
     * @param string $field
1549
     * @param array  $input
1550
     * @param array  $params
1551
     *
1552
     * @return bool
1553
     */
1554
    protected function validate_float($field, array $input, array $params = [], $value = null)
1555
    {
1556
        return filter_var($value, FILTER_VALIDATE_FLOAT) !== false;
11✔
1557
    }
1558

1559
    /**
1560
     * Determine if the provided value is a valid URL.
1561
     *
1562
     * @param string $field
1563
     * @param array  $input
1564
     * @param array  $params
1565
     *
1566
     * @return bool
1567
     */
1568
    protected function validate_valid_url($field, array $input, array $params = [], $value = null)
1569
    {
1570
        return filter_var($value, FILTER_VALIDATE_URL) !== false;
8✔
1571
    }
1572

1573
    /**
1574
     * Determine if a URL exists & is accessible.
1575
     *
1576
     * @param string $field
1577
     * @param array  $input
1578
     * @param array  $params
1579
     *
1580
     * @return bool
1581
     */
1582
    protected function validate_url_exists($field, array $input, array $params = [], $value = null)
1583
    {
1584
        $url = parse_url(mb_strtolower($value));
2✔
1585

1586
        if (isset($url['host'])) {
2✔
1587
            $url = $url['host'];
2✔
1588
        }
1589

1590
        return EnvHelpers::checkdnsrr(idn_to_ascii($url, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46), 'A') !== false;
2✔
1591
    }
1592

1593
    /**
1594
     * Determine if the provided value is a valid IP address.
1595
     *
1596
     * @param string $field
1597
     * @param array $input
1598
     * @param array $params
1599
     * @param mixed $value
1600
     *
1601
     * @return bool
1602
     */
1603
    protected function validate_valid_ip($field, array $input, array $params = [], $value = null)
1604
    {
1605
        return filter_var($value, FILTER_VALIDATE_IP) !== false;
6✔
1606
    }
1607

1608
    /**
1609
     * Determine if the provided value is a valid IPv4 address.
1610
     *
1611
     * @see What about private networks? What about loop-back address? 127.0.0.1 http://en.wikipedia.org/wiki/Private_network
1612
     *
1613
     * @param string $field
1614
     * @param array $input
1615
     * @param array $params
1616
     * @param mixed $value
1617
     *
1618
     * @return bool
1619
     */
1620
    protected function validate_valid_ipv4($field, array $input, array $params = [], $value = null)
1621
    {
1622
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
6✔
1623
    }
1624

1625
    /**
1626
     * Determine if the provided value is a valid IPv6 address.
1627
     *
1628
     * @param string $field
1629
     * @param array $input
1630
     * @param array $params
1631
     * @param mixed $value
1632
     *
1633
     * @return bool
1634
     */
1635
    protected function validate_valid_ipv6($field, array $input, array $params = [], $value = null)
1636
    {
1637
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
4✔
1638
    }
1639

1640
    /**
1641
     * Determine if the input is a valid credit card number.
1642
     *
1643
     * @see http://stackoverflow.com/questions/174730/what-is-the-best-way-to-validate-a-credit-card-in-php
1644
     *
1645
     * @param string $field
1646
     * @param array $input
1647
     * @param array $params
1648
     * @param mixed $value
1649
     *
1650
     * @return bool
1651
     */
1652
    protected function validate_valid_cc($field, array $input, array $params = [], $value = null)
1653
    {
1654
        $number = preg_replace('/\D/', '', $value);
5✔
1655

1656
        $number_length = mb_strlen($number);
5✔
1657

1658
        /**
1659
         * Bail out if $number_length is 0.
1660
         * This can be the case if a user has entered only alphabets
1661
         *
1662
         * @since 1.5
1663
         */
1664
        if ($number_length == 0) {
5✔
1665
            return false;
1✔
1666
        }
1667

1668
        $parity = $number_length % 2;
4✔
1669

1670
        $total = 0;
4✔
1671

1672
        for ($i = 0; $i < $number_length; ++$i) {
4✔
1673
            $digit = $number[$i];
4✔
1674

1675
            if ($i % 2 == $parity) {
4✔
1676
                $digit *= 2;
4✔
1677

1678
                if ($digit > 9) {
4✔
1679
                    $digit -= 9;
2✔
1680
                }
1681
            }
1682

1683
            $total += $digit;
4✔
1684
        }
1685

1686
        return $total % 10 == 0;
4✔
1687
    }
1688

1689
    /**
1690
     * Determine if the input is a valid human name.
1691
     *
1692
     * @see https://github.com/Wixel/GUMP/issues/5
1693
     *
1694
     * @param string $field
1695
     * @param array $input
1696
     * @param array $params
1697
     * @param mixed $value
1698
     *
1699
     * @return bool
1700
     */
1701
    protected function validate_valid_name($field, array $input, array $params = [], $value = null)
1702
    {
1703
        return preg_match("/^([a-z \p{L} '-])+$/i", $value) > 0;
6✔
1704
    }
1705

1706
    /**
1707
     * Determine if the provided input is likely to be a street address using weak detection.
1708
     *
1709
     * @param string $field
1710
     * @param array $input
1711
     * @param array $params
1712
     * @param mixed $value
1713
     *
1714
     * @return bool
1715
     */
1716
    protected function validate_street_address($field, array $input, array $params = [], $value = null)
1717
    {
1718
        // Theory: 1 number, 1 or more spaces, 1 or more words
1719
        $has_letter = preg_match('/[a-zA-Z]/', $value);
7✔
1720
        $has_digit = preg_match('/\d/', $value);
7✔
1721
        $has_space = preg_match('/\s/', $value);
7✔
1722

1723
        return $has_letter && $has_digit && $has_space;
7✔
1724
    }
1725

1726
    /**
1727
     * Determine if the provided value is a valid IBAN.
1728
     *
1729
     * @param string $field
1730
     * @param array $input
1731
     * @param array $params
1732
     * @param mixed $value
1733
     *
1734
     * @return bool
1735
     */
1736
    protected function validate_iban($field, array $input, array $params = [], $value = null)
1737
    {
1738
        $character = [
1739
            'A' => 10, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16,
5✔
1740
            'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22,
1741
            'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28,
1742
            'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34,
1743
            'Z' => 35, 'B' => 11,
1744
        ];
1745

1746
        if (!preg_match("/\A[A-Z]{2}\d{2} ?[A-Z\d]{4}( ?\d{4}){1,} ?\d{1,4}\z/", $value)) {
5✔
1747
            return false;
2✔
1748
        }
1749

1750
        $iban = str_replace(' ', '', $value);
3✔
1751
        $iban = substr($iban, 4).substr($iban, 0, 4);
3✔
1752
        $iban = strtr($iban, $character);
3✔
1753

1754
        return bcmod($iban, 97) == 1;
3✔
1755
    }
1756

1757
    /**
1758
     * Determine if the provided input is a valid date (ISO 8601) or specify a custom format (optional).
1759
     *
1760
     * @example_parameter d/m/Y
1761
     *
1762
     * @param string $field
1763
     * @param array $input
1764
     * @param array $params
1765
     * @param mixed $value
1766
     *
1767
     * @return bool
1768
     */
1769
    protected function validate_date($field, array $input, array $params = [], $value = null)
1770
    {
1771
        // Default
1772
        if (count($params) === 0) {
10✔
1773
            $cdate1 = date('Y-m-d', strtotime($value));
6✔
1774
            $cdate2 = date('Y-m-d H:i:s', strtotime($value));
6✔
1775

1776
            return !($cdate1 != $value && $cdate2 != $value);
6✔
1777
        }
1778

1779
        $date = \DateTime::createFromFormat($params[0], $value);
4✔
1780

1781
        return !($date === false || $value != date($params[0], $date->getTimestamp()));
4✔
1782
    }
1783

1784
    /**
1785
     * Determine if the provided input meets age requirement (ISO 8601). Input should be a date (Y-m-d).
1786
     *
1787
     * @example_parameter 18
1788
     *
1789
     * @param string $field
1790
     * @param array $input
1791
     * @param array $params
1792
     * @param mixed $value
1793
     *
1794
     * @return bool
1795
     * @throws Exception
1796
     */
1797
    protected function validate_min_age($field, array $input, array $params = [], $value = null)
1798
    {
1799
        $inputDatetime = new DateTime(EnvHelpers::date('Y-m-d', strtotime($value)));
3✔
1800
        $todayDatetime = new DateTime(EnvHelpers::date('Y-m-d'));
3✔
1801

1802
        $interval = $todayDatetime->diff($inputDatetime);
3✔
1803
        $yearsPassed = $interval->y;
3✔
1804

1805
        return $yearsPassed >= $params[0];
3✔
1806
    }
1807

1808
    /**
1809
     * Determine if the provided numeric value is lower or equal to a specific value.
1810
     *
1811
     * @example_parameter 50
1812
     *
1813
     * @param string $field
1814
     * @param array  $input
1815
     * @param array  $params
1816
     * @param mixed  $value
1817
     * @return bool
1818
     */
1819
    protected function validate_max_numeric($field, array $input, array $params = [], $value = null)
1820
    {
1821
        return is_numeric($value) && is_numeric($params[0]) && ($value <= $params[0]);
5✔
1822
    }
1823

1824
    /**
1825
     * Determine if the provided numeric value is higher or equal to a specific value.
1826
     *
1827
     * @example_parameter 1
1828
     *
1829
     * @param string $field
1830
     * @param array  $input
1831
     * @param array  $params
1832
     * @param mixed  $value
1833
     * @return bool
1834
     */
1835
    protected function validate_min_numeric($field, array $input, array $params = [], $value = null)
1836
    {
1837
        return is_numeric($value) && is_numeric($params[0]) && ($value >= $params[0]);
8✔
1838
    }
1839

1840
    /**
1841
     * Determine if the provided value starts with param.
1842
     *
1843
     * @example_parameter Z
1844
     *
1845
     * @param string $field
1846
     * @param array $input
1847
     * @param array $params
1848
     * @param mixed $value
1849
     * @return bool
1850
     */
1851
    protected function validate_starts($field, array $input, array $params = [], $value = null)
1852
    {
1853
        return strpos($value, $params[0]) === 0;
4✔
1854
    }
1855

1856
    /**
1857
     * Determine if the file was successfully uploaded.
1858
     *
1859
     * @param string $field
1860
     * @param array $input
1861
     * @param array $params
1862
     * @param mixed $value
1863
     *
1864
     * @return bool
1865
     */
1866
    protected function validate_required_file($field, array $input, array $params = [], $value = null)
1867
    {
1868
        return isset($input[$field]) && is_array($input[$field]) && $input[$field]['error'] === 0;
5✔
1869
    }
1870

1871
    /**
1872
     * Check the uploaded file for extension. Doesn't check mime-type yet.
1873
     *
1874
     * @example_parameter png;jpg;gif
1875
     *
1876
     * @param string $field
1877
     * @param array $input
1878
     * @param array $params
1879
     * @param mixed $value
1880
     *
1881
     * @return bool
1882
     */
1883
    protected function validate_extension($field, array $input, array $params = [], $value = null)
1884
    {
1885
        if (!is_array($input[$field])) {
5✔
1886
            return false;
×
1887
        }
1888

1889
        // file is not required (empty upload)
1890
        if ($input[$field]['error'] === 4 && $input[$field]['size'] === 0 && $input[$field]['name'] === '') {
5✔
1891
            return true;
1✔
1892
        }
1893

1894
        // when successfully uploaded we proceed to verify the extension
1895
        if ($input[$field]['error'] === 0) {
4✔
1896
            $params = array_map(function ($v) {
1897
                return trim(mb_strtolower($v));
3✔
1898
            }, $params);
3✔
1899

1900
            $path_info = pathinfo($input[$field]['name']);
3✔
1901
            $extension = $path_info['extension'] ?? null;
3✔
1902

1903
            return $extension && in_array(mb_strtolower($extension), $params, true);
3✔
1904
        }
1905

1906
        return false;
1✔
1907
    }
1908

1909
    /**
1910
     * Determine if the provided field value equals current field value.
1911
     *
1912
     * @example_parameter other_field_name
1913
     *
1914
     * @param string $field
1915
     * @param array $input
1916
     * @param array $params
1917
     * @param mixed $value
1918
     *
1919
     * @return bool
1920
     */
1921
    protected function validate_equalsfield($field, array $input, array $params = [], $value = null)
1922
    {
1923
        return $input[$field] == $input[$params[0]];
5✔
1924
    }
1925

1926
    /**
1927
     * Determine if the provided field value is a valid GUID (v4)
1928
     *
1929
     * @param string $field
1930
     * @param array $input
1931
     * @param array $params
1932
     * @param mixed $value
1933
     *
1934
     * @return bool
1935
     */
1936
    protected function validate_guidv4($field, array $input, array $params = [], $value = null)
1937
    {
1938
        return preg_match("/\{?[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}\}?$/", $value) > 0;
4✔
1939
    }
1940

1941
    /**
1942
     * Determine if the provided value is a valid phone number.
1943
     *
1944
     * @example_value 5555425555
1945
     * @example_value 555-555-5555
1946
     * @example_value 1(519) 555-4444
1947
     * @example_value 1-555-555-5555
1948
     * @example_value 1-(555)-555-5555
1949
     *
1950
     * @param string $field
1951
     * @param array $input
1952
     * @param array $params
1953
     * @param mixed $value
1954
     *
1955
     * @return bool
1956
     */
1957
    protected function validate_phone_number($field, array $input, array $params = [], $value = null)
1958
    {
1959
        $regex = '/^(\d[\s-]?)?[\(\[\s-]{0,2}?\d{3}[\)\]\s-]{0,2}?\d{3}[\s-]?\d{4}$/i';
11✔
1960

1961
        return preg_match($regex, $value) > 0;
11✔
1962
    }
1963

1964
    /**
1965
     * Custom regex validator.
1966
     *
1967
     * @example_parameter /test-[0-9]{3}/
1968
     * @example_value     test-123
1969
     *
1970
     * @param string $field
1971
     * @param array $input
1972
     * @param array $params
1973
     * @param mixed $value
1974
     *
1975
     * @return bool
1976
     */
1977
    protected function validate_regex($field, array $input, array $params = [], $value = null)
1978
    {
1979
        return preg_match($params[0], $value) > 0;
6✔
1980
    }
1981

1982
    /**
1983
     * Determine if the provided value is a valid JSON string.
1984
     *
1985
     * @example_value {"test": true}
1986
     *
1987
     * @param string $field
1988
     * @param array $input
1989
     * @param array $params
1990
     * @param mixed $value
1991
     *
1992
     * @return bool
1993
     */
1994
    protected function validate_valid_json_string($field, array $input, array $params = [], $value = null)
1995
    {
1996
        return is_string($input[$field])
6✔
1997
            && is_array(json_decode($value, true))
6✔
1998
            && (json_last_error() == JSON_ERROR_NONE);
6✔
1999
    }
2000

2001
    /**
2002
     * Check if an input is an array and if the size is more or equal to a specific value.
2003
     *
2004
     * @example_parameter 1
2005
     *
2006
     * @param string $field
2007
     * @param array $input
2008
     * @param array $params
2009
     * @param mixed $value
2010
     *
2011
     * @return bool
2012
     */
2013
    protected function validate_valid_array_size_greater($field, array $input, array $params = [], $value = null)
2014
    {
2015
        if (!is_array($input[$field]) || count($input[$field]) < $params[0]) {
4✔
2016
            return false;
1✔
2017
        }
2018

2019
        return true;
3✔
2020
    }
2021

2022
    /**
2023
     * Check if an input is an array and if the size is less or equal to a specific value.
2024
     *
2025
     * @example_parameter 1
2026
     *
2027
     * @param string $field
2028
     * @param array $input
2029
     * @param array $params
2030
     * @param mixed $value
2031
     *
2032
     * @return bool
2033
     */
2034
    protected function validate_valid_array_size_lesser($field, array $input, array $params = [], $value = null)
2035
    {
2036
        if (!is_array($input[$field]) || count($input[$field]) > $params[0]) {
4✔
2037
            return false;
1✔
2038
        }
2039

2040
        return true;
3✔
2041
    }
2042

2043
    /**
2044
     * Check if an input is an array and if the size is equal to a specific value.
2045
     *
2046
     * @example_parameter 1
2047
     *
2048
     * @param string $field
2049
     * @param array $input
2050
     * @param array $params
2051
     * @param mixed $value
2052
     *
2053
     * @return bool
2054
     */
2055
    protected function validate_valid_array_size_equal($field, array $input, array $params = [], $value = null)
2056
    {
2057
        return !(!is_array($input[$field]) || count($input[$field]) != $params[0]);
3✔
2058
    }
2059

2060
    // ** ------------------------- Security Validators --------------------------- ** //
2061

2062
    /**
2063
     * Validate strong password with uppercase, lowercase, number and special character.
2064
     *
2065
     * @param string $field
2066
     * @param array $input
2067
     * @param array $params
2068
     * @param mixed $value
2069
     *
2070
     * @return bool
2071
     */
2072
    protected function validate_strong_password($field, array $input, array $params = [], $value = null)
2073
    {
2074
        // At least 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char
2075
        return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $value) > 0;
6✔
2076
    }
2077

2078
    /**
2079
     * Validate JWT token format.
2080
     *
2081
     * @param string $field
2082
     * @param array $input
2083
     * @param array $params
2084
     * @param mixed $value
2085
     *
2086
     * @return bool
2087
     */
2088
    protected function validate_jwt_token($field, array $input, array $params = [], $value = null)
2089
    {
2090
        $parts = explode('.', $value);
4✔
2091
        if (count($parts) !== 3) {
4✔
2092
            return false;
2✔
2093
        }
2094

2095
        foreach ($parts as $part) {
2✔
2096
            if (!preg_match('/^[A-Za-z0-9_-]+$/', $part)) {
2✔
2097
                return false;
2✔
2098
            }
2099
        }
2100

2101
        return true;
1✔
2102
    }
2103

2104
    /**
2105
     * Validate hash format for specified algorithm.
2106
     *
2107
     * @example_parameter md5
2108
     * @example_parameter sha1
2109
     * @example_parameter sha256
2110
     *
2111
     * @param string $field
2112
     * @param array $input
2113
     * @param array $params
2114
     * @param mixed $value
2115
     *
2116
     * @return bool
2117
     */
2118
    protected function validate_hash($field, array $input, array $params = [], $value = null)
2119
    {
2120
        $algorithm = $params[0] ?? 'md5';
×
2121

2122
        $patterns = [
2123
            'md5' => '/^[a-f0-9]{32}$/i',
×
2124
            'sha1' => '/^[a-f0-9]{40}$/i',
2125
            'sha256' => '/^[a-f0-9]{64}$/i',
2126
            'sha512' => '/^[a-f0-9]{128}$/i',
2127
        ];
2128

2129
        return isset($patterns[$algorithm]) && preg_match($patterns[$algorithm], $value) > 0;
×
2130
    }
2131

2132
    /**
2133
     * Detect common SQL injection patterns.
2134
     *
2135
     * @param string $field
2136
     * @param array $input
2137
     * @param array $params
2138
     * @param mixed $value
2139
     *
2140
     * @return bool
2141
     */
2142
    protected function validate_no_sql_injection($field, array $input, array $params = [], $value = null)
2143
    {
2144
        $patterns = [
2145
            '/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/i',
×
2146
            '/(\b(OR|AND)\s+\d+\s*=\s*\d+)/i',
2147
            '/[\'";]/i',
2148
            '/--/i',
2149
            '/\/\*/i',
2150
            '/\*\//i',
2151
        ];
2152

2153
        foreach ($patterns as $pattern) {
×
2154
            if (preg_match($pattern, $value)) {
×
2155
                return false;
×
2156
            }
2157
        }
2158

2159
        return true;
×
2160
    }
2161

2162
    /**
2163
     * Enhanced XSS detection beyond basic sanitize_string.
2164
     *
2165
     * @param string $field
2166
     * @param array $input
2167
     * @param array $params
2168
     * @param mixed $value
2169
     *
2170
     * @return bool
2171
     */
2172
    protected function validate_no_xss($field, array $input, array $params = [], $value = null)
2173
    {
2174
        $patterns = [
2175
            '/<script[^>]*>.*?<\/script>/is',
×
2176
            '/javascript:/i',
2177
            '/on\w+\s*=/i',
2178
            '/<iframe[^>]*>.*?<\/iframe>/is',
2179
            '/<object[^>]*>.*?<\/object>/is',
2180
            '/<embed[^>]*>/i',
2181
            '/expression\s*\(/i',
2182
            '/vbscript:/i',
2183
        ];
2184

2185
        foreach ($patterns as $pattern) {
×
2186
            if (preg_match($pattern, $value)) {
×
2187
                return false;
×
2188
            }
2189
        }
2190

2191
        return true;
×
2192
    }
2193

2194
    // ** ------------------------- Modern Web Validators ------------------------- ** //
2195

2196
    /**
2197
     * Validate UUID format (any version).
2198
     *
2199
     * @param string $field
2200
     * @param array $input
2201
     * @param array $params
2202
     * @param mixed $value
2203
     *
2204
     * @return bool
2205
     */
2206
    protected function validate_uuid($field, array $input, array $params = [], $value = null)
2207
    {
2208
        return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value) > 0;
6✔
2209
    }
2210

2211
    /**
2212
     * Validate base64 encoded data.
2213
     *
2214
     * @param string $field
2215
     * @param array $input
2216
     * @param array $params
2217
     * @param mixed $value
2218
     *
2219
     * @return bool
2220
     */
2221
    protected function validate_base64($field, array $input, array $params = [], $value = null)
2222
    {
2223
        return base64_encode(base64_decode($value, true)) === $value;
×
2224
    }
2225

2226
    /**
2227
     * Validate hexadecimal color code.
2228
     *
2229
     * @param string $field
2230
     * @param array $input
2231
     * @param array $params
2232
     * @param mixed $value
2233
     *
2234
     * @return bool
2235
     */
2236
    protected function validate_hex_color($field, array $input, array $params = [], $value = null)
2237
    {
2238
        return preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $value) > 0;
6✔
2239
    }
2240

2241
    /**
2242
     * Validate RGB color format.
2243
     *
2244
     * @param string $field
2245
     * @param array $input
2246
     * @param array $params
2247
     * @param mixed $value
2248
     *
2249
     * @return bool
2250
     */
2251
    protected function validate_rgb_color($field, array $input, array $params = [], $value = null)
2252
    {
2253
        if (preg_match('/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i', $value, $matches)) {
×
2254
            $r = (int)$matches[1];
×
NEW
2255
            $g = (int)$matches[2];
×
2256
            $b = (int)$matches[3];
×
2257

UNCOV
2258
            return $r >= 0 && $r <= 255 && $g >= 0 && $g <= 255 && $b >= 0 && $b <= 255;
×
2259
        }
2260

UNCOV
2261
        return false;
×
2262
    }
2263

2264
    /**
2265
     * Validate timezone identifier.
2266
     *
2267
     * @param string $field
2268
     * @param array $input
2269
     * @param array $params
2270
     * @param mixed $value
2271
     *
2272
     * @return bool
2273
     */
2274
    protected function validate_timezone($field, array $input, array $params = [], $value = null)
2275
    {
2276
        return in_array($value, timezone_identifiers_list());
×
2277
    }
2278

2279
    /**
2280
     * Validate language code (ISO 639).
2281
     *
2282
     * @param string $field
2283
     * @param array $input
2284
     * @param array $params
2285
     * @param mixed $value
2286
     *
2287
     * @return bool
2288
     */
2289
    protected function validate_language_code($field, array $input, array $params = [], $value = null)
2290
    {
2291
        // ISO 639-1 (2 letter) or 639-1 with country code (en-US)
2292
        return preg_match('/^[a-z]{2}(-[A-Z]{2})?$/', $value) > 0;
×
2293
    }
2294

2295
    /**
2296
     * Validate country code (ISO 3166).
2297
     *
2298
     * @param string $field
2299
     * @param array $input
2300
     * @param array $params
2301
     * @param mixed $value
2302
     *
2303
     * @return bool
2304
     */
2305
    protected function validate_country_code($field, array $input, array $params = [], $value = null)
2306
    {
2307
        return preg_match('/^[A-Z]{2}$/', $value) > 0;
×
2308
    }
2309

2310
    /**
2311
     * Validate currency code (ISO 4217).
2312
     *
2313
     * @param string $field
2314
     * @param array $input
2315
     * @param array $params
2316
     * @param mixed $value
2317
     *
2318
     * @return bool
2319
     */
2320
    protected function validate_currency_code($field, array $input, array $params = [], $value = null)
2321
    {
2322
        $currencies = [
2323
            'USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'CHF', 'CNY', 'SEK', 'NZD',
×
2324
            'MXN', 'SGD', 'HKD', 'NOK', 'TRY', 'ZAR', 'BRL', 'INR', 'KRW', 'RUB',
2325
        ];
2326

UNCOV
2327
        return in_array($value, $currencies);
×
2328
    }
2329

2330
    // ** ------------------------- Network Validators ---------------------------- ** //
2331

2332
    /**
2333
     * Validate MAC address format.
2334
     *
2335
     * @param string $field
2336
     * @param array $input
2337
     * @param array $params
2338
     * @param mixed $value
2339
     *
2340
     * @return bool
2341
     */
2342
    protected function validate_mac_address($field, array $input, array $params = [], $value = null)
2343
    {
2344
        return preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $value) > 0;
6✔
2345
    }
2346

2347
    /**
2348
     * Validate domain name format (without protocol).
2349
     *
2350
     * @param string $field
2351
     * @param array $input
2352
     * @param array $params
2353
     * @param mixed $value
2354
     *
2355
     * @return bool
2356
     */
2357
    protected function validate_domain_name($field, array $input, array $params = [], $value = null)
2358
    {
2359
        return preg_match('/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/', $value) > 0;
×
2360
    }
2361

2362
    /**
2363
     * Validate port number (1-65535).
2364
     *
2365
     * @param string $field
2366
     * @param array $input
2367
     * @param array $params
2368
     * @param mixed $value
2369
     *
2370
     * @return bool
2371
     */
2372
    protected function validate_port_number($field, array $input, array $params = [], $value = null)
2373
    {
2374
        return is_numeric($value) && $value >= 1 && $value <= 65535;
×
2375
    }
2376

2377
    /**
2378
     * Validate social media handle format.
2379
     *
2380
     * @param string $field
2381
     * @param array $input
2382
     * @param array $params
2383
     * @param mixed $value
2384
     *
2385
     * @return bool
2386
     */
2387
    protected function validate_social_handle($field, array $input, array $params = [], $value = null)
2388
    {
2389
        return preg_match('/^@?[A-Za-z0-9_]{1,15}$/', $value) > 0;
×
2390
    }
2391

2392
    // ** ------------------------- Geographic Validators ------------------------- ** //
2393

2394
    /**
2395
     * Validate latitude coordinate (-90 to 90).
2396
     *
2397
     * @param string $field
2398
     * @param array $input
2399
     * @param array $params
2400
     * @param mixed $value
2401
     *
2402
     * @return bool
2403
     */
2404
    protected function validate_latitude($field, array $input, array $params = [], $value = null)
2405
    {
2406
        return is_numeric($value) && $value >= -90 && $value <= 90;
6✔
2407
    }
2408

2409
    /**
2410
     * Validate longitude coordinate (-180 to 180).
2411
     *
2412
     * @param string $field
2413
     * @param array $input
2414
     * @param array $params
2415
     * @param mixed $value
2416
     *
2417
     * @return bool
2418
     */
2419
    protected function validate_longitude($field, array $input, array $params = [], $value = null)
2420
    {
2421
        return is_numeric($value) && $value >= -180 && $value <= 180;
×
2422
    }
2423

2424
    /**
2425
     * Validate postal code for specified country.
2426
     *
2427
     * @example_parameter US
2428
     * @example_parameter CA
2429
     * @example_parameter UK
2430
     *
2431
     * @param string $field
2432
     * @param array $input
2433
     * @param array $params
2434
     * @param mixed $value
2435
     *
2436
     * @return bool
2437
     */
2438
    protected function validate_postal_code($field, array $input, array $params = [], $value = null)
2439
    {
2440
        $country = $params[0] ?? 'US';
×
2441

2442
        $patterns = [
2443
            'US' => '/^\d{5}(-\d{4})?$/',
×
2444
            'CA' => '/^[A-Za-z]\d[A-Za-z] ?\d[A-Za-z]\d$/',
2445
            'UK' => '/^[A-Za-z]{1,2}\d[A-Za-z\d]? ?\d[A-Za-z]{2}$/',
2446
            'DE' => '/^\d{5}$/',
2447
            'FR' => '/^\d{5}$/',
2448
            'AU' => '/^\d{4}$/',
2449
            'JP' => '/^\d{3}-\d{4}$/',
2450
        ];
2451

2452
        return isset($patterns[$country]) && preg_match($patterns[$country], $value) > 0;
×
2453
    }
2454

2455
    /**
2456
     * Validate coordinates in lat,lng format.
2457
     *
2458
     * @param string $field
2459
     * @param array $input
2460
     * @param array $params
2461
     * @param mixed $value
2462
     *
2463
     * @return bool
2464
     */
2465
    protected function validate_coordinates($field, array $input, array $params = [], $value = null)
2466
    {
2467
        if (preg_match('/^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/', $value, $matches)) {
×
2468
            $lat = (float)$matches[1];
×
2469
            $lng = (float)$matches[2];
×
2470

UNCOV
2471
            return $lat >= -90 && $lat <= 90 && $lng >= -180 && $lng <= 180;
×
2472
        }
2473

UNCOV
2474
        return false;
×
2475
    }
2476

2477
    // ** ------------------------- Enhanced Date/Time Validators ---------------- ** //
2478

2479
    /**
2480
     * Validate that date is in the future.
2481
     *
2482
     * @param string $field
2483
     * @param array $input
2484
     * @param array $params
2485
     * @param mixed $value
2486
     *
2487
     * @return bool
2488
     */
2489
    protected function validate_future_date($field, array $input, array $params = [], $value = null)
2490
    {
2491
        $timestamp = strtotime($value);
5✔
2492

2493
        return $timestamp !== false && $timestamp > time();
5✔
2494
    }
2495

2496
    /**
2497
     * Validate that date is in the past.
2498
     *
2499
     * @param string $field
2500
     * @param array $input
2501
     * @param array $params
2502
     * @param mixed $value
2503
     *
2504
     * @return bool
2505
     */
2506
    protected function validate_past_date($field, array $input, array $params = [], $value = null)
2507
    {
2508
        $timestamp = strtotime($value);
×
2509

UNCOV
2510
        return $timestamp !== false && $timestamp < time();
×
2511
    }
2512

2513
    /**
2514
     * Validate that date falls on a business day (Monday-Friday).
2515
     *
2516
     * @param string $field
2517
     * @param array $input
2518
     * @param array $params
2519
     * @param mixed $value
2520
     *
2521
     * @return bool
2522
     */
2523
    protected function validate_business_day($field, array $input, array $params = [], $value = null)
2524
    {
2525
        $timestamp = strtotime($value);
×
2526
        if ($timestamp === false) {
×
2527
            return false;
×
2528
        }
2529
        $dayOfWeek = date('N', $timestamp);
×
2530

UNCOV
2531
        return $dayOfWeek >= 1 && $dayOfWeek <= 5;
×
2532
    }
2533

2534
    /**
2535
     * Validate time format (HH:MM:SS or HH:MM).
2536
     *
2537
     * @param string $field
2538
     * @param array $input
2539
     * @param array $params
2540
     * @param mixed $value
2541
     *
2542
     * @return bool
2543
     */
2544
    protected function validate_valid_time($field, array $input, array $params = [], $value = null)
2545
    {
2546
        return preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/', $value) > 0;
×
2547
    }
2548

2549
    /**
2550
     * Validate date falls within specified range.
2551
     *
2552
     * @example_parameter 2024-01-01;2024-12-31
2553
     *
2554
     * @param string $field
2555
     * @param array $input
2556
     * @param array $params
2557
     * @param mixed $value
2558
     *
2559
     * @return bool
2560
     */
2561
    protected function validate_date_range($field, array $input, array $params = [], $value = null)
2562
    {
2563
        if (count($params) < 2) {
×
2564
            return false;
×
2565
        }
2566

2567
        $timestamp = strtotime($value);
×
2568
        $startTimestamp = strtotime($params[0]);
×
2569
        $endTimestamp = strtotime($params[1]);
×
2570

2571
        if ($timestamp === false || $startTimestamp === false || $endTimestamp === false) {
×
2572
            return false;
×
2573
        }
2574

2575
        return $timestamp >= $startTimestamp && $timestamp <= $endTimestamp;
×
2576
    }
2577

2578
    // ** ------------------------- Mathematical Validators ---------------------- ** //
2579

2580
    /**
2581
     * Validate that number is even.
2582
     *
2583
     * @param string $field
2584
     * @param array $input
2585
     * @param array $params
2586
     * @param mixed $value
2587
     *
2588
     * @return bool
2589
     */
2590
    protected function validate_even($field, array $input, array $params = [], $value = null)
2591
    {
2592
        return is_numeric($value) && (int)$value % 2 === 0;
×
2593
    }
2594

2595
    /**
2596
     * Validate that number is odd.
2597
     *
2598
     * @param string $field
2599
     * @param array $input
2600
     * @param array $params
2601
     * @param mixed $value
2602
     *
2603
     * @return bool
2604
     */
2605
    protected function validate_odd($field, array $input, array $params = [], $value = null)
2606
    {
2607
        return is_numeric($value) && (int)$value % 2 === 1;
×
2608
    }
2609

2610
    /**
2611
     * Validate that number is prime.
2612
     *
2613
     * @param string $field
2614
     * @param array $input
2615
     * @param array $params
2616
     * @param mixed $value
2617
     *
2618
     * @return bool
2619
     */
2620
    protected function validate_prime($field, array $input, array $params = [], $value = null)
2621
    {
2622
        if (!is_numeric($value)) {
5✔
2623
            return false;
1✔
2624
        }
2625

2626
        $num = (int)$value;
4✔
2627
        if ($num < 2) {
4✔
2628
            return false;
3✔
2629
        }
2630
        if ($num === 2) {
2✔
2631
            return true;
1✔
2632
        }
2633
        if ($num % 2 === 0) {
2✔
2634
            return false;
1✔
2635
        }
2636

2637
        for ($i = 3; $i <= sqrt($num); $i += 2) {
2✔
2638
            if ($num % $i === 0) {
2✔
2639
                return false;
1✔
2640
            }
2641
        }
2642

2643
        return true;
1✔
2644
    }
2645

2646
    // ** ------------------------- Content Validators ---------------------------- ** //
2647

2648
    /**
2649
     * Validate word count within specified range.
2650
     *
2651
     * @example_parameter min,10,max,500
2652
     *
2653
     * @param string $field
2654
     * @param array $input
2655
     * @param array $params
2656
     * @param mixed $value
2657
     *
2658
     * @return bool
2659
     */
2660
    protected function validate_word_count($field, array $input, array $params = [], $value = null)
2661
    {
2662
        $wordCount = str_word_count($value);
×
2663

2664
        for ($i = 0; $i < count($params); $i += 2) {
×
2665
            if ($params[$i] === 'min' && isset($params[$i + 1])) {
×
2666
                if ($wordCount < (int)$params[$i + 1]) {
×
2667
                    return false;
×
2668
                }
2669
            }
2670
            if ($params[$i] === 'max' && isset($params[$i + 1])) {
×
2671
                if ($wordCount > (int)$params[$i + 1]) {
×
2672
                    return false;
×
2673
                }
2674
            }
2675
        }
2676

2677
        return true;
×
2678
    }
2679

2680
    /**
2681
     * Validate camelCase format.
2682
     *
2683
     * @param string $field
2684
     * @param array $input
2685
     * @param array $params
2686
     * @param mixed $value
2687
     *
2688
     * @return bool
2689
     */
2690
    protected function validate_camel_case($field, array $input, array $params = [], $value = null)
2691
    {
2692
        return !empty($value) && preg_match('/^[a-z][a-zA-Z0-9]*$/', $value) > 0;
2✔
2693
    }
2694

2695
    /**
2696
     * Validate snake_case format.
2697
     *
2698
     * @param string $field
2699
     * @param array $input
2700
     * @param array $params
2701
     * @param mixed $value
2702
     *
2703
     * @return bool
2704
     */
2705
    protected function validate_snake_case($field, array $input, array $params = [], $value = null)
2706
    {
2707
        return preg_match('/^[a-z][a-z0-9_]*$/', $value) > 0;
×
2708
    }
2709

2710
    /**
2711
     * Validate URL slug format.
2712
     *
2713
     * @param string $field
2714
     * @param array $input
2715
     * @param array $params
2716
     * @param mixed $value
2717
     *
2718
     * @return bool
2719
     */
2720
    protected function validate_url_slug($field, array $input, array $params = [], $value = null)
2721
    {
2722
        return preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $value) > 0;
×
2723
    }
2724
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc