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: const DEFAULT_LOCALE = 'en_US';
37:
38: 39: 40:
41: const COMPARE_SAME = '===';
42:
43: 44: 45:
46: const COMPARE_NOT_SAME = '!==';
47:
48: 49: 50:
51: const COMPARE_EQUAL = '==';
52:
53: 54: 55:
56: const COMPARE_NOT_EQUAL = '!=';
57:
58: 59: 60:
61: const COMPARE_GREATER = '>';
62:
63: 64: 65:
66: const COMPARE_GREATER_OR_EQUAL = '>=';
67:
68: 69: 70:
71: const COMPARE_LESS = '<';
72:
73: 74: 75:
76: const COMPARE_LESS_OR_EQUAL = '<=';
77:
78: 79: 80:
81: const DATETIME_ISO8601 = 'iso8601';
82:
83: 84: 85: 86: 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: 96: 97: 98: 99:
100: public static $errors = [];
101:
102: 103: 104: 105: 106: 107: 108: 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: 122: 123: 124: 125: 126: 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: 139: 140: 141: 142: 143: 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: 156: 157: 158: 159: 160: 161: 162: 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: 176: 177: 178: 179: 180: 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: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 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: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 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: 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: 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: 289: 290: 291: 292: 293: 294: 295: 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: 308: 309: 310: 311: 312: 313: 314: 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: 329:
330: deprecationWarning(sprintf($message, $operator, 'COMPARE_GREATER'));
331:
332: case static::COMPARE_GREATER:
333: if ($check1 > $check2) {
334: return true;
335: }
336: break;
337: case 'isless':
338: 339: 340:
341: deprecationWarning(sprintf($message, $operator, 'COMPARE_LESS'));
342:
343: case static::COMPARE_LESS:
344: if ($check1 < $check2) {
345: return true;
346: }
347: break;
348: case 'greaterorequal':
349: 350: 351:
352: deprecationWarning(sprintf($message, $operator, 'COMPARE_GREATER_OR_EQUAL'));
353:
354: case static::COMPARE_GREATER_OR_EQUAL:
355: if ($check1 >= $check2) {
356: return true;
357: }
358: break;
359: case 'lessorequal':
360: 361: 362:
363: deprecationWarning(sprintf($message, $operator, 'COMPARE_LESS_OR_EQUAL'));
364:
365: case static::COMPARE_LESS_OR_EQUAL:
366: if ($check1 <= $check2) {
367: return true;
368: }
369: break;
370: case 'equalto':
371: 372: 373:
374: deprecationWarning(sprintf($message, $operator, 'COMPARE_EQUAL'));
375:
376: case static::COMPARE_EQUAL:
377: if ($check1 == $check2) {
378: return true;
379: }
380: break;
381: case 'notequal':
382: 383: 384:
385: deprecationWarning(sprintf($message, $operator, 'COMPARE_NOT_EQUAL'));
386:
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) {
394: return true;
395: }
396: break;
397: case static::COMPARE_NOT_SAME:
398: if ($check1 !== $check2) {
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: 411: 412: 413: 414: 415: 416: 417: 418:
419: public static function compareWith($check, $field, $context)
420: {
421: return self::compareFields($check, $field, static::COMPARE_SAME, $context);
422: }
423:
424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435:
436: public static function compareFields($check, $field, $operator, $context)
437: {
438: if (!isset($context['data'][$field])) {
439: return false;
440: }
441:
442: return static::comparison($check, $operator, $context['data'][$field]);
443: }
444:
445: 446: 447: 448: 449: 450: 451: 452: 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: 467: 468: 469: 470: 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: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 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:
526: $fourDigitYear = '(?:(?!0000)[012]\d{3})';
527: $twoDigitYear = '(?:\d{2})';
528: $year = '(?:' . $fourDigitYear . '|' . $twoDigitYear . ')';
529:
530:
531: $leapYear = '(?:(?:(?:(?!0000)[012]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))';
532:
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: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 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: 616: 617: 618: 619: 620: 621: 622: 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: 640: 641: 642: 643: 644: 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: 660: 661: 662: 663: 664: 665: 666: 667: 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: 692: 693: 694: 695: 696: 697: 698:
699: public static function boolean($check, array $booleanValues = [])
700: {
701: if (!$booleanValues) {
702: $booleanValues = [true, false, 0, 1, '0', '1'];
703: }
704:
705: return in_array($check, $booleanValues, true);
706: }
707:
708: 709: 710: 711: 712: 713: 714: 715: 716:
717: public static function truthy($check, array $truthyValues = [])
718: {
719: if (!$truthyValues) {
720: $truthyValues = [true, 1, '1'];
721: }
722:
723: return in_array($check, $truthyValues, true);
724: }
725:
726: 727: 728: 729: 730: 731: 732: 733: 734:
735: public static function falsey($check, array $falseyValues = [])
736: {
737: if (!$falseyValues) {
738: $falseyValues = [false, 0, '0'];
739: }
740:
741: return in_array($check, $falseyValues, true);
742: }
743:
744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 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:
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: 793: 794: 795: 796: 797: 798: 799: 800: 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: 832: 833: 834: 835: 836:
837: public static function equalTo($check, $comparedTo)
838: {
839: return ($check === $comparedTo);
840: }
841:
842: 843: 844: 845: 846: 847: 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: 871: 872: 873: 874: 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: 892: 893: 894: 895: 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: 908: 909: 910: 911: 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: 924: 925: 926: 927: 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: 940: 941: 942: 943: 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: 956: 957: 958: 959: 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: 975: 976: 977: 978: 979: 980: 981: 982: 983: 984: 985: 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: 1024: 1025: 1026: 1027:
1028: public static function numeric($check)
1029: {
1030: return is_numeric($check);
1031: }
1032:
1033: 1034: 1035: 1036: 1037: 1038: 1039: 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: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 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: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 1085: 1086: 1087: 1088: 1089: 1090: 1091: 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: 1114: 1115: 1116: 1117: 1118: 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: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 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: 1154: 1155: 1156: 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: 1167: 1168: 1169: 1170: 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: 1179: 1180: 1181: 1182: 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: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 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);
1250: }
1251:
1252: 1253: 1254: 1255: 1256: 1257: 1258:
1259: protected static function getFilename($check)
1260: {
1261: if ($check instanceof UploadedFileInterface) {
1262: try {
1263:
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: 1282: 1283: 1284: 1285: 1286: 1287: 1288: 1289: 1290: 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: 1309: 1310: 1311: 1312: 1313: 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: 1333: 1334: 1335: 1336: 1337: 1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346: 1347: 1348: 1349: 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: if (isset($options['minSize']) && !static::fileSize($file, static::COMPARE_GREATER_OR_EQUAL, $options['minSize'])) {
1384: return false;
1385: }
1386: 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: 1398: 1399: 1400: 1401: 1402: 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: 1437: 1438: 1439: 1440: 1441: 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: 1455: 1456: 1457: 1458: 1459: 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: 1473: 1474: 1475: 1476: 1477: 1478: 1479: 1480: 1481: 1482: 1483: 1484: 1485: 1486: 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: 1513: 1514: 1515: 1516: 1517: 1518: 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: 1529: 1530: 1531: 1532: 1533: 1534: 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: 1545: 1546: 1547: 1548: 1549: 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: 1562: 1563: 1564: 1565: 1566: 1567: 1568: 1569: 1570: 1571: 1572: 1573: 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: 1590: 1591: 1592: 1593: 1594: 1595: 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: 1611: 1612: 1613: 1614:
1615: public static function isArray($value)
1616: {
1617: return is_array($value);
1618: }
1619:
1620: 1621: 1622: 1623: 1624: 1625: 1626: 1627: 1628:
1629: public static function isScalar($value)
1630: {
1631: return is_scalar($value);
1632: }
1633:
1634: 1635: 1636: 1637: 1638: 1639:
1640: public static function hexColor($check)
1641: {
1642: return static::_check($check, '/^#[0-9a-f]{6}$/iD');
1643: }
1644:
1645: 1646: 1647: 1648: 1649: 1650: 1651: 1652: 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: 1682: 1683: 1684: 1685: 1686: 1687:
1688: protected static function _getDateString($value)
1689: {
1690: $formatted = '';
1691: if (isset($value['year'], $value['month'], $value['day']) &&
1692: (is_numeric($value['year']) && is_numeric($value['month']) && is_numeric($value['day']))
1693: ) {
1694: $formatted .= sprintf('%d-%02d-%02d ', $value['year'], $value['month'], $value['day']);
1695: }
1696:
1697: if (isset($value['hour'])) {
1698: if (isset($value['meridian']) && (int)$value['hour'] === 12) {
1699: $value['hour'] = 0;
1700: }
1701: if (isset($value['meridian'])) {
1702: $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12;
1703: }
1704: $value += ['minute' => 0, 'second' => 0];
1705: if (is_numeric($value['hour']) && is_numeric($value['minute']) && is_numeric($value['second'])) {
1706: $formatted .= sprintf('%02d:%02d:%02d', $value['hour'], $value['minute'], $value['second']);
1707: }
1708: }
1709:
1710: return trim($formatted);
1711: }
1712:
1713: 1714: 1715: 1716: 1717:
1718: protected static function _populateIp()
1719: {
1720: if (!isset(static::$_pattern['IPv6'])) {
1721: $pattern = '((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}';
1722: $pattern .= '(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})';
1723: $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})';
1724: $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}:)';
1725: $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}))';
1726: $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}';
1727: $pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|';
1728: $pattern .= '((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}';
1729: $pattern .= '((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2}))';
1730: $pattern .= '{3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4})';
1731: $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})?)';
1732: $pattern .= '|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]';
1733: $pattern .= '\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4})';
1734: $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})))(%.+)?';
1735:
1736: static::$_pattern['IPv6'] = $pattern;
1737: }
1738: if (!isset(static::$_pattern['IPv4'])) {
1739: $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])';
1740: static::$_pattern['IPv4'] = $pattern;
1741: }
1742: }
1743:
1744: 1745: 1746: 1747: 1748:
1749: protected static function _reset()
1750: {
1751: static::$errors = [];
1752: }
1753: }
1754: