Validation::uuid()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link          https://cakephp.org CakePHP(tm) Project
12
 * @since         1.2.0
13
 * @license       https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Validation;
16
17
use Cake\I18n\Time;
18
use Cake\Utility\Text;
19
use DateTimeInterface;
20
use InvalidArgumentException;
21
use LogicException;
22
use NumberFormatter;
23
use Psr\Http\Message\UploadedFileInterface;
24
use RuntimeException;
25
26
/**
27
 * Validation Class. Used for validation of model data
28
 *
29
 * Offers different validation methods.
30
 */
31
class Validation
32
{
33
    /**
34
     * Default locale
35
     */
36
    const DEFAULT_LOCALE = 'en_US';
37
38
    /**
39
     * Same as operator.
40
     */
41
    const COMPARE_SAME = '===';
42
43
    /**
44
     * Not same as comparison operator.
45
     */
46
    const COMPARE_NOT_SAME = '!==';
47
48
    /**
49
     * Equal to comparison operator.
50
     */
51
    const COMPARE_EQUAL = '==';
52
53
    /**
54
     * Not equal to comparison operator.
55
     */
56
    const COMPARE_NOT_EQUAL = '!=';
57
58
    /**
59
     * Greater than comparison operator.
60
     */
61
    const COMPARE_GREATER = '>';
62
63
    /**
64
     * Greater than or equal to comparison operator.
65
     */
66
    const COMPARE_GREATER_OR_EQUAL = '>=';
67
68
    /**
69
     * Less than comparison operator.
70
     */
71
    const COMPARE_LESS = '<';
72
73
    /**
74
     * Less than or equal to comparison operator.
75
     */
76
    const COMPARE_LESS_OR_EQUAL = '<=';
77
78
    /**
79
     * Datetime ISO8601 format
80
     */
81
    const DATETIME_ISO8601 = 'iso8601';
82
83
    /**
84
     * Some complex patterns needed in multiple places
85
     *
86
     * @var array
87
     */
88
    protected static $_pattern = [
89
        'hostname' => '(?:[_\p{L}0-9][-_\p{L}0-9]*\.)*(?:[\p{L}0-9][-\p{L}0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})',
90
        'latitude' => '[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)',
91
        'longitude' => '[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)',
92
    ];
93
94
    /**
95
     * Holds an array of errors messages set in this class.
96
     * These are used for debugging purposes
97
     *
98
     * @var array
99
     */
100
    public static $errors = [];
101
102
    /**
103
     * Backwards compatibility wrapper for Validation::notBlank().
104
     *
105
     * @param string $check Value to check.
106
     * @return bool Success.
107
     * @deprecated 3.0.2 Use Validation::notBlank() instead.
108
     * @see \Cake\Validation\Validation::notBlank()
109
     */
110
    public static function notEmpty($check)
111
    {
112
        deprecationWarning(
113
            'Validation::notEmpty() is deprecated. ' .
114
            'Use Validation::notBlank() instead.'
115
        );
116
117
        return static::notBlank($check);
118
    }
119
120
    /**
121
     * Checks that a string contains something other than whitespace
122
     *
123
     * Returns true if string contains something other than whitespace
124
     *
125
     * @param string $check Value to check
126
     * @return bool Success
127
     */
128
    public static function notBlank($check)
129
    {
130
        if (empty($check) && !is_bool($check) && !is_numeric($check)) {
131
            return false;
132
        }
133
134
        return static::_check($check, '/[^\s]+/m');
135
    }
136
137
    /**
138
     * Checks that a string contains only integer or letters
139
     *
140
     * Returns true if string contains only integer or letters
141
     *
142
     * @param string $check Value to check
143
     * @return bool Success
144
     */
145
    public static function alphaNumeric($check)
146
    {
147
        if (empty($check) && $check !== '0') {
148
            return false;
149
        }
150
151
        return self::_check($check, '/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/Du');
152
    }
153
154
    /**
155
     * Checks that a string length is within specified range.
156
     * Spaces are included in the character count.
157
     * Returns true if string matches value min, max, or between min and max,
158
     *
159
     * @param string $check Value to check for length
160
     * @param int $min Minimum value in range (inclusive)
161
     * @param int $max Maximum value in range (inclusive)
162
     * @return bool Success
163
     */
164
    public static function lengthBetween($check, $min, $max)
165
    {
166
        if (!is_string($check)) {
167
            return false;
168
        }
169
        $length = mb_strlen($check);
170
171
        return ($length >= $min && $length <= $max);
172
    }
173
174
    /**
175
     * Returns true if field is left blank -OR- only whitespace characters are present in its value
176
     * Whitespace characters include Space, Tab, Carriage Return, Newline
177
     *
178
     * @param string $check Value to check
179
     * @return bool Success
180
     * @deprecated 3.0.2 Validation::blank() is deprecated.
181
     */
182
    public static function blank($check)
183
    {
184
        deprecationWarning(
185
            'Validation::blank() is deprecated.'
186
        );
187
188
        return !static::_check($check, '/[^\\s]/');
189
    }
190
191
    /**
192
     * Backwards compatibility wrapper for Validation::creditCard().
193
     *
194
     * @param string $check credit card number to validate
195
     * @param string|string[] $type 'all' may be passed as a string, defaults to fast which checks format of most major credit cards
196
     *    if an array is used only the values of the array are checked.
197
     *    Example: ['amex', 'bankcard', 'maestro']
198
     * @param bool $deep set to true this will check the Luhn algorithm of the credit card.
199
     * @param string|null $regex A custom regex can also be passed, this will be used instead of the defined regex values
200
     * @return bool Success
201
     * @deprecated 3.7.0 Use Validation::creditCard() instead.
202
     * @see \Cake\Validation\Validation::creditCard()
203
     */
204
    public static function cc($check, $type = 'fast', $deep = false, $regex = null)
205
    {
206
        deprecationWarning(
207
            'Validation::cc() is deprecated. ' .
208
            'Use Validation::creditCard() instead.'
209
        );
210
211
        return static::creditCard($check, $type, $deep, $regex);
212
    }
213
214
    /**
215
     * Validation of credit card numbers.
216
     * Returns true if $check is in the proper credit card format.
217
     *
218
     * @param string $check credit card number to validate
219
     * @param string|array $type 'all' may be passed as a string, defaults to fast which checks format of most major credit cards
220
     *    if an array is used only the values of the array are checked.
221
     *    Example: ['amex', 'bankcard', 'maestro']
222
     * @param bool $deep set to true this will check the Luhn algorithm of the credit card.
223
     * @param string|null $regex A custom regex can also be passed, this will be used instead of the defined regex values
224
     * @return bool Success
225
     * @see \Cake\Validation\Validation::luhn()
226
     */
227
    public static function creditCard($check, $type = 'fast', $deep = false, $regex = null)
228
    {
229
        if (!is_scalar($check)) {
230
            return false;
231
        }
232
233
        $check = str_replace(['-', ' '], '', $check);
234
        if (mb_strlen($check) < 13) {
235
            return false;
236
        }
237
238
        if ($regex !== null && static::_check($check, $regex)) {
239
            return !$deep || static::luhn($check);
240
        }
241
        $cards = [
242
            'all' => [
243
                'amex' => '/^3[47]\\d{13}$/',
244
                'bankcard' => '/^56(10\\d\\d|022[1-5])\\d{10}$/',
245
                'diners' => '/^(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})$/',
246
                'disc' => '/^(?:6011|650\\d)\\d{12}$/',
247
                'electron' => '/^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$/',
248
                'enroute' => '/^2(?:014|149)\\d{11}$/',
249
                'jcb' => '/^(3\\d{4}|2131|1800)\\d{11}$/',
250
                'maestro' => '/^(?:5020|6\\d{3})\\d{12}$/',
251
                'mc' => '/^(5[1-5]\\d{14})|(2(?:22[1-9]|2[3-9][0-9]|[3-6][0-9]{2}|7[0-1][0-9]|720)\\d{12})$/',
252
                'solo' => '/^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$/',
253
                'switch' => '/^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$/',
254
                'visa' => '/^4\\d{12}(\\d{3})?$/',
255
                'voyager' => '/^8699[0-9]{11}$/',
256
            ],
257
            'fast' => '/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})$/',
258
        ];
259
260
        if (is_array($type)) {
261 View Code Duplication
            foreach ($type as $value) {
262
                $regex = $cards['all'][strtolower($value)];
263
264
                if (static::_check($check, $regex)) {
265
                    return static::luhn($check);
266
                }
267
            }
268
        } elseif ($type === 'all') {
269 View Code Duplication
            foreach ($cards['all'] as $value) {
270
                $regex = $value;
271
272
                if (static::_check($check, $regex)) {
273
                    return static::luhn($check);
274
                }
275
            }
276
        } else {
277
            $regex = $cards['fast'];
278
279
            if (static::_check($check, $regex)) {
280
                return static::luhn($check);
281
            }
282
        }
283
284
        return false;
285
    }
286
287
    /**
288
     * Used to check the count of a given value of type array or Countable.
289
     *
290
     * @param array|\Countable $check The value to check the count on.
291
     * @param string $operator Can be either a word or operand
292
     *    is greater >, is less <, greater or equal >=
293
     *    less or equal <=, is less <, equal to ==, not equal !=
294
     * @param int $expectedCount The expected count value.
295
     * @return bool Success
296
     */
297
    public static function numElements($check, $operator, $expectedCount)
298
    {
299
        if (!is_array($check) && !$check instanceof \Countable) {
300
            return false;
301
        }
302
303
        return self::comparison(count($check), $operator, $expectedCount);
304
    }
305
306
    /**
307
     * Used to compare 2 numeric values.
308
     *
309
     * @param string $check1 The left value to compare.
310
     * @param string $operator Can be either a word or operand
311
     *    is greater >, is less <, greater or equal >=
312
     *    less or equal <=, is less <, equal to ==, not equal !=
313
     * @param int $check2 The right value to compare.
314
     * @return bool Success
315
     */
316
    public static function comparison($check1, $operator, $check2)
317
    {
318
        if ((float)$check1 != $check1) {
319
            return false;
320
        }
321
322
        $message = 'Operator `%s` is deprecated, use constant `Validation::%s` instead.';
323
324
        $operator = str_replace([' ', "\t", "\n", "\r", "\0", "\x0B"], '', strtolower($operator));
325
        switch ($operator) {
326
            case 'isgreater':
327
                /*
328
                 * @deprecated 3.6.0 Use Validation::COMPARE_GREATER instead.
329
                 */
330
                deprecationWarning(sprintf($message, $operator, 'COMPARE_GREATER'));
331
                // no break
332
            case static::COMPARE_GREATER:
333
                if ($check1 > $check2) {
334
                    return true;
335
                }
336
                break;
337
            case 'isless':
338
                /*
339
                 * @deprecated 3.6.0 Use Validation::COMPARE_LESS instead.
340
                 */
341
                deprecationWarning(sprintf($message, $operator, 'COMPARE_LESS'));
342
                // no break
343
            case static::COMPARE_LESS:
344
                if ($check1 < $check2) {
345
                    return true;
346
                }
347
                break;
348
            case 'greaterorequal':
349
                /*
350
                 * @deprecated 3.6.0 Use Validation::COMPARE_GREATER_OR_EQUAL instead.
351
                 */
352
                deprecationWarning(sprintf($message, $operator, 'COMPARE_GREATER_OR_EQUAL'));
353
                // no break
354
            case static::COMPARE_GREATER_OR_EQUAL:
355
                if ($check1 >= $check2) {
356
                    return true;
357
                }
358
                break;
359
            case 'lessorequal':
360
                /*
361
                 * @deprecated 3.6.0 Use Validation::COMPARE_LESS_OR_EQUAL instead.
362
                 */
363
                deprecationWarning(sprintf($message, $operator, 'COMPARE_LESS_OR_EQUAL'));
364
                // no break
365
            case static::COMPARE_LESS_OR_EQUAL:
366
                if ($check1 <= $check2) {
367
                    return true;
368
                }
369
                break;
370
            case 'equalto':
371
                /*
372
                 * @deprecated 3.6.0 Use Validation::COMPARE_EQUAL instead.
373
                 */
374
                deprecationWarning(sprintf($message, $operator, 'COMPARE_EQUAL'));
375
                // no break
376
            case static::COMPARE_EQUAL:
377
                if ($check1 == $check2) {
378
                    return true;
379
                }
380
                break;
381
            case 'notequal':
382
                /*
383
                 * @deprecated 3.6.0 Use Validation::COMPARE_NOT_EQUAL instead.
384
                 */
385
                deprecationWarning(sprintf($message, $operator, 'COMPARE_NOT_EQUAL'));
386
                // no break
387
            case static::COMPARE_NOT_EQUAL:
388
                if ($check1 != $check2) {
389
                    return true;
390
                }
391
                break;
392
            case static::COMPARE_SAME:
393
                if ($check1 === $check2) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $check1 (string) and $check2 (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
394
                    return true;
395
                }
396
                break;
397
            case static::COMPARE_NOT_SAME:
398
                if ($check1 !== $check2) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $check1 (string) and $check2 (integer) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
399
                    return true;
400
                }
401
                break;
402
            default:
403
                static::$errors[] = 'You must define the $operator parameter for Validation::comparison()';
404
        }
405
406
        return false;
407
    }
408
409
    /**
410
     * Compare one field to another.
411
     *
412
     * If both fields have exactly the same value this method will return true.
413
     *
414
     * @param mixed $check The value to find in $field.
415
     * @param string $field The field to check $check against. This field must be present in $context.
416
     * @param array $context The validation context.
417
     * @return bool
418
     */
419
    public static function compareWith($check, $field, $context)
420
    {
421
        return self::compareFields($check, $field, static::COMPARE_SAME, $context);
422
    }
423
424
    /**
425
     * Compare one field to another.
426
     *
427
     * Return true if the comparison matches the expected result.
428
     *
429
     * @param mixed $check The value to find in $field.
430
     * @param string $field The field to check $check against. This field must be present in $context.
431
     * @param string $operator Comparison operator.
432
     * @param array $context The validation context.
433
     * @return bool
434
     * @since 3.6.0
435
     */
436
    public static function compareFields($check, $field, $operator, $context)
437
    {
438
        if (!isset($context['data']) || !array_key_exists($field, $context['data'])) {
439
            return false;
440
        }
441
442
        return static::comparison($check, $operator, $context['data'][$field]);
443
    }
444
445
    /**
446
     * Checks if a string contains one or more non-alphanumeric characters.
447
     *
448
     * Returns true if string contains at least the specified number of non-alphanumeric characters
449
     *
450
     * @param string $check Value to check
451
     * @param int $count Number of non-alphanumerics to check for
452
     * @return bool Success
453
     */
454
    public static function containsNonAlphaNumeric($check, $count = 1)
455
    {
456
        if (!is_scalar($check)) {
457
            return false;
458
        }
459
460
        $matches = preg_match_all('/[^a-zA-Z0-9]/', $check);
461
462
        return $matches >= $count;
463
    }
464
465
    /**
466
     * Used when a custom regular expression is needed.
467
     *
468
     * @param string $check The value to check.
469
     * @param string|null $regex If $check is passed as a string, $regex must also be set to valid regular expression
470
     * @return bool Success
471
     */
472
    public static function custom($check, $regex = null)
473
    {
474
        if ($regex === null) {
475
            static::$errors[] = 'You must define a regular expression for Validation::custom()';
476
477
            return false;
478
        }
479
480
        return static::_check($check, $regex);
481
    }
482
483
    /**
484
     * Date validation, determines if the string passed is a valid date.
485
     * keys that expect full month, day and year will validate leap years.
486
     *
487
     * Years are valid from 0001 to 2999.
488
     *
489
     * ### Formats:
490
     *
491
     * - `dmy` 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash
492
     * - `mdy` 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash
493
     * - `ymd` 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash
494
     * - `dMy` 27 December 2006 or 27 Dec 2006
495
     * - `Mdy` December 27, 2006 or Dec 27, 2006 comma is optional
496
     * - `My` December 2006 or Dec 2006
497
     * - `my` 12/2006 or 12/06 separators can be a space, period, dash, forward slash
498
     * - `ym` 2006/12 or 06/12 separators can be a space, period, dash, forward slash
499
     * - `y` 2006 just the year without any separators
500
     *
501
     * @param string|\DateTimeInterface $check a valid date string/object
502
     * @param string|array $format Use a string or an array of the keys above.
503
     *    Arrays should be passed as ['dmy', 'mdy', etc]
504
     * @param string|null $regex If a custom regular expression is used this is the only validation that will occur.
505
     * @return bool Success
506
     */
507
    public static function date($check, $format = 'ymd', $regex = null)
508
    {
509
        if ($check instanceof DateTimeInterface) {
510
            return true;
511
        }
512
        if (is_object($check)) {
513
            return false;
514
        }
515
        if (is_array($check)) {
516
            $check = static::_getDateString($check);
517
            $format = 'ymd';
518
        }
519
520
        if ($regex !== null) {
521
            return static::_check($check, $regex);
522
        }
523
        $month = '(0[123456789]|10|11|12)';
524
        $separator = '([- /.])';
525
        // Don't allow 0000, but 0001-2999 are ok.
526
        $fourDigitYear = '(?:(?!0000)[012]\d{3})';
527
        $twoDigitYear = '(?:\d{2})';
528
        $year = '(?:' . $fourDigitYear . '|' . $twoDigitYear . ')';
529
530
        // 2 or 4 digit leap year sub-pattern
531
        $leapYear = '(?:(?:(?:(?!0000)[012]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))';
532
        // 4 digit leap year sub-pattern
533
        $fourDigitLeapYear = '(?:(?:(?:(?!0000)[012]\\d)(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))';
534
535
        $regex['dmy'] = '%^(?:(?:31(\\/|-|\\.|\\x20)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)' .
536
            $separator . '(?:0?[1,3-9]|1[0-2])\\2))' . $year . '$|^(?:29' .
537
            $separator . '0?2\\3' . $leapYear . ')$|^(?:0?[1-9]|1\\d|2[0-8])' .
538
            $separator . '(?:(?:0?[1-9])|(?:1[0-2]))\\4' . $year . '$%';
539
540
        $regex['mdy'] = '%^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.|\\x20)31)\\1|(?:(?:0?[13-9]|1[0-2])' .
541
            $separator . '(?:29|30)\\2))' . $year . '$|^(?:0?2' . $separator . '29\\3' . $leapYear . ')$|^(?:(?:0?[1-9])|(?:1[0-2]))' .
542
            $separator . '(?:0?[1-9]|1\\d|2[0-8])\\4' . $year . '$%';
543
544
        $regex['ymd'] = '%^(?:(?:' . $leapYear .
545
            $separator . '(?:0?2\\1(?:29)))|(?:' . $year .
546
            $separator . '(?:(?:(?:0?[13578]|1[02])\\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\\2(?:0?[1-9]|1\\d|2[0-8]))))$%';
547
548
        $regex['dMy'] = '/^((31(?!\\ (Feb(ruary)?|Apr(il)?|June?|(Sep(?=\\b|t)t?|Nov)(ember)?)))|((30|29)(?!\\ Feb(ruary)?))|(29(?=\\ Feb(ruary)?\\ ' . $fourDigitLeapYear . '))|(0?[1-9])|1\\d|2[0-8])\\ (Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)\\ ' . $fourDigitYear . '$/';
549
550
        $regex['Mdy'] = '/^(?:(((Jan(uary)?|Ma(r(ch)?|y)|Jul(y)?|Aug(ust)?|Oct(ober)?|Dec(ember)?)\\ 31)|((Jan(uary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep)(tember)?|(Nov|Dec)(ember)?)\\ (0?[1-9]|([12]\\d)|30))|(Feb(ruary)?\\ (0?[1-9]|1\\d|2[0-8]|(29(?=,?\\ ' . $fourDigitLeapYear . ')))))\\,?\\ ' . $fourDigitYear . ')$/';
551
552
        $regex['My'] = '%^(Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\\b|t)t?|Nov|Dec)(ember)?)' .
553
            $separator . $fourDigitYear . '$%';
554
555
        $regex['my'] = '%^(' . $month . $separator . $year . ')$%';
556
        $regex['ym'] = '%^(' . $year . $separator . $month . ')$%';
557
        $regex['y'] = '%^(' . $fourDigitYear . ')$%';
558
559
        $format = is_array($format) ? array_values($format) : [$format];
560
        foreach ($format as $key) {
561
            if (static::_check($check, $regex[$key]) === true) {
562
                return true;
563
            }
564
        }
565
566
        return false;
567
    }
568
569
    /**
570
     * Validates a datetime value
571
     *
572
     * All values matching the "date" core validation rule, and the "time" one will be valid
573
     *
574
     * @param string|\DateTimeInterface $check Value to check
575
     * @param string|array $dateFormat Format of the date part. See Validation::date() for more information.
576
     *      Or `Validation::DATETIME_ISO8601` to valid an ISO8601 datetime value
577
     * @param string|null $regex Regex for the date part. If a custom regular expression is used this is the only validation that will occur.
578
     * @return bool True if the value is valid, false otherwise
579
     * @see \Cake\Validation\Validation::date()
580
     * @see \Cake\Validation\Validation::time()
581
     */
582
    public static function datetime($check, $dateFormat = 'ymd', $regex = null)
583
    {
584
        if ($check instanceof DateTimeInterface) {
585
            return true;
586
        }
587
        if (is_object($check)) {
588
            return false;
589
        }
590
        if ($dateFormat === static::DATETIME_ISO8601 && !static::iso8601($check)) {
591
            return false;
592
        }
593
594
        $valid = false;
595
        if (is_array($check)) {
596
            $check = static::_getDateString($check);
597
            $dateFormat = 'ymd';
598
        }
599
        $parts = preg_split("/[\sT]+/", $check);
600
        if (!empty($parts) && count($parts) > 1) {
601
            $date = rtrim(array_shift($parts), ',');
602
            $time = implode(' ', $parts);
603
            if ($dateFormat === static::DATETIME_ISO8601) {
604
                $dateFormat = 'ymd';
605
                $time = preg_split("/[TZ\-\+\.]/", $time);
606
                $time = array_shift($time);
607
            }
608
            $valid = static::date($date, $dateFormat, $regex) && static::time($time);
609
        }
610
611
        return $valid;
612
    }
613
614
    /**
615
     * Validates an iso8601 datetime format
616
     * ISO8601 recognize datetime like 2019 as a valid date. To validate and check date integrity, use @see \Cake\Validation\Validation::datetime()
617
     *
618
     * @param string|\DateTimeInterface $check Value to check
619
     *
620
     * @return bool True if the value is valid, false otherwise
621
     *
622
     * @see Regex credits: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
623
     */
624
    public static function iso8601($check)
625
    {
626
        if ($check instanceof DateTimeInterface) {
627
            return true;
628
        }
629
        if (is_object($check)) {
630
            return false;
631
        }
632
633
        $regex = '/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/';
634
635
        return static::_check($check, $regex);
636
    }
637
638
    /**
639
     * Time validation, determines if the string passed is a valid time.
640
     * Validates time as 24hr (HH:MM) or am/pm ([H]H:MM[a|p]m)
641
     * Does not allow/validate seconds.
642
     *
643
     * @param string|\DateTimeInterface $check a valid time string/object
644
     * @return bool Success
645
     */
646
    public static function time($check)
647
    {
648
        if ($check instanceof DateTimeInterface) {
649
            return true;
650
        }
651
        if (is_array($check)) {
652
            $check = static::_getDateString($check);
653
        }
654
655
        return static::_check($check, '%^((0?[1-9]|1[012])(:[0-5]\d){0,2} ?([AP]M|[ap]m))$|^([01]\d|2[0-3])(:[0-5]\d){0,2}$%');
656
    }
657
658
    /**
659
     * Date and/or time string validation.
660
     * Uses `I18n::Time` to parse the date. This means parsing is locale dependent.
661
     *
662
     * @param string|\DateTime $check a date string or object (will always pass)
663
     * @param string $type Parser type, one out of 'date', 'time', and 'datetime'
664
     * @param string|int|null $format any format accepted by IntlDateFormatter
665
     * @return bool Success
666
     * @throws \InvalidArgumentException when unsupported $type given
667
     * @see \Cake\I18n\Time::parseDate(), \Cake\I18n\Time::parseTime(), \Cake\I18n\Time::parseDateTime()
668
     */
669
    public static function localizedTime($check, $type = 'datetime', $format = null)
670
    {
671
        if ($check instanceof DateTimeInterface) {
672
            return true;
673
        }
674
        if (is_object($check)) {
675
            return false;
676
        }
677
        static $methods = [
678
            'date' => 'parseDate',
679
            'time' => 'parseTime',
680
            'datetime' => 'parseDateTime',
681
        ];
682
        if (empty($methods[$type])) {
683
            throw new InvalidArgumentException('Unsupported parser type given.');
684
        }
685
        $method = $methods[$type];
686
687
        return (Time::$method($check, $format) !== null);
688
    }
689
690
    /**
691
     * Validates if passed value is boolean-like.
692
     *
693
     * The list of what is considered to be boolean values, may be set via $booleanValues.
694
     *
695
     * @param bool|int|string $check Value to check.
696
     * @param array $booleanValues List of valid boolean values, defaults to `[true, false, 0, 1, '0', '1']`.
697
     * @return bool Success.
698
     */
699
    public static function boolean($check, array $booleanValues = [])
700
    {
701
        if (!$booleanValues) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $booleanValues of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
702
            $booleanValues = [true, false, 0, 1, '0', '1'];
703
        }
704
705
        return in_array($check, $booleanValues, true);
706
    }
707
708
    /**
709
     * Validates if given value is truthy.
710
     *
711
     * The list of what is considered to be truthy values, may be set via $truthyValues.
712
     *
713
     * @param bool|int|string $check Value to check.
714
     * @param array $truthyValues List of valid truthy values, defaults to `[true, 1, '1']`.
715
     * @return bool Success.
716
     */
717
    public static function truthy($check, array $truthyValues = [])
718
    {
719
        if (!$truthyValues) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $truthyValues of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
720
            $truthyValues = [true, 1, '1'];
721
        }
722
723
        return in_array($check, $truthyValues, true);
724
    }
725
726
    /**
727
     * Validates if given value is falsey.
728
     *
729
     * The list of what is considered to be falsey values, may be set via $falseyValues.
730
     *
731
     * @param bool|int|string $check Value to check.
732
     * @param array $falseyValues List of valid falsey values, defaults to `[false, 0, '0']`.
733
     * @return bool Success.
734
     */
735
    public static function falsey($check, array $falseyValues = [])
736
    {
737
        if (!$falseyValues) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $falseyValues of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
738
            $falseyValues = [false, 0, '0'];
739
        }
740
741
        return in_array($check, $falseyValues, true);
742
    }
743
744
    /**
745
     * Checks that a value is a valid decimal. Both the sign and exponent are optional.
746
     *
747
     * Valid Places:
748
     *
749
     * - null => Any number of decimal places, including none. The '.' is not required.
750
     * - true => Any number of decimal places greater than 0, or a float|double. The '.' is required.
751
     * - 1..N => Exactly that many number of decimal places. The '.' is required.
752
     *
753
     * @param float $check The value the test for decimal.
754
     * @param int|bool|null $places Decimal places.
755
     * @param string|null $regex If a custom regular expression is used, this is the only validation that will occur.
756
     * @return bool Success
757
     */
758
    public static function decimal($check, $places = null, $regex = null)
759
    {
760
        if ($regex === null) {
761
            $lnum = '[0-9]+';
762
            $dnum = "[0-9]*[\.]{$lnum}";
763
            $sign = '[+-]?';
764
            $exp = "(?:[eE]{$sign}{$lnum})?";
765
766
            if ($places === null) {
767
                $regex = "/^{$sign}(?:{$lnum}|{$dnum}){$exp}$/";
768
            } elseif ($places === true) {
769
                if (is_float($check) && floor($check) === $check) {
770
                    $check = sprintf('%.1f', $check);
771
                }
772
                $regex = "/^{$sign}{$dnum}{$exp}$/";
773
            } elseif (is_numeric($places)) {
774
                $places = '[0-9]{' . $places . '}';
775
                $dnum = "(?:[0-9]*[\.]{$places}|{$lnum}[\.]{$places})";
776
                $regex = "/^{$sign}{$dnum}{$exp}$/";
777
            }
778
        }
779
780
        // account for localized floats.
781
        $locale = ini_get('intl.default_locale') ?: static::DEFAULT_LOCALE;
782
        $formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL);
783
        $decimalPoint = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
784
        $groupingSep = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
785
786
        $check = str_replace([$groupingSep, $decimalPoint], ['', '.'], $check);
787
788
        return static::_check($check, $regex);
789
    }
790
791
    /**
792
     * Validates for an email address.
793
     *
794
     * Only uses getmxrr() checking for deep validation, or
795
     * any PHP version on a non-windows distribution
796
     *
797
     * @param string $check Value to check
798
     * @param bool $deep Perform a deeper validation (if true), by also checking availability of host
799
     * @param string|null $regex Regex to use (if none it will use built in regex)
800
     * @return bool Success
801
     */
802
    public static function email($check, $deep = false, $regex = null)
803
    {
804
        if (!is_string($check)) {
805
            return false;
806
        }
807
808
        if ($regex === null) {
809
            $regex = '/^[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' . self::$_pattern['hostname'] . '$/ui';
810
        }
811
        $return = static::_check($check, $regex);
812
        if ($deep === false || $deep === null) {
813
            return $return;
814
        }
815
816
        if ($return === true && preg_match('/@(' . static::$_pattern['hostname'] . ')$/i', $check, $regs)) {
817
            if (function_exists('getmxrr') && getmxrr($regs[1], $mxhosts)) {
818
                return true;
819
            }
820
            if (function_exists('checkdnsrr') && checkdnsrr($regs[1], 'MX')) {
821
                return true;
822
            }
823
824
            return is_array(gethostbynamel($regs[1] . '.'));
825
        }
826
827
        return false;
828
    }
829
830
    /**
831
     * Checks that value is exactly $comparedTo.
832
     *
833
     * @param mixed $check Value to check
834
     * @param mixed $comparedTo Value to compare
835
     * @return bool Success
836
     */
837
    public static function equalTo($check, $comparedTo)
838
    {
839
        return ($check === $comparedTo);
840
    }
841
842
    /**
843
     * Checks that value has a valid file extension.
844
     *
845
     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check
846
     * @param array $extensions file extensions to allow. By default extensions are 'gif', 'jpeg', 'png', 'jpg'
847
     * @return bool Success
848
     */
849
    public static function extension($check, $extensions = ['gif', 'jpeg', 'png', 'jpg'])
850
    {
851
        if ($check instanceof UploadedFileInterface) {
852
            return static::extension($check->getClientFilename(), $extensions);
853
        }
854
        if (is_array($check)) {
855
            $check = isset($check['name']) ? $check['name'] : array_shift($check);
856
857
            return static::extension($check, $extensions);
858
        }
859
        $extension = strtolower(pathinfo($check, PATHINFO_EXTENSION));
860
        foreach ($extensions as $value) {
861
            if ($extension === strtolower($value)) {
862
                return true;
863
            }
864
        }
865
866
        return false;
867
    }
868
869
    /**
870
     * Validation of an IP address.
871
     *
872
     * @param string $check The string to test.
873
     * @param string $type The IP Protocol version to validate against
874
     * @return bool Success
875
     */
876
    public static function ip($check, $type = 'both')
877
    {
878
        $type = strtolower($type);
879
        $flags = 0;
880
        if ($type === 'ipv4') {
881
            $flags = FILTER_FLAG_IPV4;
882
        }
883
        if ($type === 'ipv6') {
884
            $flags = FILTER_FLAG_IPV6;
885
        }
886
887
        return (bool)filter_var($check, FILTER_VALIDATE_IP, ['flags' => $flags]);
888
    }
889
890
    /**
891
     * Checks whether the length of a string (in characters) is greater or equal to a minimal length.
892
     *
893
     * @param string $check The string to test
894
     * @param int $min The minimal string length
895
     * @return bool Success
896
     */
897
    public static function minLength($check, $min)
898
    {
899
        if (!is_scalar($check)) {
900
            return false;
901
        }
902
903
        return mb_strlen($check) >= $min;
904
    }
905
906
    /**
907
     * Checks whether the length of a string (in characters) is smaller or equal to a maximal length.
908
     *
909
     * @param string $check The string to test
910
     * @param int $max The maximal string length
911
     * @return bool Success
912
     */
913
    public static function maxLength($check, $max)
914
    {
915
        if (!is_scalar($check)) {
916
            return false;
917
        }
918
919
        return mb_strlen($check) <= $max;
920
    }
921
922
    /**
923
     * Checks whether the length of a string (in bytes) is greater or equal to a minimal length.
924
     *
925
     * @param string $check The string to test
926
     * @param int $min The minimal string length (in bytes)
927
     * @return bool Success
928
     */
929
    public static function minLengthBytes($check, $min)
930
    {
931
        if (!is_scalar($check)) {
932
            return false;
933
        }
934
935
        return strlen($check) >= $min;
936
    }
937
938
    /**
939
     * Checks whether the length of a string (in bytes) is smaller or equal to a maximal length.
940
     *
941
     * @param string $check The string to test
942
     * @param int $max The maximal string length
943
     * @return bool Success
944
     */
945
    public static function maxLengthBytes($check, $max)
946
    {
947
        if (!is_scalar($check)) {
948
            return false;
949
        }
950
951
        return strlen($check) <= $max;
952
    }
953
954
    /**
955
     * Checks that a value is a monetary amount.
956
     *
957
     * @param string $check Value to check
958
     * @param string $symbolPosition Where symbol is located (left/right)
959
     * @return bool Success
960
     */
961
    public static function money($check, $symbolPosition = 'left')
962
    {
963
        $money = '(?!0,?\d)(?:\d{1,3}(?:([, .])\d{3})?(?:\1\d{3})*|(?:\d+))((?!\1)[,.]\d{1,2})?';
964
        if ($symbolPosition === 'right') {
965
            $regex = '/^' . $money . '(?<!\x{00a2})\p{Sc}?$/u';
966
        } else {
967
            $regex = '/^(?!\x{00a2})\p{Sc}?' . $money . '$/u';
968
        }
969
970
        return static::_check($check, $regex);
971
    }
972
973
    /**
974
     * Validates a multiple select. Comparison is case sensitive by default.
975
     *
976
     * Valid Options
977
     *
978
     * - in => provide a list of choices that selections must be made from
979
     * - max => maximum number of non-zero choices that can be made
980
     * - min => minimum number of non-zero choices that can be made
981
     *
982
     * @param array $check Value to check
983
     * @param array $options Options for the check.
984
     * @param bool $caseInsensitive Set to true for case insensitive comparison.
985
     * @return bool Success
986
     */
987
    public static function multiple($check, array $options = [], $caseInsensitive = false)
988
    {
989
        $defaults = ['in' => null, 'max' => null, 'min' => null];
990
        $options += $defaults;
991
992
        $check = array_filter((array)$check, function ($value) {
993
            return ($value || is_numeric($value));
994
        });
995
        if (empty($check)) {
996
            return false;
997
        }
998
        if ($options['max'] && count($check) > $options['max']) {
999
            return false;
1000
        }
1001
        if ($options['min'] && count($check) < $options['min']) {
1002
            return false;
1003
        }
1004
        if ($options['in'] && is_array($options['in'])) {
1005
            if ($caseInsensitive) {
1006
                $options['in'] = array_map('mb_strtolower', $options['in']);
1007
            }
1008
            foreach ($check as $val) {
1009
                $strict = !is_numeric($val);
1010
                if ($caseInsensitive) {
1011
                    $val = mb_strtolower($val);
1012
                }
1013
                if (!in_array((string)$val, $options['in'], $strict)) {
1014
                    return false;
1015
                }
1016
            }
1017
        }
1018
1019
        return true;
1020
    }
1021
1022
    /**
1023
     * Checks if a value is numeric.
1024
     *
1025
     * @param string $check Value to check
1026
     * @return bool Success
1027
     */
1028
    public static function numeric($check)
1029
    {
1030
        return is_numeric($check);
1031
    }
1032
1033
    /**
1034
     * Checks if a value is a natural number.
1035
     *
1036
     * @param string $check Value to check
1037
     * @param bool $allowZero Set true to allow zero, defaults to false
1038
     * @return bool Success
1039
     * @see https://en.wikipedia.org/wiki/Natural_number
1040
     */
1041
    public static function naturalNumber($check, $allowZero = false)
1042
    {
1043
        $regex = $allowZero ? '/^(?:0|[1-9][0-9]*)$/' : '/^[1-9][0-9]*$/';
1044
1045
        return static::_check($check, $regex);
1046
    }
1047
1048
    /**
1049
     * Validates that a number is in specified range.
1050
     *
1051
     * If $lower and $upper are set, the range is inclusive.
1052
     * If they are not set, will return true if $check is a
1053
     * legal finite on this platform.
1054
     *
1055
     * @param string $check Value to check
1056
     * @param int|float|null $lower Lower limit
1057
     * @param int|float|null $upper Upper limit
1058
     * @return bool Success
1059
     */
1060
    public static function range($check, $lower = null, $upper = null)
1061
    {
1062
        if (!is_numeric($check)) {
1063
            return false;
1064
        }
1065
        if ((float)$check != $check) {
1066
            return false;
1067
        }
1068
        if (isset($lower, $upper)) {
1069
            return ($check >= $lower && $check <= $upper);
1070
        }
1071
1072
        return is_finite($check);
1073
    }
1074
1075
    /**
1076
     * Checks that a value is a valid URL according to https://www.w3.org/Addressing/URL/url-spec.txt
1077
     *
1078
     * The regex checks for the following component parts:
1079
     *
1080
     * - a valid, optional, scheme
1081
     * - a valid ip address OR
1082
     *   a valid domain name as defined by section 2.3.1 of https://www.ietf.org/rfc/rfc1035.txt
1083
     *   with an optional port number
1084
     * - an optional valid path
1085
     * - an optional query string (get parameters)
1086
     * - an optional fragment (anchor tag) as defined in RFC 3986
1087
     *
1088
     * @param string $check Value to check
1089
     * @param bool $strict Require URL to be prefixed by a valid scheme (one of http(s)/ftp(s)/file/news/gopher)
1090
     * @return bool Success
1091
     * @link https://tools.ietf.org/html/rfc3986
1092
     */
1093
    public static function url($check, $strict = false)
1094
    {
1095
        static::_populateIp();
1096
1097
        $emoji = '\x{1F190}-\x{1F9EF}';
1098
        $alpha = '0-9\p{L}\p{N}' . $emoji;
1099
        $hex = '(%[0-9a-f]{2})';
1100
        $subDelimiters = preg_quote('/!"$&\'()*+,-.@_:;=~[]', '/');
1101
        $path = '([' . $subDelimiters . $alpha . ']|' . $hex . ')';
1102
        $fragmentAndQuery = '([\?' . $subDelimiters . $alpha . ']|' . $hex . ')';
1103
        $regex = '/^(?:(?:https?|ftps?|sftp|file|news|gopher):\/\/)' . (!empty($strict) ? '' : '?') .
1104
            '(?:' . static::$_pattern['IPv4'] . '|\[' . static::$_pattern['IPv6'] . '\]|' . static::$_pattern['hostname'] . ')(?::[1-9][0-9]{0,4})?' .
1105
            '(?:\/' . $path . '*)?' .
1106
            '(?:\?' . $fragmentAndQuery . '*)?' .
1107
            '(?:#' . $fragmentAndQuery . '*)?$/iu';
1108
1109
        return static::_check($check, $regex);
1110
    }
1111
1112
    /**
1113
     * Checks if a value is in a given list. Comparison is case sensitive by default.
1114
     *
1115
     * @param string $check Value to check.
1116
     * @param string[] $list List to check against.
1117
     * @param bool $caseInsensitive Set to true for case insensitive comparison.
1118
     * @return bool Success.
1119
     */
1120
    public static function inList($check, array $list, $caseInsensitive = false)
1121
    {
1122
        if ($caseInsensitive) {
1123
            $list = array_map('mb_strtolower', $list);
1124
            $check = mb_strtolower($check);
1125
        } else {
1126
            $list = array_map('strval', $list);
1127
        }
1128
1129
        return in_array((string)$check, $list, true);
1130
    }
1131
1132
    /**
1133
     * Runs an user-defined validation.
1134
     *
1135
     * @param string|array $check value that will be validated in user-defined methods.
1136
     * @param object $object class that holds validation method
1137
     * @param string $method class method name for validation to run
1138
     * @param array|null $args arguments to send to method
1139
     * @return mixed user-defined class class method returns
1140
     * @deprecated 3.0.2 You can just set a callable for `rule` key when adding validators.
1141
     */
1142
    public static function userDefined($check, $object, $method, $args = null)
1143
    {
1144
        deprecationWarning(
1145
            'Validation::userDefined() is deprecated. ' .
1146
            'You can just set a callable for `rule` key when adding validators.'
1147
        );
1148
1149
        return $object->$method($check, $args);
1150
    }
1151
1152
    /**
1153
     * Checks that a value is a valid UUID - https://tools.ietf.org/html/rfc4122
1154
     *
1155
     * @param string $check Value to check
1156
     * @return bool Success
1157
     */
1158
    public static function uuid($check)
1159
    {
1160
        $regex = '/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[0-5][a-fA-F0-9]{3}-[089aAbB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$/';
1161
1162
        return self::_check($check, $regex);
1163
    }
1164
1165
    /**
1166
     * Runs a regular expression match.
1167
     *
1168
     * @param string $check Value to check against the $regex expression
1169
     * @param string $regex Regular expression
1170
     * @return bool Success of match
1171
     */
1172
    protected static function _check($check, $regex)
1173
    {
1174
        return is_string($regex) && is_scalar($check) && preg_match($regex, $check);
1175
    }
1176
1177
    /**
1178
     * Luhn algorithm
1179
     *
1180
     * @param string|array $check Value to check.
1181
     * @return bool Success
1182
     * @see https://en.wikipedia.org/wiki/Luhn_algorithm
1183
     */
1184
    public static function luhn($check)
1185
    {
1186
        if (!is_scalar($check) || (int)$check === 0) {
1187
            return false;
1188
        }
1189
        $sum = 0;
1190
        $length = strlen($check);
1191
1192
        for ($position = 1 - ($length % 2); $position < $length; $position += 2) {
1193
            $sum += $check[$position];
1194
        }
1195
1196
        for ($position = ($length % 2); $position < $length; $position += 2) {
1197
            $number = (int)$check[$position] * 2;
1198
            $sum += ($number < 10) ? $number : $number - 9;
1199
        }
1200
1201
        return ($sum % 10 === 0);
1202
    }
1203
1204
    /**
1205
     * Checks the mime type of a file.
1206
     *
1207
     * Will check the mimetype of files/UploadedFileInterface instances
1208
     * by checking the using finfo on the file, not relying on the content-type
1209
     * sent by the client.
1210
     *
1211
     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check.
1212
     * @param array|string $mimeTypes Array of mime types or regex pattern to check.
1213
     * @return bool Success
1214
     * @throws \RuntimeException when mime type can not be determined.
1215
     * @throws \LogicException when ext/fileinfo is missing
1216
     */
1217
    public static function mimeType($check, $mimeTypes = [])
1218
    {
1219
        $file = static::getFilename($check);
1220
        if ($file === false) {
1221
            return false;
1222
        }
1223
1224
        if (!function_exists('finfo_open')) {
1225
            throw new LogicException('ext/fileinfo is required for validating file mime types');
1226
        }
1227
1228
        if (!is_file($file)) {
1229
            throw new RuntimeException('Cannot validate mimetype for a missing file');
1230
        }
1231
1232
        $finfo = finfo_open(FILEINFO_MIME);
1233
        $finfo = finfo_file($finfo, $file);
1234
1235
        if (!$finfo) {
1236
            throw new RuntimeException('Can not determine the mimetype.');
1237
        }
1238
1239
        list($mime) = explode(';', $finfo);
1240
1241
        if (is_string($mimeTypes)) {
1242
            return self::_check($mime, $mimeTypes);
1243
        }
1244
1245
        foreach ($mimeTypes as $key => $val) {
1246
            $mimeTypes[$key] = strtolower($val);
1247
        }
1248
1249
        return in_array(strtolower($mime), $mimeTypes, true);
1250
    }
1251
1252
    /**
1253
     * Helper for reading the file out of the various file implementations
1254
     * we accept.
1255
     *
1256
     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check The data to read a filename out of.
1257
     * @return string|bool Either the filename or false on failure.
1258
     */
1259
    protected static function getFilename($check)
1260
    {
1261
        if ($check instanceof UploadedFileInterface) {
1262
            try {
1263
                // Uploaded files throw exceptions on upload errors.
1264
                return $check->getStream()->getMetadata('uri');
1265
            } catch (RuntimeException $e) {
1266
                return false;
1267
            }
1268
        }
1269
        if (is_array($check) && isset($check['tmp_name'])) {
1270
            return $check['tmp_name'];
1271
        }
1272
1273
        if (is_string($check)) {
1274
            return $check;
1275
        }
1276
1277
        return false;
1278
    }
1279
1280
    /**
1281
     * Checks the filesize
1282
     *
1283
     * Will check the filesize of files/UploadedFileInterface instances
1284
     * by checking the filesize() on disk and not relying on the length
1285
     * reported by the client.
1286
     *
1287
     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check.
1288
     * @param string|null $operator See `Validation::comparison()`.
1289
     * @param int|string|null $size Size in bytes or human readable string like '5MB'.
1290
     * @return bool Success
1291
     */
1292
    public static function fileSize($check, $operator = null, $size = null)
1293
    {
1294
        $file = static::getFilename($check);
1295
        if ($file === false) {
1296
            return false;
1297
        }
1298
1299
        if (is_string($size)) {
1300
            $size = Text::parseFileSize($size);
1301
        }
1302
        $filesize = filesize($file);
1303
1304
        return static::comparison($filesize, $operator, $size);
1305
    }
