1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 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: 28: 29: 30:
31: class Validation
32: {
33:
34: 35: 36:
37: const DEFAULT_LOCALE = 'en_US';
38:
39: 40: 41:
42: const COMPARE_SAME = '===';
43:
44: 45: 46:
47: const COMPARE_NOT_SAME = '!==';
48:
49: 50: 51:
52: const COMPARE_EQUAL = '==';
53:
54: 55: 56:
57: const COMPARE_NOT_EQUAL = '!=';
58:
59: 60: 61:
62: const COMPARE_GREATER = '>';
63:
64: 65: 66:
67: const COMPARE_GREATER_OR_EQUAL = '>=';
68:
69: 70: 71:
72: const COMPARE_LESS = '<';
73:
74: 75: 76:
77: const COMPARE_LESS_OR_EQUAL = '<=';
78:
79: 80: 81: 82: 83:
84: protected static $_pattern = [
85: '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,})',
86: 'latitude' => '[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)',
87: 'longitude' => '[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)',
88: ];
89:
90: 91: 92: 93: 94: 95:
96: public static $errors = [];
97:
98: 99: 100: 101: 102: 103: 104: 105:
106: public static function notEmpty($check)
107: {
108: deprecationWarning(
109: 'Validation::notEmpty() is deprecated. ' .
110: 'Use Validation::notBlank() instead.'
111: );
112:
113: return static::notBlank($check);
114: }
115:
116: 117: 118: 119: 120: 121: 122: 123:
124: public static function notBlank($check)
125: {
126: if (empty($check) && !is_bool($check) && !is_numeric($check)) {
127: return false;
128: }
129:
130: return static::_check($check, '/[^\s]+/m');
131: }
132:
133: 134: 135: 136: 137: 138: 139: 140:
141: public static function alphaNumeric($check)
142: {
143: if (empty($check) && $check !== '0') {
144: return false;
145: }
146:
147: return self::_check($check, '/^[\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]+$/Du');
148: }
149:
150: 151: 152: 153: 154: 155: 156: 157: 158: 159:
160: public static function lengthBetween($check, $min, $max)
161: {
162: if (!is_string($check)) {
163: return false;
164: }
165: $length = mb_strlen($check);
166:
167: return ($length >= $min && $length <= $max);
168: }
169:
170: 171: 172: 173: 174: 175: 176: 177:
178: public static function blank($check)
179: {
180: deprecationWarning(
181: 'Validation::blank() is deprecated.'
182: );
183:
184: return !static::_check($check, '/[^\\s]/');
185: }
186:
187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199:
200: public static function cc($check, $type = 'fast', $deep = false, $regex = null)
201: {
202: deprecationWarning(
203: 'Validation::cc() is deprecated. ' .
204: 'Use Validation::creditCard() instead.'
205: );
206:
207: return static::creditCard($check, $type, $deep, $regex);
208: }
209:
210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222:
223: public static function creditCard($check, $type = 'fast', $deep = false, $regex = null)
224: {
225: if (!is_scalar($check)) {
226: return false;
227: }
228:
229: $check = str_replace(['-', ' '], '', $check);
230: if (mb_strlen($check) < 13) {
231: return false;
232: }
233:
234: if ($regex !== null && static::_check($check, $regex)) {
235: return !$deep || static::luhn($check);
236: }
237: $cards = [
238: 'all' => [
239: 'amex' => '/^3[47]\\d{13}$/',
240: 'bankcard' => '/^56(10\\d\\d|022[1-5])\\d{10}$/',
241: 'diners' => '/^(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})$/',
242: 'disc' => '/^(?:6011|650\\d)\\d{12}$/',
243: 'electron' => '/^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$/',
244: 'enroute' => '/^2(?:014|149)\\d{11}$/',
245: 'jcb' => '/^(3\\d{4}|2131|1800)\\d{11}$/',
246: 'maestro' => '/^(?:5020|6\\d{3})\\d{12}$/',
247: '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})$/',
248: 'solo' => '/^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$/',
249: '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})?)$/',
250: 'visa' => '/^4\\d{12}(\\d{3})?$/',
251: 'voyager' => '/^8699[0-9]{11}$/'
252: ],
253: '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})$/'
254: ];
255:
256: if (is_array($type)) {
257: foreach ($type as $value) {
258: $regex = $cards['all'][strtolower($value)];
259:
260: if (static::_check($check, $regex)) {
261: return static::luhn($check);
262: }
263: }
264: } elseif ($type === 'all') {
265: foreach ($cards['all'] as $value) {
266: $regex = $value;
267:
268: if (static::_check($check, $regex)) {
269: return static::luhn($check);
270: }
271: }
272: } else {
273: $regex = $cards['fast'];
274:
275: if (static::_check($check, $regex)) {
276: return static::luhn($check);
277: }
278: }
279:
280: return false;
281: }
282:
283: 284: 285: 286: 287: 288: 289: 290: 291: 292:
293: public static function numElements($check, $operator, $expectedCount)
294: {
295: if (!is_array($check) && !$check instanceof \Countable) {
296: return false;
297: }
298:
299: return self::comparison(count($check), $operator, $expectedCount);
300: }
301:
302: 303: 304: 305: 306: 307: 308: 309: 310: 311:
312: public static function comparison($check1, $operator, $check2)
313: {
314: if ((float)$check1 != $check1) {
315: return false;
316: }
317:
318: $message = 'Operator `%s` is deprecated, use constant `Validation::%s` instead.';
319:
320: $operator = str_replace([' ', "\t", "\n", "\r", "\0", "\x0B"], '', strtolower($operator));
321: switch ($operator) {
322: case 'isgreater':
323: 324: 325:
326: deprecationWarning(sprintf($message, $operator, 'COMPARE_GREATER'));
327:
328: case static::COMPARE_GREATER:
329: if ($check1 > $check2) {
330: return true;
331: }
332: break;
333: case 'isless':
334: 335: 336:
337: deprecationWarning(sprintf($message, $operator, 'COMPARE_LESS'));
338:
339: case static::COMPARE_LESS:
340: if ($check1 < $check2) {
341: return true;
342: }
343: break;
344: case 'greaterorequal':
345: 346: 347:
348: deprecationWarning(sprintf($message, $operator, 'COMPARE_GREATER_OR_EQUAL'));
349:
350: case static::COMPARE_GREATER_OR_EQUAL:
351: if ($check1 >= $check2) {
352: return true;
353: }
354: break;
355: case 'lessorequal':
356: 357: 358:
359: deprecationWarning(sprintf($message, $operator, 'COMPARE_LESS_OR_EQUAL'));
360:
361: case static::COMPARE_LESS_OR_EQUAL:
362: if ($check1 <= $check2) {
363: return true;
364: }
365: break;
366: case 'equalto':
367: 368: 369:
370: deprecationWarning(sprintf($message, $operator, 'COMPARE_EQUAL'));
371:
372: case static::COMPARE_EQUAL:
373: if ($check1 == $check2) {
374: return true;
375: }
376: break;
377: case 'notequal':
378: 379: 380:
381: deprecationWarning(sprintf($message, $operator, 'COMPARE_NOT_EQUAL'));
382:
383: case static::COMPARE_NOT_EQUAL:
384: if ($check1 != $check2) {
385: return true;
386: }
387: break;
388: case static::COMPARE_SAME:
389: if ($check1 === $check2) {
390: return true;
391: }
392: break;
393: case static::COMPARE_NOT_SAME:
394: if ($check1 !== $check2) {
395: return true;
396: }
397: break;
398: default:
399: static::$errors[] = 'You must define the $operator parameter for Validation::comparison()';
400: }
401:
402: return false;
403: }
404:
405: 406: 407: 408: 409: 410: 411: 412: 413: 414:
415: public static function compareWith($check, $field, $context)
416: {
417: return self::compareFields($check, $field, static::COMPARE_SAME, $context);
418: }
419:
420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431:
432: public static function compareFields($check, $field, $operator, $context)
433: {
434: if (!isset($context['data'][$field])) {
435: return false;
436: }
437:
438: return static::comparison($check, $operator, $context['data'][$field]);
439: }
440:
441: 442: 443: 444: 445: 446: 447: 448: 449:
450: public static function containsNonAlphaNumeric($check, $count = 1)
451: {
452: if (!is_scalar($check)) {
453: return false;
454: }
455:
456: $matches = preg_match_all('/[^a-zA-Z0-9]/', $check);
457:
458: return $matches >= $count;
459: }
460:
461: 462: 463: 464: 465: 466: 467:
468: public static function custom($check, $regex = null)
469: {
470: if ($regex === null) {
471: static::$errors[] = 'You must define a regular expression for Validation::custom()';
472:
473: return false;
474: }
475:
476: return static::_check($check, $regex);
477: }
478:
479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502:
503: public static function date($check, $format = 'ymd', $regex = null)
504: {
505: if ($check instanceof DateTimeInterface) {
506: return true;
507: }
508: if (is_object($check)) {
509: return false;
510: }
511: if (is_array($check)) {
512: $check = static::_getDateString($check);
513: $format = 'ymd';
514: }
515:
516: if ($regex !== null) {
517: return static::_check($check, $regex);
518: }
519: $month = '(0[123456789]|10|11|12)';
520: $separator = '([- /.])';
521: $fourDigitYear = '(([1][8-9][0-9][0-9])|([2][0-9][0-9][0-9]))';
522: $twoDigitYear = '([0-9]{2})';
523: $year = '(?:' . $fourDigitYear . '|' . $twoDigitYear . ')';
524:
525: $regex['dmy'] = '%^(?:(?:31(\\/|-|\\.|\\x20)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)' .
526: $separator . '(?:0?[1,3-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:29' .
527: $separator . '0?2\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\\d|2[0-8])' .
528: $separator . '(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$%';
529:
530: $regex['mdy'] = '%^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.|\\x20)31)\\1|(?:(?:0?[13-9]|1[0-2])' .
531: $separator . '(?:29|30)\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:0?2' . $separator . '29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))' .
532: $separator . '(?:0?[1-9]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$%';
533:
534: $regex['ymd'] = '%^(?:(?:(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))' .
535: $separator . '(?:0?2\\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\\d)?\\d{2})' .
536: $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]))))$%';
537:
538: $regex['dMy'] = '/^((31(?!\\ (Feb(ruary)?|Apr(il)?|June?|(Sep(?=\\b|t)t?|Nov)(ember)?)))|((30|29)(?!\\ Feb(ruary)?))|(29(?=\\ Feb(ruary)?\\ (((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))|(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)?)\\ ((1[6-9]|[2-9]\\d)\\d{2})$/';
539:
540: $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(?=,?\\ ((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))))\\,?\\ ((1[6-9]|[2-9]\\d)\\d{2}))$/';
541:
542: $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)?)' .
543: $separator . '((1[6-9]|[2-9]\\d)\\d{2})$%';
544:
545: $regex['my'] = '%^(' . $month . $separator . $year . ')$%';
546: $regex['ym'] = '%^(' . $year . $separator . $month . ')$%';
547: $regex['y'] = '%^(' . $fourDigitYear . ')$%';
548:
549: $format = is_array($format) ? array_values($format) : [$format];
550: foreach ($format as $key) {
551: if (static::_check($check, $regex[$key]) === true) {
552: return true;
553: }
554: }
555:
556: return false;
557: }
558:
559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570:
571: public static function datetime($check, $dateFormat = 'ymd', $regex = null)
572: {
573: if ($check instanceof DateTimeInterface) {
574: return true;
575: }
576: if (is_object($check)) {
577: return false;
578: }
579: $valid = false;
580: if (is_array($check)) {
581: $check = static::_getDateString($check);
582: $dateFormat = 'ymd';
583: }
584: $parts = explode(' ', $check);
585: if (!empty($parts) && count($parts) > 1) {
586: $date = rtrim(array_shift($parts), ',');
587: $time = implode(' ', $parts);
588: $valid = static::date($date, $dateFormat, $regex) && static::time($time);
589: }
590:
591: return $valid;
592: }
593:
594: 595: 596: 597: 598: 599: 600: 601:
602: public static function time($check)
603: {
604: if ($check instanceof DateTimeInterface) {
605: return true;
606: }
607: if (is_array($check)) {
608: $check = static::_getDateString($check);
609: }
610:
611: 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}$%');
612: }
613:
614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624:
625: public static function localizedTime($check, $type = 'datetime', $format = null)
626: {
627: if ($check instanceof DateTimeInterface) {
628: return true;
629: }
630: if (is_object($check)) {
631: return false;
632: }
633: static $methods = [
634: 'date' => 'parseDate',
635: 'time' => 'parseTime',
636: 'datetime' => 'parseDateTime',
637: ];
638: if (empty($methods[$type])) {
639: throw new InvalidArgumentException('Unsupported parser type given.');
640: }
641: $method = $methods[$type];
642:
643: return (Time::$method($check, $format) !== null);
644: }
645:
646: 647: 648: 649: 650: 651: 652: 653: 654:
655: public static function boolean($check, array $booleanValues = [])
656: {
657: if (!$booleanValues) {
658: $booleanValues = [true, false, 0, 1, '0', '1'];
659: }
660:
661: return in_array($check, $booleanValues, true);
662: }
663:
664: 665: 666: 667: 668: 669: 670: 671: 672:
673: public static function truthy($check, array $truthyValues = [])
674: {
675: if (!$truthyValues) {
676: $truthyValues = [true, 1, '1'];
677: }
678:
679: return in_array($check, $truthyValues, true);
680: }
681:
682: 683: 684: 685: 686: 687: 688: 689: 690:
691: public static function falsey($check, array $falseyValues = [])
692: {
693: if (!$falseyValues) {
694: $falseyValues = [false, 0, '0'];
695: }
696:
697: return in_array($check, $falseyValues, true);
698: }
699:
700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713:
714: public static function decimal($check, $places = null, $regex = null)
715: {
716: if ($regex === null) {
717: $lnum = '[0-9]+';
718: $dnum = "[0-9]*[\.]{$lnum}";
719: $sign = '[+-]?';
720: $exp = "(?:[eE]{$sign}{$lnum})?";
721:
722: if ($places === null) {
723: $regex = "/^{$sign}(?:{$lnum}|{$dnum}){$exp}$/";
724: } elseif ($places === true) {
725: if (is_float($check) && floor($check) === $check) {
726: $check = sprintf('%.1f', $check);
727: }
728: $regex = "/^{$sign}{$dnum}{$exp}$/";
729: } elseif (is_numeric($places)) {
730: $places = '[0-9]{' . $places . '}';
731: $dnum = "(?:[0-9]*[\.]{$places}|{$lnum}[\.]{$places})";
732: $regex = "/^{$sign}{$dnum}{$exp}$/";
733: }
734: }
735:
736:
737: $locale = ini_get('intl.default_locale') ?: static::DEFAULT_LOCALE;
738: $formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL);
739: $decimalPoint = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
740: $groupingSep = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
741:
742: $check = str_replace([$groupingSep, $decimalPoint], ['', '.'], $check);
743:
744: return static::_check($check, $regex);
745: }
746:
747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757:
758: public static function email($check, $deep = false, $regex = null)
759: {
760: if (!is_string($check)) {
761: return false;
762: }
763:
764: if ($regex === null) {
765: $regex = '/^[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' . self::$_pattern['hostname'] . '$/ui';
766: }
767: $return = static::_check($check, $regex);
768: if ($deep === false || $deep === null) {
769: return $return;
770: }
771:
772: if ($return === true && preg_match('/@(' . static::$_pattern['hostname'] . ')$/i', $check, $regs)) {
773: if (function_exists('getmxrr') && getmxrr($regs[1], $mxhosts)) {
774: return true;
775: }
776: if (function_exists('checkdnsrr') && checkdnsrr($regs[1], 'MX')) {
777: return true;
778: }
779:
780: return is_array(gethostbynamel($regs[1] . '.'));
781: }
782:
783: return false;
784: }
785:
786: 787: 788: 789: 790: 791: 792:
793: public static function equalTo($check, $comparedTo)
794: {
795: return ($check === $comparedTo);
796: }
797:
798: 799: 800: 801: 802: 803: 804:
805: public static function extension($check, $extensions = ['gif', 'jpeg', 'png', 'jpg'])
806: {
807: if ($check instanceof UploadedFileInterface) {
808: return static::extension($check->getClientFilename(), $extensions);
809: }
810: if (is_array($check)) {
811: $check = isset($check['name']) ? $check['name'] : array_shift($check);
812:
813: return static::extension($check, $extensions);
814: }
815: $extension = strtolower(pathinfo($check, PATHINFO_EXTENSION));
816: foreach ($extensions as $value) {
817: if ($extension === strtolower($value)) {
818: return true;
819: }
820: }
821:
822: return false;
823: }
824:
825: 826: 827: 828: 829: 830: 831:
832: public static function ip($check, $type = 'both')
833: {
834: $type = strtolower($type);
835: $flags = 0;
836: if ($type === 'ipv4') {
837: $flags = FILTER_FLAG_IPV4;
838: }
839: if ($type === 'ipv6') {
840: $flags = FILTER_FLAG_IPV6;
841: }
842:
843: return (bool)filter_var($check, FILTER_VALIDATE_IP, ['flags' => $flags]);
844: }
845:
846: 847: 848: 849: 850: 851: 852:
853: public static function minLength($check, $min)
854: {
855: return mb_strlen($check) >= $min;
856: }
857:
858: 859: 860: 861: 862: 863: 864:
865: public static function maxLength($check, $max)
866: {
867: return mb_strlen($check) <= $max;
868: }
869:
870: 871: 872: 873: 874: 875: 876:
877: public static function minLengthBytes($check, $min)
878: {
879: return strlen($check) >= $min;
880: }
881:
882: 883: 884: 885: 886: 887: 888:
889: public static function maxLengthBytes($check, $max)
890: {
891: return strlen($check) <= $max;
892: }
893:
894: 895: 896: 897: 898: 899: 900:
901: public static function money($check, $symbolPosition = 'left')
902: {
903: $money = '(?!0,?\d)(?:\d{1,3}(?:([, .])\d{3})?(?:\1\d{3})*|(?:\d+))((?!\1)[,.]\d{1,2})?';
904: if ($symbolPosition === 'right') {
905: $regex = '/^' . $money . '(?<!\x{00a2})\p{Sc}?$/u';
906: } else {
907: $regex = '/^(?!\x{00a2})\p{Sc}?' . $money . '$/u';
908: }
909:
910: return static::_check($check, $regex);
911: }
912:
913: 914: 915: 916: 917: 918: 919: 920: 921: 922: 923: 924: 925: 926:
927: public static function multiple($check, array $options = [], $caseInsensitive = false)
928: {
929: $defaults = ['in' => null, 'max' => null, 'min' => null];
930: $options += $defaults;
931:
932: $check = array_filter((array)$check, function ($value) {
933: return ($value || is_numeric($value));
934: });
935: if (empty($check)) {
936: return false;
937: }
938: if ($options['max'] && count($check) > $options['max']) {
939: return false;
940: }
941: if ($options['min'] && count($check) < $options['min']) {
942: return false;
943: }
944: if ($options['in'] && is_array($options['in'])) {
945: if ($caseInsensitive) {
946: $options['in'] = array_map('mb_strtolower', $options['in']);
947: }
948: foreach ($check as $val) {
949: $strict = !is_numeric($val);
950: if ($caseInsensitive) {
951: $val = mb_strtolower($val);
952: }
953: if (!in_array((string)$val, $options['in'], $strict)) {
954: return false;
955: }
956: }
957: }
958:
959: return true;
960: }
961:
962: 963: 964: 965: 966: 967:
968: public static function numeric($check)
969: {
970: return is_numeric($check);
971: }
972:
973: 974: 975: 976: 977: 978: 979: 980:
981: public static function naturalNumber($check, $allowZero = false)
982: {
983: $regex = $allowZero ? '/^(?:0|[1-9][0-9]*)$/' : '/^[1-9][0-9]*$/';
984:
985: return static::_check($check, $regex);
986: }
987:
988: 989: 990: 991: 992: 993: 994: 995: 996: 997: 998: 999:
1000: public static function range($check, $lower = null, $upper = null)
1001: {
1002: if (!is_numeric($check)) {
1003: return false;
1004: }
1005: if ((float)$check != $check) {
1006: return false;
1007: }
1008: if (isset($lower, $upper)) {
1009: return ($check >= $lower && $check <= $upper);
1010: }
1011:
1012: return is_finite($check);
1013: }
1014:
1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032:
1033: public static function url($check, $strict = false)
1034: {
1035: static::_populateIp();
1036:
1037: $emoji = '\x{1F190}-\x{1F9EF}';
1038: $alpha = '0-9\p{L}\p{N}' . $emoji;
1039: $hex = '(%[0-9a-f]{2})';
1040: $subDelimiters = preg_quote('/!"$&\'()*+,-.@_:;=~[]', '/');
1041: $path = '([' . $subDelimiters . $alpha . ']|' . $hex . ')';
1042: $fragmentAndQuery = '([\?' . $subDelimiters . $alpha . ']|' . $hex . ')';
1043: $regex = '/^(?:(?:https?|ftps?|sftp|file|news|gopher):\/\/)' . (!empty($strict) ? '' : '?') .
1044: '(?:' . static::$_pattern['IPv4'] . '|\[' . static::$_pattern['IPv6'] . '\]|' . static::$_pattern['hostname'] . ')(?::[1-9][0-9]{0,4})?' .
1045: '(?:\/' . $path . '*)?' .
1046: '(?:\?' . $fragmentAndQuery . '*)?' .
1047: '(?:#' . $fragmentAndQuery . '*)?$/iu';
1048:
1049: return static::_check($check, $regex);
1050: }
1051:
1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059:
1060: public static function inList($check, array $list, $caseInsensitive = false)
1061: {
1062: if ($caseInsensitive) {
1063: $list = array_map('mb_strtolower', $list);
1064: $check = mb_strtolower($check);
1065: } else {
1066: $list = array_map('strval', $list);
1067: }
1068:
1069: return in_array((string)$check, $list, true);
1070: }
1071:
1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081:
1082: public static function userDefined($check, $object, $method, $args = null)
1083: {
1084: deprecationWarning(
1085: 'Validation::userDefined() is deprecated. ' .
1086: 'You can just set a callable for `rule` key when adding validators.'
1087: );
1088:
1089: return $object->$method($check, $args);
1090: }
1091:
1092: 1093: 1094: 1095: 1096: 1097:
1098: public static function uuid($check)
1099: {
1100: $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}$/';
1101:
1102: return self::_check($check, $regex);
1103: }
1104:
1105: 1106: 1107: 1108: 1109: 1110: 1111:
1112: protected static function _check($check, $regex)
1113: {
1114: return is_string($regex) && is_scalar($check) && preg_match($regex, $check);
1115: }
1116:
1117: 1118: 1119: 1120: 1121: 1122: 1123:
1124: public static function luhn($check)
1125: {
1126: if (!is_scalar($check) || (int)$check === 0) {
1127: return false;
1128: }
1129: $sum = 0;
1130: $length = strlen($check);
1131:
1132: for ($position = 1 - ($length % 2); $position < $length; $position += 2) {
1133: $sum += $check[$position];
1134: }
1135:
1136: for ($position = ($length % 2); $position < $length; $position += 2) {
1137: $number = (int)$check[$position] * 2;
1138: $sum += ($number < 10) ? $number : $number - 9;
1139: }
1140:
1141: return ($sum % 10 === 0);
1142: }
1143:
1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156:
1157: public static function mimeType($check, $mimeTypes = [])
1158: {
1159: $file = static::getFilename($check);
1160: if ($file === false) {
1161: return false;
1162: }
1163:
1164: if (!function_exists('finfo_open')) {
1165: throw new LogicException('ext/fileinfo is required for validating file mime types');
1166: }
1167:
1168: if (!is_file($file)) {
1169: throw new RuntimeException('Cannot validate mimetype for a missing file');
1170: }
1171:
1172: $finfo = finfo_open(FILEINFO_MIME);
1173: $finfo = finfo_file($finfo, $file);
1174:
1175: if (!$finfo) {
1176: throw new RuntimeException('Can not determine the mimetype.');
1177: }
1178:
1179: list($mime) = explode(';', $finfo);
1180:
1181: if (is_string($mimeTypes)) {
1182: return self::_check($mime, $mimeTypes);
1183: }
1184:
1185: foreach ($mimeTypes as $key => $val) {
1186: $mimeTypes[$key] = strtolower($val);
1187: }
1188:
1189: return in_array($mime, $mimeTypes);
1190: }
1191:
1192: 1193: 1194: 1195: 1196: 1197: 1198:
1199: protected static function getFilename($check)
1200: {
1201: if ($check instanceof UploadedFileInterface) {
1202: try {
1203:
1204: return $check->getStream()->getMetadata('uri');
1205: } catch (RuntimeException $e) {
1206: return false;
1207: }
1208: }
1209: if (is_array($check) && isset($check['tmp_name'])) {
1210: return $check['tmp_name'];
1211: }
1212:
1213: if (is_string($check)) {
1214: return $check;
1215: }
1216:
1217: return false;
1218: }
1219:
1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230: 1231:
1232: public static function fileSize($check, $operator = null, $size = null)
1233: {
1234: $file = static::getFilename($check);
1235: if ($file === false) {
1236: return false;
1237: }
1238:
1239: if (is_string($size)) {
1240: $size = Text::parseFileSize($size);
1241: }
1242: $filesize = filesize($file);
1243:
1244: return static::comparison($filesize, $operator, $size);
1245: }
1246:
1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254:
1255: public static function uploadError($check, $allowNoFile = false)
1256: {
1257: if ($check instanceof UploadedFileInterface) {
1258: $code = $check->getError();
1259: } elseif (is_array($check) && isset($check['error'])) {
1260: $code = $check['error'];
1261: } else {
1262: $code = $check;
1263: }
1264: if ($allowNoFile) {
1265: return in_array((int)$code, [UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE], true);
1266: }
1267:
1268: return (int)$code === UPLOAD_ERR_OK;
1269: }
1270:
1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282: 1283: 1284: 1285: 1286: 1287: 1288: 1289: 1290:
1291: public static function uploadedFile($file, array $options = [])
1292: {
1293: $options += [
1294: 'minSize' => null,
1295: 'maxSize' => null,
1296: 'types' => null,
1297: 'optional' => false,
1298: ];
1299: if (!is_array($file) && !($file instanceof UploadedFileInterface)) {
1300: return false;
1301: }
1302: $error = $isUploaded = false;
1303: if ($file instanceof UploadedFileInterface) {
1304: $error = $file->getError();
1305: $isUploaded = true;
1306: }
1307: if (is_array($file)) {
1308: $keys = ['error', 'name', 'size', 'tmp_name', 'type'];
1309: ksort($file);
1310: if (array_keys($file) != $keys) {
1311: return false;
1312: }
1313: $error = (int)$file['error'];
1314: $isUploaded = is_uploaded_file($file['tmp_name']);
1315: }
1316:
1317: if (!static::uploadError($file, $options['optional'])) {
1318: return false;
1319: }
1320: if ($options['optional'] && $error === UPLOAD_ERR_NO_FILE) {
1321: return true;
1322: }
1323: if (isset($options['minSize']) && !static::fileSize($file, static::COMPARE_GREATER_OR_EQUAL, $options['minSize'])) {
1324: return false;
1325: }
1326: if (isset($options['maxSize']) && !static::fileSize($file, static::COMPARE_LESS_OR_EQUAL, $options['maxSize'])) {
1327: return false;
1328: }
1329: if (isset($options['types']) && !static::mimeType($file, $options['types'])) {
1330: return false;
1331: }
1332:
1333: return $isUploaded;
1334: }
1335:
1336: 1337: 1338: 1339: 1340: 1341: 1342: 1343:
1344: public static function imageSize($file, $options)
1345: {
1346: if (!isset($options['height']) && !isset($options['width'])) {
1347: throw new InvalidArgumentException('Invalid image size validation parameters! Missing `width` and / or `height`.');
1348: }
1349:
1350: $filename = static::getFilename($file);
1351:
1352: list($width, $height) = getimagesize($filename);
1353:
1354: $validHeight = $validWidth = null;
1355:
1356: if (isset($options['height'])) {
1357: $validHeight = self::comparison($height, $options['height'][0], $options['height'][1]);
1358: }
1359: if (isset($options['width'])) {
1360: $validWidth = self::comparison($width, $options['width'][0], $options['width'][1]);
1361: }
1362: if ($validHeight !== null && $validWidth !== null) {
1363: return ($validHeight && $validWidth);
1364: }
1365: if ($validHeight !== null) {
1366: return $validHeight;
1367: }
1368: if ($validWidth !== null) {
1369: return $validWidth;
1370: }
1371:
1372: throw new InvalidArgumentException('The 2nd argument is missing the `width` and / or `height` options.');
1373: }
1374:
1375: 1376: 1377: 1378: 1379: 1380: 1381: 1382:
1383: public static function imageWidth($file, $operator, $width)
1384: {
1385: return self::imageSize($file, [
1386: 'width' => [
1387: $operator,
1388: $width
1389: ]
1390: ]);
1391: }
1392:
1393: 1394: 1395: 1396: 1397: 1398: 1399: 1400:
1401: public static function imageHeight($file, $operator, $height)
1402: {
1403: return self::imageSize($file, [
1404: 'height' => [
1405: $operator,
1406: $height
1407: ]
1408: ]);
1409: }
1410:
1411: 1412: 1413: 1414: 1415: 1416: 1417: 1418: 1419: 1420: 1421: 1422: 1423: 1424: 1425: 1426: 1427:
1428: public static function geoCoordinate($value, array $options = [])
1429: {
1430: $options += [
1431: 'format' => 'both',
1432: 'type' => 'latLong'
1433: ];
1434: if ($options['type'] !== 'latLong') {
1435: throw new RuntimeException(sprintf(
1436: 'Unsupported coordinate type "%s". Use "latLong" instead.',
1437: $options['type']
1438: ));
1439: }
1440: $pattern = '/^' . self::$_pattern['latitude'] . ',\s*' . self::$_pattern['longitude'] . '$/';
1441: if ($options['format'] === 'long') {
1442: $pattern = '/^' . self::$_pattern['longitude'] . '$/';
1443: }
1444: if ($options['format'] === 'lat') {
1445: $pattern = '/^' . self::$_pattern['latitude'] . '$/';
1446: }
1447:
1448: return (bool)preg_match($pattern, $value);
1449: }
1450:
1451: 1452: 1453: 1454: 1455: 1456: 1457: 1458: 1459:
1460: public static function latitude($value, array $options = [])
1461: {
1462: $options['format'] = 'lat';
1463:
1464: return self::geoCoordinate($value, $options);
1465: }
1466:
1467: 1468: 1469: 1470: 1471: 1472: 1473: 1474: 1475:
1476: public static function longitude($value, array $options = [])
1477: {
1478: $options['format'] = 'long';
1479:
1480: return self::geoCoordinate($value, $options);
1481: }
1482:
1483: 1484: 1485: 1486: 1487: 1488: 1489: 1490:
1491: public static function ascii($value)
1492: {
1493: if (!is_string($value)) {
1494: return false;
1495: }
1496:
1497: return strlen($value) <= mb_strlen($value, 'utf-8');
1498: }
1499:
1500: 1501: 1502: 1503: 1504: 1505: 1506: 1507: 1508: 1509: 1510: 1511: 1512: 1513: 1514:
1515: public static function utf8($value, array $options = [])
1516: {
1517: if (!is_string($value)) {
1518: return false;
1519: }
1520: $options += ['extended' => false];
1521: if ($options['extended']) {
1522: return true;
1523: }
1524:
1525: return preg_match('/[\x{10000}-\x{10FFFF}]/u', $value) === 0;
1526: }
1527:
1528: 1529: 1530: 1531: 1532: 1533: 1534: 1535: 1536:
1537: public static function isInteger($value)
1538: {
1539: if (!is_scalar($value) || is_float($value)) {
1540: return false;
1541: }
1542: if (is_int($value)) {
1543: return true;
1544: }
1545:
1546: return (bool)preg_match('/^-?[0-9]+$/', $value);
1547: }
1548:
1549: 1550: 1551: 1552: 1553: 1554:
1555: public static function isArray($value)
1556: {
1557: return is_array($value);
1558: }
1559:
1560: 1561: 1562: 1563: 1564: 1565: 1566: 1567: 1568:
1569: public static function isScalar($value)
1570: {
1571: return is_scalar($value);
1572: }
1573:
1574: 1575: 1576: 1577: 1578: 1579:
1580: public static function hexColor($check)
1581: {
1582: return static::_check($check, '/^#[0-9a-f]{6}$/iD');
1583: }
1584:
1585: 1586: 1587: 1588: 1589: 1590: 1591: 1592: 1593:
1594: public static function iban($check)
1595: {
1596: if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $check)) {
1597: return false;
1598: }
1599:
1600: $country = substr($check, 0, 2);
1601: $checkInt = intval(substr($check, 2, 2));
1602: $account = substr($check, 4);
1603: $search = range('A', 'Z');
1604: $replace = [];
1605: foreach (range(10, 35) as $tmp) {
1606: $replace[] = strval($tmp);
1607: }
1608: $numStr = str_replace($search, $replace, $account . $country . '00');
1609: $checksum = intval(substr($numStr, 0, 1));
1610: $numStrLength = strlen($numStr);
1611: for ($pos = 1; $pos < $numStrLength; $pos++) {
1612: $checksum *= 10;
1613: $checksum += intval(substr($numStr, $pos, 1));
1614: $checksum %= 97;
1615: }
1616:
1617: return ((98 - $checksum) === $checkInt);
1618: }
1619:
1620: 1621: 1622: 1623: 1624: 1625: 1626: 1627:
1628: protected static function _getDateString($value)
1629: {
1630: $formatted = '';
1631: if (isset($value['year'], $value['month'], $value['day']) &&
1632: (is_numeric($value['year']) && is_numeric($value['month']) && is_numeric($value['day']))
1633: ) {
1634: $formatted .= sprintf('%d-%02d-%02d ', $value['year'], $value['month'], $value['day']);
1635: }
1636:
1637: if (isset($value['hour'])) {
1638: if (isset($value['meridian']) && (int)$value['hour'] === 12) {
1639: $value['hour'] = 0;
1640: }
1641: if (isset($value['meridian'])) {
1642: $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12;
1643: }
1644: $value += ['minute' => 0, 'second' => 0];
1645: if (is_numeric($value['hour']) && is_numeric($value['minute']) && is_numeric($value['second'])) {
1646: $formatted .= sprintf('%02d:%02d:%02d', $value['hour'], $value['minute'], $value['second']);
1647: }
1648: }
1649:
1650: return trim($formatted);
1651: }
1652:
1653: 1654: 1655: 1656: 1657:
1658: protected static function _populateIp()
1659: {
1660: if (!isset(static::$_pattern['IPv6'])) {
1661: $pattern = '((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}';
1662: $pattern .= '(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})';
1663: $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})';
1664: $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}:)';
1665: $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}))';
1666: $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}';
1667: $pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|';
1668: $pattern .= '((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}';
1669: $pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
1670: $pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4})';
1671: $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})?)';
1672: $pattern .= '|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]';
1673: $pattern .= '\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4})';
1674: $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})))(%.+)?';
1675:
1676: static::$_pattern['IPv6'] = $pattern;
1677: }
1678: if (!isset(static::$_pattern['IPv4'])) {
1679: $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])';
1680: static::$_pattern['IPv4'] = $pattern;
1681: }
1682: }
1683:
1684: 1685: 1686: 1687: 1688:
1689: protected static function _reset()
1690: {
1691: static::$errors = [];
1692: }
1693: }
1694: