CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.8 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.8
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • RulesProvider
  • Validation
  • ValidationRule
  • ValidationSet
  • Validator

Interfaces

  • ValidatableInterface
  • ValidatorAwareInterface

Traits

  • ValidatorAwareTrait
   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:             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:      * 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) {
 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:      * 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'][$field])) {
 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) {
 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) {
 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) {
 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);
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:         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:      * 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');
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:         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:      * Lazily populate the IP address patterns used for validations
1715:      *
1716:      * @return void
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:      * Reset internal variables for another validation run.
1746:      *
1747:      * @return void
1748:      */
1749:     protected static function _reset()
1750:     {
1751:         static::$errors = [];
1752:     }
1753: }
1754: 
Follow @CakePHP
#IRC
OpenHub
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Logos & Trademarks
  • Community
  • Team
  • Issues (Github)
  • YouTube Channel
  • Get Involved
  • Bakery
  • Featured Resources
  • Newsletter
  • Certification
  • My CakePHP
  • CakeFest
  • Facebook
  • Twitter
  • Help & Support
  • Forum
  • Stack Overflow
  • IRC
  • Slack
  • Paid Support

Generated using CakePHP API Docs