1306
1307
    /**
1308
     * Checking for upload errors
1309
     *
1310
     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check.
1311
     * @param bool $allowNoFile Set to true to allow UPLOAD_ERR_NO_FILE as a pass.
1312
     * @return bool
1313
     * @see https://secure.php.net/manual/en/features.file-upload.errors.php
1314
     */
1315
    public static function uploadError($check, $allowNoFile = false)
1316
    {
1317
        if ($check instanceof UploadedFileInterface) {
1318
            $code = $check->getError();
1319
        } elseif (is_array($check) && isset($check['error'])) {
1320
            $code = $check['error'];
1321
        } else {
1322
            $code = $check;
1323
        }
1324
        if ($allowNoFile) {
1325
            return in_array((int)$code, [UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE], true);
1326
        }
1327
1328
        return (int)$code === UPLOAD_ERR_OK;
1329
    }
1330
1331
    /**
1332
     * Validate an uploaded file.
1333
     *
1334
     * Helps join `uploadError`, `fileSize` and `mimeType` into
1335
     * one higher level validation method.
1336
     *
1337
     * ### Options
1338
     *
1339
     * - `types` - An array of valid mime types. If empty all types
1340
     *   will be accepted. The `type` will not be looked at, instead
1341
     *   the file type will be checked with ext/finfo.
1342
     * - `minSize` - The minimum file size in bytes. Defaults to not checking.
1343
     * - `maxSize` - The maximum file size in bytes. Defaults to not checking.
1344
     * - `optional` - Whether or not this file is optional. Defaults to false.
1345
     *   If true a missing file will pass the validator regardless of other constraints.
1346
     *
1347
     * @param array|\Psr\Http\Message\UploadedFileInterface $file The uploaded file data from PHP.
1348
     * @param array $options An array of options for the validation.
1349
     * @return bool
1350
     */
1351
    public static function uploadedFile($file, array $options = [])
1352
    {
1353
        $options += [
1354
            'minSize' => null,
1355
            'maxSize' => null,
1356
            'types' => null,
1357
            'optional' => false,
1358
        ];
1359
        if (!is_array($file) && !($file instanceof UploadedFileInterface)) {
1360
            return false;
1361
        }
1362
        $error = $isUploaded = false;
1363
        if ($file instanceof UploadedFileInterface) {
1364
            $error = $file->getError();
1365
            $isUploaded = true;
1366
        }
1367
        if (is_array($file)) {
1368
            $keys = ['error', 'name', 'size', 'tmp_name', 'type'];
1369
            ksort($file);
1370
            if (array_keys($file) != $keys) {
1371
                return false;
1372
            }
1373
            $error = (int)$file['error'];
1374
            $isUploaded = is_uploaded_file($file['tmp_name']);
1375
        }
1376
1377
        if (!static::uploadError($file, $options['optional'])) {
1378
            return false;
1379
        }
1380
        if ($options['optional'] && $error === UPLOAD_ERR_NO_FILE) {
1381
            return true;
1382
        }
1383 View Code Duplication
        if (isset($options['minSize']) && !static::fileSize($file, static::COMPARE_GREATER_OR_EQUAL, $options['minSize'])) {
1384
            return false;
1385
        }
1386 View Code Duplication
        if (isset($options['maxSize']) && !static::fileSize($file, static::COMPARE_LESS_OR_EQUAL, $options['maxSize'])) {
1387
            return false;
1388
        }
1389
        if (isset($options['types']) && !static::mimeType($file, $options['types'])) {
1390
            return false;
1391
        }
1392
1393
        return $isUploaded;
1394
    }
1395
1396
    /**
1397
     * Validates the size of an uploaded image.
1398
     *
1399
     * @param array|\Psr\Http\Message\UploadedFileInterface $file The uploaded file data from PHP.
1400
     * @param array $options Options to validate width and height.
1401
     * @return bool
1402
     * @throws \InvalidArgumentException
1403
     */
1404
    public static function imageSize($file, $options)
1405
    {
1406
        if (!isset($options['height']) && !isset($options['width'])) {
1407
            throw new InvalidArgumentException('Invalid image size validation parameters! Missing `width` and / or `height`.');
1408
        }
1409
1410
        $filename = static::getFilename($file);
1411
1412
        list($width, $height) = getimagesize($filename);
1413
1414
        $validHeight = $validWidth = null;
1415
1416
        if (isset($options['height'])) {
1417
            $validHeight = self::comparison($height, $options['height'][0], $options['height'][1]);
1418
        }
1419
        if (isset($options['width'])) {
1420
            $validWidth = self::comparison($width, $options['width'][0], $options['width'][1]);
1421
        }
1422
        if ($validHeight !== null && $validWidth !== null) {
1423
            return ($validHeight && $validWidth);
1424
        }
1425
        if ($validHeight !== null) {
1426
            return $validHeight;
1427
        }
1428
        if ($validWidth !== null) {
1429
            return $validWidth;
1430
        }
1431
1432
        throw new InvalidArgumentException('The 2nd argument is missing the `width` and / or `height` options.');
1433
    }
1434
1435
    /**
1436
     * Validates the image width.
1437
     *
1438
     * @param array $file The uploaded file data from PHP.
1439
     * @param string $operator Comparison operator.
1440
     * @param int $width Min or max width.
1441
     * @return bool
1442
     */
1443
    public static function imageWidth($file, $operator, $width)
1444
    {
1445
        return self::imageSize($file, [
1446
            'width' => [
1447
                $operator,
1448
                $width,
1449
            ],
1450
        ]);
1451
    }
1452
1453
    /**
1454
     * Validates the image width.
1455
     *
1456
     * @param array $file The uploaded file data from PHP.
1457
     * @param string $operator Comparison operator.
1458
     * @param int $height Min or max width.
1459
     * @return bool
1460
     */
1461
    public static function imageHeight($file, $operator, $height)
1462
    {
1463
        return self::imageSize($file, [
1464
            'height' => [
1465
                $operator,
1466
                $height,
1467
            ],
1468
        ]);
1469
    }
1470
1471
    /**
1472
     * Validates a geographic coordinate.
1473
     *
1474
     * Supported formats:
1475
     *
1476
     * - `<latitude>, <longitude>` Example: `-25.274398, 133.775136`
1477
     *
1478
     * ### Options
1479
     *
1480
     * - `type` - A string of the coordinate format, right now only `latLong`.
1481
     * - `format` - By default `both`, can be `long` and `lat` as well to validate
1482
     *   only a part of the coordinate.
1483
     *
1484
     * @param string $value Geographic location as string
1485
     * @param array $options Options for the validation logic.
1486
     * @return bool
1487
     */
1488
    public static function geoCoordinate($value, array $options = [])
1489
    {
1490
        $options += [
1491
            'format' => 'both',
1492
            'type' => 'latLong',
1493
        ];
1494
        if ($options['type'] !== 'latLong') {
1495
            throw new RuntimeException(sprintf(
1496
                'Unsupported coordinate type "%s". Use "latLong" instead.',
1497
                $options['type']
1498
            ));
1499
        }
1500
        $pattern = '/^' . self::$_pattern['latitude'] . ',\s*' . self::$_pattern['longitude'] . '$/';
1501
        if ($options['format'] === 'long') {
1502
            $pattern = '/^' . self::$_pattern['longitude'] . '$/';
1503
        }
1504
        if ($options['format'] === 'lat') {
1505
            $pattern = '/^' . self::$_pattern['latitude'] . '$/';
1506
        }
1507
1508
        return (bool)preg_match($pattern, $value);
1509
    }
1510
1511
    /**
1512
     * Convenience method for latitude validation.
1513
     *
1514
     * @param string $value Latitude as string
1515
     * @param array $options Options for the validation logic.
1516
     * @return bool
1517
     * @link https://en.wikipedia.org/wiki/Latitude
1518
     * @see \Cake\Validation\Validation::geoCoordinate()
1519
     */
1520
    public static function latitude($value, array $options = [])
1521
    {
1522
        $options['format'] = 'lat';
1523
1524
        return self::geoCoordinate($value, $options);
1525
    }
1526
1527
    /**
1528
     * Convenience method for longitude validation.
1529
     *
1530
     * @param string $value Latitude as string
1531
     * @param array $options Options for the validation logic.
1532
     * @return bool
1533
     * @link https://en.wikipedia.org/wiki/Longitude
1534
     * @see \Cake\Validation\Validation::geoCoordinate()
1535
     */
1536
    public static function longitude($value, array $options = [])
1537
    {
1538
        $options['format'] = 'long';
1539
1540
        return self::geoCoordinate($value, $options);
1541
    }
1542
1543
    /**
1544
     * Check that the input value is within the ascii byte range.
1545
     *
1546
     * This method will reject all non-string values.
1547
     *
1548
     * @param string $value The value to check
1549
     * @return bool
1550
     */
1551
    public static function ascii($value)
1552
    {
1553
        if (!is_string($value)) {
1554
            return false;
1555
        }
1556
1557
        return strlen($value) <= mb_strlen($value, 'utf-8');
1558
    }
1559
1560
    /**
1561
     * Check that the input value is a utf8 string.
1562
     *
1563
     * This method will reject all non-string values.
1564
     *
1565
     * # Options
1566
     *
1567
     * - `extended` - Disallow bytes higher within the basic multilingual plane.
1568
     *   MySQL's older utf8 encoding type does not allow characters above
1569
     *   the basic multilingual plane. Defaults to false.
1570
     *
1571
     * @param string $value The value to check
1572
     * @param array $options An array of options. See above for the supported options.
1573
     * @return bool
1574
     */
1575
    public static function utf8($value, array $options = [])
1576
    {
1577
        if (!is_string($value)) {
1578
            return false;
1579
        }
1580
        $options += ['extended' => false];
1581
        if ($options['extended']) {
1582
            return true;
1583
        }
1584
1585
        return preg_match('/[\x{10000}-\x{10FFFF}]/u', $value) === 0;
1586
    }
1587
1588
    /**
1589
     * Check that the input value is an integer
1590
     *
1591
     * This method will accept strings that contain only integer data
1592
     * as well.
1593
     *
1594
     * @param string $value The value to check
1595
     * @return bool
1596
     */
1597
    public static function isInteger($value)
1598
    {
1599
        if (!is_numeric($value) || is_float($value)) {
1600
            return false;
1601
        }
1602
        if (is_int($value)) {
1603
            return true;
1604
        }
1605
1606
        return (bool)preg_match('/^-?[0-9]+$/', $value);
1607
    }
1608
1609
    /**
1610
     * Check that the input value is an array.
1611
     *
1612
     * @param array $value The value to check
1613
     * @return bool
1614
     */
1615
    public static function isArray($value)
1616
    {
1617
        return is_array($value);
1618
    }
1619
1620
    /**
1621
     * Check that the input value is a scalar.
1622
     *
1623
     * This method will accept integers, floats, strings and booleans, but
1624
     * not accept arrays, objects, resources and nulls.
1625
     *
1626
     * @param mixed $value The value to check
1627
     * @return bool
1628
     */
1629
    public static function isScalar($value)
1630
    {
1631
        return is_scalar($value);
1632
    }
1633
1634
    /**
1635
     * Check that the input value is a 6 digits hex color.
1636
     *
1637
     * @param string|array $check The value to check
1638
     * @return bool Success
1639
     */
1640
    public static function hexColor($check)
1641
    {
1642
        return static::_check($check, '/^#[0-9a-f]{6}$/iD');
0 ignored issues
show
Bug introduced by
It seems like $check defined by parameter $check on line 1640 can also be of type array; however, Cake\Validation\Validation::_check() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1643
    }
1644
1645
    /**
1646
     * Check that the input value has a valid International Bank Account Number IBAN syntax
1647
     * Requirements are uppercase, no whitespaces, max length 34, country code and checksum exist at right spots,
1648
     * body matches against checksum via Mod97-10 algorithm
1649
     *
1650
     * @param string $check The value to check
1651
     *
1652
     * @return bool Success
1653
     */
1654
    public static function iban($check)
1655
    {
1656
        if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $check)) {
1657
            return false;
1658
        }
1659
1660
        $country = substr($check, 0, 2);
1661
        $checkInt = intval(substr($check, 2, 2));
1662
        $account = substr($check, 4);
1663
        $search = range('A', 'Z');
1664
        $replace = [];
1665
        foreach (range(10, 35) as $tmp) {
1666
            $replace[] = strval($tmp);
1667
        }
1668
        $numStr = str_replace($search, $replace, $account . $country . '00');
1669
        $checksum = intval(substr($numStr, 0, 1));
1670
        $numStrLength = strlen($numStr);
1671
        for ($pos = 1; $pos < $numStrLength; $pos++) {
1672
            $checksum *= 10;
1673
            $checksum += intval(substr($numStr, $pos, 1));
1674
            $checksum %= 97;
1675
        }
1676
1677
        return ((98 - $checksum) === $checkInt);
1678
    }
1679
1680
    /**
1681
     * Converts an array representing a date or datetime into a ISO string.
1682
     * The arrays are typically sent for validation from a form generated by
1683
     * the CakePHP FormHelper.
1684
     *
1685
     * @param array $value The array representing a date or datetime.
1686
     * @return string
1687
     */
1688
    protected static function _getDateString($value)
1689
    {
1690
        $formatted = '';
1691 View Code Duplication
        if (
1692
            isset($value['year'], $value['month'], $value['day']) &&
1693
            (is_numeric($value['year']) && is_numeric($value['month']) && is_numeric($value['day']))
1694
        ) {
1695
            $formatted .= sprintf('%d-%02d-%02d ', $value['year'], $value['month'], $value['day']);
1696
        }
1697
1698
        if (isset($value['hour'])) {
1699 View Code Duplication
            if (isset($value['meridian']) && (int)$value['hour'] === 12) {
1700
                $value['hour'] = 0;
1701
            }
1702 View Code Duplication
            if (isset($value['meridian'])) {
1703
                $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12;
1704
            }
1705
            $value += ['minute' => 0, 'second' => 0];
1706
            if (is_numeric($value['hour']) && is_numeric($value['minute']) && is_numeric($value['second'])) {
1707
                $formatted .= sprintf('%02d:%02d:%02d', $value['hour'], $value['minute'], $value['second']);
1708
            }
1709
        }
1710
1711
        return trim($formatted);
1712
    }
1713
1714
    /**
1715
     * Lazily populate the IP address patterns used for validations
1716
     *
1717
     * @return void
1718
     */
1719
    protected static function _populateIp()
1720
    {
1721
        if (!isset(static::$_pattern['IPv6'])) {
1722
            $pattern = '((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}';
1723
            $pattern .= '(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})';
1724
            $pattern .= '|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})';
1725
            $pattern .= '(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)';
1726
            $pattern .= '{4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
1727
            $pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}';
1728
            $pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|';
1729
            $pattern .= '((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}';
1730
            $pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
1731
            $pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4})';
1732
            $pattern .= '{0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)';
1733
            $pattern .= '|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]';
1734
            $pattern .= '\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4})';
1735
            $pattern .= '{1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?';
1736
1737
            static::$_pattern['IPv6'] = $pattern;
1738
        }
1739
        if (!isset(static::$_pattern['IPv4'])) {
1740
            $pattern = '(?:(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])';
1741
            static::$_pattern['IPv4'] = $pattern;
1742
        }
1743
    }
1744
1745
    /**
1746
     * Reset internal variables for another validation run.
1747
     *
1748
     * @return void
1749
     */
1750
    protected static function _reset()
1751
    {
1752
        static::$errors = [];
1753
    }
1754
}
1755