First Commit

This commit is contained in:
marco
2026-09-04 14:54:13 -03:00
commit 9c54bd3ac7
4914 changed files with 789004 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
<?php
namespace Rakit\Validation;
class Attribute
{
/** @var array */
protected $rules = [];
/** @var string */
protected $key;
/** @var string|null */
protected $alias;
/** @var \Rakit\Validation\Validation */
protected $validation;
/** @var bool */
protected $required = false;
/** @var \Rakit\Validation\Validation|null */
protected $primaryAttribute = null;
/** @var array */
protected $otherAttributes = [];
/** @var array */
protected $keyIndexes = [];
/**
* Constructor
*
* @param \Rakit\Validation\Validation $validation
* @param string $key
* @param string|null $alias
* @param array $rules
* @return void
*/
public function __construct(
Validation $validation,
string $key,
$alias = null,
array $rules = []
) {
$this->validation = $validation;
$this->alias = $alias;
$this->key = $key;
foreach ($rules as $rule) {
$this->addRule($rule);
}
}
/**
* Set the primary attribute
*
* @param \Rakit\Validation\Attribute $primaryAttribute
* @return void
*/
public function setPrimaryAttribute(Attribute $primaryAttribute)
{
$this->primaryAttribute = $primaryAttribute;
}
/**
* Set key indexes
*
* @param array $keyIndexes
* @return void
*/
public function setKeyIndexes(array $keyIndexes)
{
$this->keyIndexes = $keyIndexes;
}
/**
* Get primary attributes
*
* @return \Rakit\Validation\Attribute|null
*/
public function getPrimaryAttribute()
{
return $this->primaryAttribute;
}
/**
* Set other attributes
*
* @param array $otherAttributes
* @return void
*/
public function setOtherAttributes(array $otherAttributes)
{
$this->otherAttributes = [];
foreach ($otherAttributes as $otherAttribute) {
$this->addOtherAttribute($otherAttribute);
}
}
/**
* Add other attributes
*
* @param \Rakit\Validation\Attribute $otherAttribute
* @return void
*/
public function addOtherAttribute(Attribute $otherAttribute)
{
$this->otherAttributes[] = $otherAttribute;
}
/**
* Get other attributes
*
* @return array
*/
public function getOtherAttributes(): array
{
return $this->otherAttributes;
}
/**
* Add rule
*
* @param \Rakit\Validation\Rule $rule
* @return void
*/
public function addRule(Rule $rule)
{
$rule->setAttribute($this);
$rule->setValidation($this->validation);
$this->rules[$rule->getKey()] = $rule;
}
/**
* Get rule
*
* @param string $ruleKey
* @return void
*/
public function getRule(string $ruleKey)
{
return $this->hasRule($ruleKey)? $this->rules[$ruleKey] : null;
}
/**
* Get rules
*
* @return array
*/
public function getRules(): array
{
return $this->rules;
}
/**
* Check the $ruleKey has in the rule
*
* @param string $ruleKey
* @return bool
*/
public function hasRule(string $ruleKey): bool
{
return isset($this->rules[$ruleKey]);
}
/**
* Set required
*
* @param boolean $required
* @return void
*/
public function setRequired(bool $required)
{
$this->required = $required;
}
/**
* Set rule is required
*
* @return boolean
*/
public function isRequired(): bool
{
return $this->required;
}
/**
* Get key
*
* @return string
*/
public function getKey(): string
{
return $this->key;
}
/**
* Get key indexes
*
* @return array
*/
public function getKeyIndexes(): array
{
return $this->keyIndexes;
}
/**
* Get value
*
* @param string|null $key
* @return mixed
*/
public function getValue(string $key = null)
{
if ($key && $this->isArrayAttribute()) {
$key = $this->resolveSiblingKey($key);
}
if (!$key) {
$key = $this->getKey();
}
return $this->validation->getValue($key);
}
/**
* Get that is array attribute
*
* @return boolean
*/
public function isArrayAttribute(): bool
{
return count($this->getKeyIndexes()) > 0;
}
/**
* Check this attribute is using dot notation
*
* @return boolean
*/
public function isUsingDotNotation(): bool
{
return strpos($this->getKey(), '.') !== false;
}
/**
* Resolve sibling key
*
* @param string $key
* @return string
*/
public function resolveSiblingKey(string $key): string
{
$indexes = $this->getKeyIndexes();
$keys = explode("*", $key);
$countAsterisks = count($keys) - 1;
if (count($indexes) < $countAsterisks) {
$indexes = array_merge($indexes, array_fill(0, $countAsterisks - count($indexes), "*"));
}
$args = array_merge([str_replace("*", "%s", $key)], $indexes);
return call_user_func_array('sprintf', $args);
}
/**
* Get humanize key
*
* @return string
*/
public function getHumanizedKey()
{
$primaryAttribute = $this->getPrimaryAttribute();
$key = str_replace('_', ' ', $this->key);
// Resolve key from array validation
if ($primaryAttribute) {
$split = explode('.', $key);
$key = implode(' ', array_map(function ($word) {
if (is_numeric($word)) {
$word = $word + 1;
}
return Helper::snakeCase($word, ' ');
}, $split));
}
return ucfirst($key);
}
/**
* Set alias
*
* @param string $alias
* @return void
*/
public function setAlias(string $alias)
{
$this->alias = $alias;
}
/**
* Get alias
*
* @return string|null
*/
public function getAlias()
{
return $this->alias;
}
}
+247
View File
@@ -0,0 +1,247 @@
<?php
namespace Rakit\Validation;
class ErrorBag
{
/** @var array */
protected $messages = [];
/**
* Constructor
*
* @param array $messages
* @return void
*/
public function __construct(array $messages = [])
{
$this->messages = $messages;
}
/**
* Add message for given key and rule
*
* @param string $key
* @param string $rule
* @param string $message
* @return void
*/
public function add(string $key, string $rule, string $message)
{
if (!isset($this->messages[$key])) {
$this->messages[$key] = [];
}
$this->messages[$key][$rule] = $message;
}
/**
* Get messages count
*
* @return int
*/
public function count(): int
{
return count($this->all());
}
/**
* Check given key is existed
*
* @param string $key
* @return bool
*/
public function has(string $key): bool
{
list($key, $ruleName) = $this->parsekey($key);
if ($this->isWildcardKey($key)) {
$messages = $this->filterMessagesForWildcardKey($key, $ruleName);
return count(Helper::arrayDot($messages)) > 0;
} else {
$messages = isset($this->messages[$key])? $this->messages[$key] : null;
if (!$ruleName) {
return !empty($messages);
} else {
return !empty($messages) and isset($messages[$ruleName]);
}
}
}
/**
* Get the first value of array
*
* @param string $key
* @return mixed
*/
public function first(string $key)
{
list($key, $ruleName) = $this->parsekey($key);
if ($this->isWildcardKey($key)) {
$messages = $this->filterMessagesForWildcardKey($key, $ruleName);
$flattenMessages = Helper::arrayDot($messages);
return array_shift($flattenMessages);
} else {
$keyMessages = isset($this->messages[$key])? $this->messages[$key] : [];
if (empty($keyMessages)) {
return null;
}
if ($ruleName) {
return isset($keyMessages[$ruleName])? $keyMessages[$ruleName] : null;
} else {
return array_shift($keyMessages);
}
}
}
/**
* Get messages from given key, can be use custom format
*
* @param string $key
* @param string $format
* @return array
*/
public function get(string $key, string $format = ':message'): array
{
list($key, $ruleName) = $this->parsekey($key);
$results = [];
if ($this->isWildcardKey($key)) {
$messages = $this->filterMessagesForWildcardKey($key, $ruleName);
foreach ($messages as $explicitKey => $keyMessages) {
foreach ($keyMessages as $rule => $message) {
$results[$explicitKey][$rule] = $this->formatMessage($message, $format);
}
}
} else {
$keyMessages = isset($this->messages[$key])? $this->messages[$key] : [];
foreach ($keyMessages as $rule => $message) {
if ($ruleName and $ruleName != $rule) {
continue;
}
$results[$rule] = $this->formatMessage($message, $format);
}
}
return $results;
}
/**
* Get all messages
*
* @param string $format
* @return array
*/
public function all(string $format = ':message'): array
{
$messages = $this->messages;
$results = [];
foreach ($messages as $key => $keyMessages) {
foreach ($keyMessages as $message) {
$results[] = $this->formatMessage($message, $format);
}
}
return $results;
}
/**
* Get the first message from existing keys
*
* @param string $format
* @param boolean $dotNotation
* @return array
*/
public function firstOfAll(string $format = ':message', bool $dotNotation = false): array
{
$messages = $this->messages;
$results = [];
foreach ($messages as $key => $keyMessages) {
if ($dotNotation) {
$results[$key] = $this->formatMessage(array_shift($messages[$key]), $format);
} else {
Helper::arraySet($results, $key, $this->formatMessage(array_shift($messages[$key]), $format));
}
}
return $results;
}
/**
* Get plain array messages
*
* @return array
*/
public function toArray(): array
{
return $this->messages;
}
/**
* Parse $key to get the array of $key and $ruleName
*
* @param string $key
* @return array
*/
protected function parseKey(string $key): array
{
$expl = explode(':', $key, 2);
$key = $expl[0];
$ruleName = isset($expl[1])? $expl[1] : null;
return [$key, $ruleName];
}
/**
* Check the $key is wildcard
*
* @param mixed $key
* @return bool
*/
protected function isWildcardKey(string $key): bool
{
return false !== strpos($key, '*');
}
/**
* Filter messages with wildcard key
*
* @param string $key
* @param mixed $ruleName
* @return array
*/
protected function filterMessagesForWildcardKey(string $key, $ruleName = null): array
{
$messages = $this->messages;
$pattern = preg_quote($key, '#');
$pattern = str_replace('\*', '.*', $pattern);
$filteredMessages = [];
foreach ($messages as $k => $keyMessages) {
if ((bool) preg_match('#^'.$pattern.'\z#u', $k) === false) {
continue;
}
foreach ($keyMessages as $rule => $message) {
if ($ruleName and $rule != $ruleName) {
continue;
}
$filteredMessages[$k][$rule] = $message;
}
}
return $filteredMessages;
}
/**
* Get formatted message
*
* @param string $message
* @param string $format
* @return string
*/
protected function formatMessage(string $message, string $format): string
{
return str_replace(':message', $message, $format);
}
}
+258
View File
@@ -0,0 +1,258 @@
<?php
namespace Rakit\Validation;
class Helper
{
/**
* Determine if a given string matches a given pattern.
* Adapted from: https://github.com/illuminate/support/blob/v5.3.23/Str.php#L119
*
* @param string $pattern
* @param string $value
* @return bool
*/
public static function strIs(string $pattern, string $value): bool
{
if ($pattern == $value) {
return true;
}
$pattern = preg_quote($pattern, '#');
// Asterisks are translated into zero-or-more regular expression wildcards
// to make it convenient to check if the strings starts with the given
// pattern such as "library/*", making any string check convenient.
$pattern = str_replace('\*', '.*', $pattern);
return (bool) preg_match('#^'.$pattern.'\z#u', $value);
}
/**
* Check if an item or items exist in an array using "dot" notation.
* Adapted from: https://github.com/illuminate/support/blob/v5.3.23/Arr.php#L81
*
* @param array $array
* @param string $key
* @return bool
*/
public static function arrayHas(array $array, string $key): bool
{
if (array_key_exists($key, $array)) {
return true;
}
foreach (explode('.', $key) as $segment) {
if (is_array($array) && array_key_exists($segment, $array)) {
$array = $array[$segment];
} else {
return false;
}
}
return true;
}
/**
* Get an item from an array using "dot" notation.
* Adapted from: https://github.com/illuminate/support/blob/v5.3.23/Arr.php#L246
*
* @param array $array
* @param string|null $key
* @param mixed $default
* @return mixed
*/
public static function arrayGet(array $array, $key, $default = null)
{
if (is_null($key)) {
return $array;
}
if (array_key_exists($key, $array)) {
return $array[$key];
}
foreach (explode('.', $key) as $segment) {
if (is_array($array) && array_key_exists($segment, $array)) {
$array = $array[$segment];
} else {
return $default;
}
}
return $array;
}
/**
* Flatten a multi-dimensional associative array with dots.
* Adapted from: https://github.com/illuminate/support/blob/v5.3.23/Arr.php#L81
*
* @param array $array
* @param string $prepend
* @return array
*/
public static function arrayDot(array $array, string $prepend = ''): array
{
$results = [];
foreach ($array as $key => $value) {
if (is_array($value) && ! empty($value)) {
$results = array_merge($results, static::arrayDot($value, $prepend.$key.'.'));
} else {
$results[$prepend.$key] = $value;
}
}
return $results;
}
/**
* Set an item on an array or object using dot notation.
* Adapted from: https://github.com/illuminate/support/blob/v5.3.23/helpers.php#L437
*
* @param mixed $target
* @param string|array|null $key
* @param mixed $value
* @param bool $overwrite
* @return mixed
*/
public static function arraySet(&$target, $key, $value, $overwrite = true): array
{
if (is_null($key)) {
if ($overwrite) {
return $target = array_merge($target, $value);
}
return $target = array_merge($value, $target);
}
$segments = is_array($key) ? $key : explode('.', $key);
if (($segment = array_shift($segments)) === '*') {
if (! is_array($target)) {
$target = [];
}
if ($segments) {
foreach ($target as &$inner) {
static::arraySet($inner, $segments, $value, $overwrite);
}
} elseif ($overwrite) {
foreach ($target as &$inner) {
$inner = $value;
}
}
} elseif (is_array($target)) {
if ($segments) {
if (! array_key_exists($segment, $target)) {
$target[$segment] = [];
}
static::arraySet($target[$segment], $segments, $value, $overwrite);
} elseif ($overwrite || ! array_key_exists($segment, $target)) {
$target[$segment] = $value;
}
} else {
$target = [];
if ($segments) {
static::arraySet($target[$segment], $segments, $value, $overwrite);
} elseif ($overwrite) {
$target[$segment] = $value;
}
}
return $target;
}
/**
* Unset an item on an array or object using dot notation.
*
* @param mixed $target
* @param string|array $key
* @return mixed
*/
public static function arrayUnset(&$target, $key)
{
if (!is_array($target)) {
return $target;
}
$segments = is_array($key) ? $key : explode('.', $key);
$segment = array_shift($segments);
if ($segment == '*') {
$target = [];
} elseif ($segments) {
if (array_key_exists($segment, $target)) {
static::arrayUnset($target[$segment], $segments);
}
} elseif (array_key_exists($segment, $target)) {
unset($target[$segment]);
}
return $target;
}
/**
* Get snake_case format from given string
*
* @param string $value
* @param string $delimiter
* @return string
*/
public static function snakeCase(string $value, string $delimiter = '_'): string
{
if (! ctype_lower($value)) {
$value = preg_replace('/\s+/u', '', ucwords($value));
$value = strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
}
return $value;
}
/**
* Join string[] to string with given $separator and $lastSeparator.
*
* @param array $pieces
* @param string $separator
* @param string|null $lastSeparator
* @return string
*/
public static function join(array $pieces, string $separator, string $lastSeparator = null): string
{
if (is_null($lastSeparator)) {
$lastSeparator = $separator;
}
$last = array_pop($pieces);
switch (count($pieces)) {
case 0:
return $last ?: '';
case 1:
return $pieces[0] . $lastSeparator . $last;
default:
return implode($separator, $pieces) . $lastSeparator . $last;
}
}
/**
* Wrap string[] by given $prefix and $suffix
*
* @param array $strings
* @param string $prefix
* @param string|null $suffix
* @return array
*/
public static function wraps(array $strings, string $prefix, string $suffix = null): array
{
if (is_null($suffix)) {
$suffix = $prefix;
}
return array_map(function ($str) use ($prefix, $suffix) {
return $prefix . $str . $suffix;
}, $strings);
}
}
+804
View File
@@ -0,0 +1,804 @@
<?php
namespace Rakit\Validation;
class MimeTypeGuesser
{
/** @var array */
protected $mimeTypes = [
'application/andrew-inset' => 'ez',
'application/applixware' => 'aw',
'application/atom+xml' => 'atom',
'application/atomcat+xml' => 'atomcat',
'application/atomsvc+xml' => 'atomsvc',
'application/ccxml+xml' => 'ccxml',
'application/cdmi-capability' => 'cdmia',
'application/cdmi-container' => 'cdmic',
'application/cdmi-domain' => 'cdmid',
'application/cdmi-object' => 'cdmio',
'application/cdmi-queue' => 'cdmiq',
'application/cu-seeme' => 'cu',
'application/davmount+xml' => 'davmount',
'application/docbook+xml' => 'dbk',
'application/dssc+der' => 'dssc',
'application/dssc+xml' => 'xdssc',
'application/ecmascript' => 'ecma',
'application/emma+xml' => 'emma',
'application/epub+zip' => 'epub',
'application/exi' => 'exi',
'application/font-tdpfr' => 'pfr',
'application/gml+xml' => 'gml',
'application/gpx+xml' => 'gpx',
'application/gxf' => 'gxf',
'application/hyperstudio' => 'stk',
'application/inkml+xml' => 'ink',
'application/ipfix' => 'ipfix',
'application/java-archive' => 'jar',
'application/java-serialized-object' => 'ser',
'application/java-vm' => 'class',
'application/javascript' => 'js',
'application/json' => 'json',
'application/jsonml+json' => 'jsonml',
'application/lost+xml' => 'lostxml',
'application/mac-binhex40' => 'hqx',
'application/mac-compactpro' => 'cpt',
'application/mads+xml' => 'mads',
'application/marc' => 'mrc',
'application/marcxml+xml' => 'mrcx',
'application/mathematica' => 'ma',
'application/mathml+xml' => 'mathml',
'application/mbox' => 'mbox',
'application/mediaservercontrol+xml' => 'mscml',
'application/metalink+xml' => 'metalink',
'application/metalink4+xml' => 'meta4',
'application/mets+xml' => 'mets',
'application/mods+xml' => 'mods',
'application/mp21' => 'm21',
'application/mp4' => 'mp4s',
'application/msword' => 'doc',
'application/mxf' => 'mxf',
'application/octet-stream' => 'bin',
'application/oda' => 'oda',
'application/oebps-package+xml' => 'opf',
'application/ogg' => 'ogx',
'application/omdoc+xml' => 'omdoc',
'application/onenote' => 'onetoc',
'application/oxps' => 'oxps',
'application/patch-ops-error+xml' => 'xer',
'application/pdf' => 'pdf',
'application/pgp-encrypted' => 'pgp',
'application/pgp-signature' => 'asc',
'application/pics-rules' => 'prf',
'application/pkcs10' => 'p10',
'application/pkcs7-mime' => 'p7m',
'application/pkcs7-signature' => 'p7s',
'application/pkcs8' => 'p8',
'application/pkix-attr-cert' => 'ac',
'application/pkix-cert' => 'cer',
'application/pkix-crl' => 'crl',
'application/pkix-pkipath' => 'pkipath',
'application/pkixcmp' => 'pki',
'application/pls+xml' => 'pls',
'application/postscript' => 'ai',
'application/prs.cww' => 'cww',
'application/pskc+xml' => 'pskcxml',
'application/rdf+xml' => 'rdf',
'application/reginfo+xml' => 'rif',
'application/relax-ng-compact-syntax' => 'rnc',
'application/resource-lists+xml' => 'rl',
'application/resource-lists-diff+xml' => 'rld',
'application/rls-services+xml' => 'rs',
'application/rpki-ghostbusters' => 'gbr',
'application/rpki-manifest' => 'mft',
'application/rpki-roa' => 'roa',
'application/rsd+xml' => 'rsd',
'application/rss+xml' => 'rss',
'application/rtf' => 'rtf',
'application/sbml+xml' => 'sbml',
'application/scvp-cv-request' => 'scq',
'application/scvp-cv-response' => 'scs',
'application/scvp-vp-request' => 'spq',
'application/scvp-vp-response' => 'spp',
'application/sdp' => 'sdp',
'application/set-payment-initiation' => 'setpay',
'application/set-registration-initiation' => 'setreg',
'application/shf+xml' => 'shf',
'application/smil+xml' => 'smi',
'application/sparql-query' => 'rq',
'application/sparql-results+xml' => 'srx',
'application/srgs' => 'gram',
'application/srgs+xml' => 'grxml',
'application/sru+xml' => 'sru',
'application/ssdl+xml' => 'ssdl',
'application/ssml+xml' => 'ssml',
'application/tei+xml' => 'tei',
'application/thraud+xml' => 'tfi',
'application/timestamped-data' => 'tsd',
'application/vnd.3gpp.pic-bw-large' => 'plb',
'application/vnd.3gpp.pic-bw-small' => 'psb',
'application/vnd.3gpp.pic-bw-var' => 'pvb',
'application/vnd.3gpp2.tcap' => 'tcap',
'application/vnd.3m.post-it-notes' => 'pwn',
'application/vnd.accpac.simply.aso' => 'aso',
'application/vnd.accpac.simply.imp' => 'imp',
'application/vnd.acucobol' => 'acu',
'application/vnd.acucorp' => 'atc',
'application/vnd.adobe.air-application-installer-package+zip' => 'air',
'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
'application/vnd.adobe.fxp' => 'fxp',
'application/vnd.adobe.xdp+xml' => 'xdp',
'application/vnd.adobe.xfdf' => 'xfdf',
'application/vnd.ahead.space' => 'ahead',
'application/vnd.airzip.filesecure.azf' => 'azf',
'application/vnd.airzip.filesecure.azs' => 'azs',
'application/vnd.amazon.ebook' => 'azw',
'application/vnd.americandynamics.acc' => 'acc',
'application/vnd.amiga.ami' => 'ami',
'application/vnd.android.package-archive' => 'apk',
'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
'application/vnd.antix.game-component' => 'atx',
'application/vnd.apple.installer+xml' => 'mpkg',
'application/vnd.apple.mpegurl' => 'm3u8',
'application/vnd.aristanetworks.swi' => 'swi',
'application/vnd.astraea-software.iota' => 'iota',
'application/vnd.audiograph' => 'aep',
'application/vnd.blueice.multipass' => 'mpm',
'application/vnd.bmi' => 'bmi',
'application/vnd.businessobjects' => 'rep',
'application/vnd.chemdraw+xml' => 'cdxml',
'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
'application/vnd.cinderella' => 'cdy',
'application/vnd.claymore' => 'cla',
'application/vnd.cloanto.rp9' => 'rp9',
'application/vnd.clonk.c4group' => 'c4g',
'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
'application/vnd.commonspace' => 'csp',
'application/vnd.contact.cmsg' => 'cdbcmsg',
'application/vnd.cosmocaller' => 'cmc',
'application/vnd.crick.clicker' => 'clkx',
'application/vnd.crick.clicker.keyboard' => 'clkk',
'application/vnd.crick.clicker.palette' => 'clkp',
'application/vnd.crick.clicker.template' => 'clkt',
'application/vnd.crick.clicker.wordbank' => 'clkw',
'application/vnd.criticaltools.wbs+xml' => 'wbs',
'application/vnd.ctc-posml' => 'pml',
'application/vnd.cups-ppd' => 'ppd',
'application/vnd.curl.car' => 'car',
'application/vnd.curl.pcurl' => 'pcurl',
'application/vnd.dart' => 'dart',
'application/vnd.data-vision.rdz' => 'rdz',
'application/vnd.dece.data' => 'uvf',
'application/vnd.dece.ttml+xml' => 'uvt',
'application/vnd.dece.unspecified' => 'uvx',
'application/vnd.dece.zip' => 'uvz',
'application/vnd.denovo.fcselayout-link' => 'fe_launch',
'application/vnd.dna' => 'dna',
'application/vnd.dolby.mlp' => 'mlp',
'application/vnd.dpgraph' => 'dpg',
'application/vnd.dreamfactory' => 'dfac',
'application/vnd.ds-keypoint' => 'kpxx',
'application/vnd.dvb.ait' => 'ait',
'application/vnd.dvb.service' => 'svc',
'application/vnd.dynageo' => 'geo',
'application/vnd.ecowin.chart' => 'mag',
'application/vnd.enliven' => 'nml',
'application/vnd.epson.esf' => 'esf',
'application/vnd.epson.msf' => 'msf',
'application/vnd.epson.quickanime' => 'qam',
'application/vnd.epson.salt' => 'slt',
'application/vnd.epson.ssf' => 'ssf',
'application/vnd.eszigno3+xml' => 'es3',
'application/vnd.ezpix-album' => 'ez2',
'application/vnd.ezpix-package' => 'ez3',
'application/vnd.fdf' => 'fdf',
'application/vnd.fdsn.mseed' => 'mseed',
'application/vnd.fdsn.seed' => 'seed',
'application/vnd.flographit' => 'gph',
'application/vnd.fluxtime.clip' => 'ftc',
'application/vnd.framemaker' => 'fm',
'application/vnd.frogans.fnc' => 'fnc',
'application/vnd.frogans.ltf' => 'ltf',
'application/vnd.fsc.weblaunch' => 'fsc',
'application/vnd.fujitsu.oasys' => 'oas',
'application/vnd.fujitsu.oasys2' => 'oa2',
'application/vnd.fujitsu.oasys3' => 'oa3',
'application/vnd.fujitsu.oasysgp' => 'fg5',
'application/vnd.fujitsu.oasysprs' => 'bh2',
'application/vnd.fujixerox.ddd' => 'ddd',
'application/vnd.fujixerox.docuworks' => 'xdw',
'application/vnd.fujixerox.docuworks.binder' => 'xbd',
'application/vnd.fuzzysheet' => 'fzs',
'application/vnd.genomatix.tuxedo' => 'txd',
'application/vnd.geogebra.file' => 'ggb',
'application/vnd.geogebra.tool' => 'ggt',
'application/vnd.geometry-explorer' => 'gex',
'application/vnd.geonext' => 'gxt',
'application/vnd.geoplan' => 'g2w',
'application/vnd.geospace' => 'g3w',
'application/vnd.gmx' => 'gmx',
'application/vnd.google-earth.kml+xml' => 'kml',
'application/vnd.google-earth.kmz' => 'kmz',
'application/vnd.grafeq' => 'gqf',
'application/vnd.groove-account' => 'gac',
'application/vnd.groove-help' => 'ghf',
'application/vnd.groove-identity-message' => 'gim',
'application/vnd.groove-injector' => 'grv',
'application/vnd.groove-tool-message' => 'gtm',
'application/vnd.groove-tool-template' => 'tpl',
'application/vnd.groove-vcard' => 'vcg',
'application/vnd.hal+xml' => 'hal',
'application/vnd.handheld-entertainment+xml' => 'zmm',
'application/vnd.hbci' => 'hbci',
'application/vnd.hhe.lesson-player' => 'les',
'application/vnd.hp-hpgl' => 'hpgl',
'application/vnd.hp-hpid' => 'hpid',
'application/vnd.hp-hps' => 'hps',
'application/vnd.hp-jlyt' => 'jlt',
'application/vnd.hp-pcl' => 'pcl',
'application/vnd.hp-pclxl' => 'pclxl',
'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
'application/vnd.ibm.minipay' => 'mpy',
'application/vnd.ibm.modcap' => 'afp',
'application/vnd.ibm.rights-management' => 'irm',
'application/vnd.ibm.secure-container' => 'sc',
'application/vnd.iccprofile' => 'icc',
'application/vnd.igloader' => 'igl',
'application/vnd.immervision-ivp' => 'ivp',
'application/vnd.immervision-ivu' => 'ivu',
'application/vnd.insors.igm' => 'igm',
'application/vnd.intercon.formnet' => 'xpw',
'application/vnd.intergeo' => 'i2g',
'application/vnd.intu.qbo' => 'qbo',
'application/vnd.intu.qfx' => 'qfx',
'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
'application/vnd.irepository.package+xml' => 'irp',
'application/vnd.is-xpr' => 'xpr',
'application/vnd.isac.fcs' => 'fcs',
'application/vnd.jam' => 'jam',
'application/vnd.jcp.javame.midlet-rms' => 'rms',
'application/vnd.jisp' => 'jisp',
'application/vnd.joost.joda-archive' => 'joda',
'application/vnd.kahootz' => 'ktz',
'application/vnd.kde.karbon' => 'karbon',
'application/vnd.kde.kchart' => 'chrt',
'application/vnd.kde.kformula' => 'kfo',
'application/vnd.kde.kivio' => 'flw',
'application/vnd.kde.kontour' => 'kon',
'application/vnd.kde.kpresenter' => 'kpr',
'application/vnd.kde.kspread' => 'ksp',
'application/vnd.kde.kword' => 'kwd',
'application/vnd.kenameaapp' => 'htke',
'application/vnd.kidspiration' => 'kia',
'application/vnd.kinar' => 'kne',
'application/vnd.koan' => 'skp',
'application/vnd.kodak-descriptor' => 'sse',
'application/vnd.las.las+xml' => 'lasxml',
'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
'application/vnd.lotus-1-2-3' => '123',
'application/vnd.lotus-approach' => 'apr',
'application/vnd.lotus-freelance' => 'pre',
'application/vnd.lotus-notes' => 'nsf',
'application/vnd.lotus-organizer' => 'org',
'application/vnd.lotus-screencam' => 'scm',
'application/vnd.lotus-wordpro' => 'lwp',
'application/vnd.macports.portpkg' => 'portpkg',
'application/vnd.mcd' => 'mcd',
'application/vnd.medcalcdata' => 'mc1',
'application/vnd.mediastation.cdkey' => 'cdkey',
'application/vnd.mfer' => 'mwf',
'application/vnd.mfmp' => 'mfm',
'application/vnd.micrografx.flo' => 'flo',
'application/vnd.micrografx.igx' => 'igx',
'application/vnd.mif' => 'mif',
'application/vnd.mobius.daf' => 'daf',
'application/vnd.mobius.dis' => 'dis',
'application/vnd.mobius.mbk' => 'mbk',
'application/vnd.mobius.mqy' => 'mqy',
'application/vnd.mobius.msl' => 'msl',
'application/vnd.mobius.plc' => 'plc',
'application/vnd.mobius.txf' => 'txf',
'application/vnd.mophun.application' => 'mpn',
'application/vnd.mophun.certificate' => 'mpc',
'application/vnd.mozilla.xul+xml' => 'xul',
'application/vnd.ms-artgalry' => 'cil',
'application/vnd.ms-cab-compressed' => 'cab',
'application/vnd.ms-excel' => 'xls',
'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
'application/vnd.ms-fontobject' => 'eot',
'application/vnd.ms-htmlhelp' => 'chm',
'application/vnd.ms-ims' => 'ims',
'application/vnd.ms-lrm' => 'lrm',
'application/vnd.ms-officetheme' => 'thmx',
'application/vnd.ms-pki.seccat' => 'cat',
'application/vnd.ms-pki.stl' => 'stl',
'application/vnd.ms-powerpoint' => 'ppt',
'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
'application/vnd.ms-project' => 'mpp',
'application/vnd.ms-word.document.macroenabled.12' => 'docm',
'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
'application/vnd.ms-works' => 'wps',
'application/vnd.ms-wpl' => 'wpl',
'application/vnd.ms-xpsdocument' => 'xps',
'application/vnd.mseq' => 'mseq',
'application/vnd.musician' => 'mus',
'application/vnd.muvee.style' => 'msty',
'application/vnd.mynfc' => 'taglet',
'application/vnd.neurolanguage.nlu' => 'nlu',
'application/vnd.nitf' => 'ntf',
'application/vnd.noblenet-directory' => 'nnd',
'application/vnd.noblenet-sealer' => 'nns',
'application/vnd.noblenet-web' => 'nnw',
'application/vnd.nokia.n-gage.data' => 'ngdat',
'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
'application/vnd.nokia.radio-preset' => 'rpst',
'application/vnd.nokia.radio-presets' => 'rpss',
'application/vnd.novadigm.edm' => 'edm',
'application/vnd.novadigm.edx' => 'edx',
'application/vnd.novadigm.ext' => 'ext',
'application/vnd.oasis.opendocument.chart' => 'odc',
'application/vnd.oasis.opendocument.chart-template' => 'otc',
'application/vnd.oasis.opendocument.database' => 'odb',
'application/vnd.oasis.opendocument.formula' => 'odf',
'application/vnd.oasis.opendocument.formula-template' => 'odft',
'application/vnd.oasis.opendocument.graphics' => 'odg',
'application/vnd.oasis.opendocument.graphics-template' => 'otg',
'application/vnd.oasis.opendocument.image' => 'odi',
'application/vnd.oasis.opendocument.image-template' => 'oti',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'application/vnd.oasis.opendocument.presentation-template' => 'otp',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.text-master' => 'odm',
'application/vnd.oasis.opendocument.text-template' => 'ott',
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/vnd.olpc-sugar' => 'xo',
'application/vnd.oma.dd2+xml' => 'dd2',
'application/vnd.openofficeorg.extension' => 'oxt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.osgeo.mapguide.package' => 'mgp',
'application/vnd.osgi.dp' => 'dp',
'application/vnd.osgi.subsystem' => 'esa',
'application/vnd.palm' => 'pdb',
'application/vnd.pawaafile' => 'paw',
'application/vnd.pg.format' => 'str',
'application/vnd.pg.osasli' => 'ei6',
'application/vnd.picsel' => 'efif',
'application/vnd.pmi.widget' => 'wg',
'application/vnd.pocketlearn' => 'plf',
'application/vnd.powerbuilder6' => 'pbd',
'application/vnd.previewsystems.box' => 'box',
'application/vnd.proteus.magazine' => 'mgz',
'application/vnd.publishare-delta-tree' => 'qps',
'application/vnd.pvi.ptid1' => 'ptid',
'application/vnd.quark.quarkxpress' => 'qxd',
'application/vnd.realvnc.bed' => 'bed',
'application/vnd.recordare.musicxml' => 'mxl',
'application/vnd.recordare.musicxml+xml' => 'musicxml',
'application/vnd.rig.cryptonote' => 'cryptonote',
'application/vnd.rim.cod' => 'cod',
'application/vnd.rn-realmedia' => 'rm',
'application/vnd.rn-realmedia-vbr' => 'rmvb',
'application/vnd.route66.link66+xml' => 'link66',
'application/vnd.sailingtracker.track' => 'st',
'application/vnd.seemail' => 'see',
'application/vnd.sema' => 'sema',
'application/vnd.semd' => 'semd',
'application/vnd.semf' => 'semf',
'application/vnd.shana.informed.formdata' => 'ifm',
'application/vnd.shana.informed.formtemplate' => 'itp',
'application/vnd.shana.informed.interchange' => 'iif',
'application/vnd.shana.informed.package' => 'ipk',
'application/vnd.simtech-mindmapper' => 'twd',
'application/vnd.smaf' => 'mmf',
'application/vnd.smart.teacher' => 'teacher',
'application/vnd.solent.sdkm+xml' => 'sdkm',
'application/vnd.spotfire.dxp' => 'dxp',
'application/vnd.spotfire.sfs' => 'sfs',
'application/vnd.stardivision.calc' => 'sdc',
'application/vnd.stardivision.draw' => 'sda',
'application/vnd.stardivision.impress' => 'sdd',
'application/vnd.stardivision.math' => 'smf',
'application/vnd.stardivision.writer' => 'sdw',
'application/vnd.stardivision.writer-global' => 'sgl',
'application/vnd.stepmania.package' => 'smzip',
'application/vnd.stepmania.stepchart' => 'sm',
'application/vnd.sun.xml.calc' => 'sxc',
'application/vnd.sun.xml.calc.template' => 'stc',
'application/vnd.sun.xml.draw' => 'sxd',
'application/vnd.sun.xml.draw.template' => 'std',
'application/vnd.sun.xml.impress' => 'sxi',
'application/vnd.sun.xml.impress.template' => 'sti',
'application/vnd.sun.xml.math' => 'sxm',
'application/vnd.sun.xml.writer' => 'sxw',
'application/vnd.sun.xml.writer.global' => 'sxg',
'application/vnd.sun.xml.writer.template' => 'stw',
'application/vnd.sus-calendar' => 'sus',
'application/vnd.svd' => 'svd',
'application/vnd.symbian.install' => 'sis',
'application/vnd.syncml+xml' => 'xsm',
'application/vnd.syncml.dm+wbxml' => 'bdm',
'application/vnd.syncml.dm+xml' => 'xdm',
'application/vnd.tao.intent-module-archive' => 'tao',
'application/vnd.tcpdump.pcap' => 'pcap',
'application/vnd.tmobile-livetv' => 'tmo',
'application/vnd.trid.tpt' => 'tpt',
'application/vnd.triscape.mxs' => 'mxs',
'application/vnd.trueapp' => 'tra',
'application/vnd.ufdl' => 'ufd',
'application/vnd.uiq.theme' => 'utz',
'application/vnd.umajin' => 'umj',
'application/vnd.unity' => 'unityweb',
'application/vnd.uoml+xml' => 'uoml',
'application/vnd.vcx' => 'vcx',
'application/vnd.visio' => 'vsd',
'application/vnd.visionary' => 'vis',
'application/vnd.vsf' => 'vsf',
'application/vnd.wap.wbxml' => 'wbxml',
'application/vnd.wap.wmlc' => 'wmlc',
'application/vnd.wap.wmlscriptc' => 'wmlsc',
'application/vnd.webturbo' => 'wtb',
'application/vnd.wolfram.player' => 'nbp',
'application/vnd.wordperfect' => 'wpd',
'application/vnd.wqd' => 'wqd',
'application/vnd.wt.stf' => 'stf',
'application/vnd.xara' => 'xar',
'application/vnd.xfdl' => 'xfdl',
'application/vnd.yamaha.hv-dic' => 'hvd',
'application/vnd.yamaha.hv-script' => 'hvs',
'application/vnd.yamaha.hv-voice' => 'hvp',
'application/vnd.yamaha.openscoreformat' => 'osf',
'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
'application/vnd.yamaha.smaf-audio' => 'saf',
'application/vnd.yamaha.smaf-phrase' => 'spf',
'application/vnd.yellowriver-custom-menu' => 'cmp',
'application/vnd.zul' => 'zir',
'application/vnd.zzazz.deck+xml' => 'zaz',
'application/voicexml+xml' => 'vxml',
'application/widget' => 'wgt',
'application/winhlp' => 'hlp',
'application/wsdl+xml' => 'wsdl',
'application/wspolicy+xml' => 'wspolicy',
'application/x-7z-compressed' => '7z',
'application/x-abiword' => 'abw',
'application/x-ace-compressed' => 'ace',
'application/x-apple-diskimage' => 'dmg',
'application/x-authorware-bin' => 'aab',
'application/x-authorware-map' => 'aam',
'application/x-authorware-seg' => 'aas',
'application/x-bcpio' => 'bcpio',
'application/x-bittorrent' => 'torrent',
'application/x-blorb' => 'blb',
'application/x-bzip' => 'bz',
'application/x-bzip2' => 'bz2',
'application/x-cbr' => 'cbr',
'application/x-cdlink' => 'vcd',
'application/x-cfs-compressed' => 'cfs',
'application/x-chat' => 'chat',
'application/x-chess-pgn' => 'pgn',
'application/x-conference' => 'nsc',
'application/x-cpio' => 'cpio',
'application/x-csh' => 'csh',
'application/x-debian-package' => 'deb',
'application/x-dgc-compressed' => 'dgc',
'application/x-director' => 'dir',
'application/x-doom' => 'wad',
'application/x-dtbncx+xml' => 'ncx',
'application/x-dtbook+xml' => 'dtb',
'application/x-dtbresource+xml' => 'res',
'application/x-dvi' => 'dvi',
'application/x-envoy' => 'evy',
'application/x-eva' => 'eva',
'application/x-font-bdf' => 'bdf',
'application/x-font-ghostscript' => 'gsf',
'application/x-font-linux-psf' => 'psf',
'application/x-font-otf' => 'otf',
'application/x-font-pcf' => 'pcf',
'application/x-font-snf' => 'snf',
'application/x-font-ttf' => 'ttf',
'application/x-font-type1' => 'pfa',
'application/x-font-woff' => 'woff',
'application/x-freearc' => 'arc',
'application/x-futuresplash' => 'spl',
'application/x-gca-compressed' => 'gca',
'application/x-glulx' => 'ulx',
'application/x-gnumeric' => 'gnumeric',
'application/x-gramps-xml' => 'gramps',
'application/x-gtar' => 'gtar',
'application/x-hdf' => 'hdf',
'application/x-install-instructions' => 'install',
'application/x-iso9660-image' => 'iso',
'application/x-java-jnlp-file' => 'jnlp',
'application/x-latex' => 'latex',
'application/x-lzh-compressed' => 'lzh',
'application/x-mie' => 'mie',
'application/x-mobipocket-ebook' => 'prc',
'application/x-ms-application' => 'application',
'application/x-ms-shortcut' => 'lnk',
'application/x-ms-wmd' => 'wmd',
'application/x-ms-wmz' => 'wmz',
'application/x-ms-xbap' => 'xbap',
'application/x-msaccess' => 'mdb',
'application/x-msbinder' => 'obd',
'application/x-mscardfile' => 'crd',
'application/x-msclip' => 'clp',
'application/x-msdownload' => 'exe',
'application/x-msmediaview' => 'mvb',
'application/x-msmetafile' => 'wmf',
'application/x-msmoney' => 'mny',
'application/x-mspublisher' => 'pub',
'application/x-msschedule' => 'scd',
'application/x-msterminal' => 'trm',
'application/x-mswrite' => 'wri',
'application/x-netcdf' => 'nc',
'application/x-nzb' => 'nzb',
'application/x-pkcs12' => 'p12',
'application/x-pkcs7-certificates' => 'p7b',
'application/x-pkcs7-certreqresp' => 'p7r',
'application/x-rar-compressed' => 'rar',
'application/x-rar' => 'rar',
'application/x-research-info-systems' => 'ris',
'application/x-sh' => 'sh',
'application/x-shar' => 'shar',
'application/x-shockwave-flash' => 'swf',
'application/x-silverlight-app' => 'xap',
'application/x-sql' => 'sql',
'application/x-stuffit' => 'sit',
'application/x-stuffitx' => 'sitx',
'application/x-subrip' => 'srt',
'application/x-sv4cpio' => 'sv4cpio',
'application/x-sv4crc' => 'sv4crc',
'application/x-t3vm-image' => 't3',
'application/x-tads' => 'gam',
'application/x-tar' => 'tar',
'application/x-tcl' => 'tcl',
'application/x-tex' => 'tex',
'application/x-tex-tfm' => 'tfm',
'application/x-texinfo' => 'texinfo',
'application/x-tgif' => 'obj',
'application/x-ustar' => 'ustar',
'application/x-wais-source' => 'src',
'application/x-x509-ca-cert' => 'der',
'application/x-xfig' => 'fig',
'application/x-xliff+xml' => 'xlf',
'application/x-xpinstall' => 'xpi',
'application/x-xz' => 'xz',
'application/x-zmachine' => 'z1',
'application/xaml+xml' => 'xaml',
'application/xcap-diff+xml' => 'xdf',
'application/xenc+xml' => 'xenc',
'application/xhtml+xml' => 'xhtml',
'application/xml' => 'xml',
'application/xml-dtd' => 'dtd',
'application/xop+xml' => 'xop',
'application/xproc+xml' => 'xpl',
'application/xslt+xml' => 'xslt',
'application/xspf+xml' => 'xspf',
'application/xv+xml' => 'mxml',
'application/yang' => 'yang',
'application/yin+xml' => 'yin',
'application/zip' => 'zip',
'audio/adpcm' => 'adp',
'audio/basic' => 'au',
'audio/midi' => 'mid',
'audio/mp3' => 'mp3',
'audio/mp4' => 'mp4a',
'audio/mpeg' => 'mpga',
'audio/ogg' => 'oga',
'audio/s3m' => 's3m',
'audio/silk' => 'sil',
'audio/vnd.dece.audio' => 'uva',
'audio/vnd.digital-winds' => 'eol',
'audio/vnd.dra' => 'dra',
'audio/vnd.dts' => 'dts',
'audio/vnd.dts.hd' => 'dtshd',
'audio/vnd.lucent.voice' => 'lvp',
'audio/vnd.ms-playready.media.pya' => 'pya',
'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
'audio/vnd.rip' => 'rip',
'audio/webm' => 'weba',
'audio/x-aac' => 'aac',
'audio/x-aiff' => 'aif',
'audio/x-caf' => 'caf',
'audio/x-flac' => 'flac',
'audio/x-matroska' => 'mka',
'audio/x-mpegurl' => 'm3u',
'audio/x-ms-wax' => 'wax',
'audio/x-ms-wma' => 'wma',
'audio/x-pn-realaudio' => 'ram',
'audio/x-pn-realaudio-plugin' => 'rmp',
'audio/x-wav' => 'wav',
'audio/xm' => 'xm',
'chemical/x-cdx' => 'cdx',
'chemical/x-cif' => 'cif',
'chemical/x-cmdf' => 'cmdf',
'chemical/x-cml' => 'cml',
'chemical/x-csml' => 'csml',
'chemical/x-xyz' => 'xyz',
'image/bmp' => 'bmp',
'image/x-ms-bmp' => 'bmp',
'image/cgm' => 'cgm',
'image/g3fax' => 'g3',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => 'jpeg',
'image/pjpeg' => 'jpeg',
'image/ktx' => 'ktx',
'image/png' => 'png',
'image/prs.btif' => 'btif',
'image/sgi' => 'sgi',
'image/svg+xml' => 'svg',
'image/tiff' => 'tiff',
'image/vnd.adobe.photoshop' => 'psd',
'image/vnd.dece.graphic' => 'uvi',
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.djvu' => 'djvu',
'image/vnd.dwg' => 'dwg',
'image/vnd.dxf' => 'dxf',
'image/vnd.fastbidsheet' => 'fbs',
'image/vnd.fpx' => 'fpx',
'image/vnd.fst' => 'fst',
'image/vnd.fujixerox.edmics-mmr' => 'mmr',
'image/vnd.fujixerox.edmics-rlc' => 'rlc',
'image/vnd.ms-modi' => 'mdi',
'image/vnd.ms-photo' => 'wdp',
'image/vnd.net-fpx' => 'npx',
'image/vnd.wap.wbmp' => 'wbmp',
'image/vnd.xiff' => 'xif',
'image/webp' => 'webp',
'image/x-3ds' => '3ds',
'image/x-cmu-raster' => 'ras',
'image/x-cmx' => 'cmx',
'image/x-freehand' => 'fh',
'image/x-icon' => 'ico',
'image/x-mrsid-image' => 'sid',
'image/x-pcx' => 'pcx',
'image/x-pict' => 'pic',
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-tga' => 'tga',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'message/rfc822' => 'eml',
'model/iges' => 'igs',
'model/mesh' => 'msh',
'model/vnd.collada+xml' => 'dae',
'model/vnd.dwf' => 'dwf',
'model/vnd.gdl' => 'gdl',
'model/vnd.gtw' => 'gtw',
'model/vnd.mts' => 'mts',
'model/vnd.vtu' => 'vtu',
'model/vrml' => 'wrl',
'model/x3d+binary' => 'x3db',
'model/x3d+vrml' => 'x3dv',
'model/x3d+xml' => 'x3d',
'text/cache-manifest' => 'appcache',
'text/calendar' => 'ics',
'text/css' => 'css',
'text/csv' => 'csv',
'text/html' => 'html',
'text/n3' => 'n3',
'text/plain' => 'txt',
'text/prs.lines.tag' => 'dsc',
'text/richtext' => 'rtx',
'text/rtf' => 'rtf',
'text/sgml' => 'sgml',
'text/tab-separated-values' => 'tsv',
'text/troff' => 't',
'text/turtle' => 'ttl',
'text/uri-list' => 'uri',
'text/vcard' => 'vcard',
'text/vnd.curl' => 'curl',
'text/vnd.curl.dcurl' => 'dcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.curl.mcurl' => 'mcurl',
'text/vnd.dvb.subtitle' => 'sub',
'text/vnd.fly' => 'fly',
'text/vnd.fmi.flexstor' => 'flx',
'text/vnd.graphviz' => 'gv',
'text/vnd.in3d.3dml' => '3dml',
'text/vnd.in3d.spot' => 'spot',
'text/vnd.sun.j2me.app-descriptor' => 'jad',
'text/vnd.wap.wml' => 'wml',
'text/vnd.wap.wmlscript' => 'wmls',
'text/x-asm' => 's',
'text/x-c' => 'c',
'text/x-fortran' => 'f',
'text/x-pascal' => 'p',
'text/x-java-source' => 'java',
'text/x-opml' => 'opml',
'text/x-nfo' => 'nfo',
'text/x-setext' => 'etx',
'text/x-sfv' => 'sfv',
'text/x-uuencode' => 'uu',
'text/x-vcalendar' => 'vcs',
'text/x-vcard' => 'vcf',
'video/3gpp' => '3gp',
'video/3gpp2' => '3g2',
'video/h261' => 'h261',
'video/h263' => 'h263',
'video/h264' => 'h264',
'video/jpeg' => 'jpgv',
'video/jpm' => 'jpm',
'video/mj2' => 'mj2',
'video/mp4' => 'mp4',
'video/mpeg' => 'mpeg',
'video/ogg' => 'ogv',
'video/quicktime' => 'qt',
'video/vnd.dece.hd' => 'uvh',
'video/vnd.dece.mobile' => 'uvm',
'video/vnd.dece.pd' => 'uvp',
'video/vnd.dece.sd' => 'uvs',
'video/vnd.dece.video' => 'uvv',
'video/vnd.dvb.file' => 'dvb',
'video/vnd.fvt' => 'fvt',
'video/vnd.mpegurl' => 'mxu',
'video/vnd.ms-playready.media.pyv' => 'pyv',
'video/vnd.uvvu.mp4' => 'uvu',
'video/vnd.vivo' => 'viv',
'video/webm' => 'webm',
'video/x-f4v' => 'f4v',
'video/x-fli' => 'fli',
'video/x-flv' => 'flv',
'video/x-m4v' => 'm4v',
'video/x-matroska' => 'mkv',
'video/x-mng' => 'mng',
'video/x-ms-asf' => 'asf',
'video/x-ms-vob' => 'vob',
'video/x-ms-wm' => 'wm',
'video/x-ms-wmv' => 'wmv',
'video/x-ms-wmx' => 'wmx',
'video/x-ms-wvx' => 'wvx',
'video/x-msvideo' => 'avi',
'video/x-sgi-movie' => 'movie',
'video/x-smv' => 'smv',
'x-conference/x-cooltalk' => 'ice'
];
/**
* Get extension by mime type
*
* @param string $mimeType
* @return string|null
*/
public function getExtension(string $mimeType)
{
return isset($this->mimeTypes[$mimeType])? $this->mimeTypes[$mimeType] : null;
}
/**
* Get mime type by extension
*
* @param string $extension
* @return string|null
*/
public function getMimeType(string $extension)
{
$key = array_search($extension, $this->mimeTypes);
return $key ?: null;
}
}
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace Rakit\Validation;
class MissingRequiredParameterException extends \Exception
{
}
+232
View File
@@ -0,0 +1,232 @@
<?php
namespace Rakit\Validation;
use Rakit\Validation\MissingRequiredParameterException;
abstract class Rule
{
/** @var string */
protected $key;
/** @var \Rakit\Validation\Attribute|null */
protected $attribute;
/** @var \Rakit\Validation\Validation|null */
protected $validation;
/** @var bool */
protected $implicit = false;
/** @var array */
protected $params = [];
/** @var array */
protected $paramsTexts = [];
/** @var array */
protected $fillableParams = [];
/** @var string */
protected $message = "The :attribute is invalid";
abstract public function check($value): bool;
/**
* Set Validation class instance
*
* @param \Rakit\Validation\Validation $validation
* @return void
*/
public function setValidation(Validation $validation)
{
$this->validation = $validation;
}
/**
* Set key
*
* @param string $key
* @return void
*/
public function setKey(string $key)
{
$this->key = $key;
}
/**
* Get key
*
* @return string
*/
public function getKey()
{
return $this->key ?: get_class($this);
}
/**
* Set attribute
*
* @param \Rakit\Validation\Attribute $attribute
* @return void
*/
public function setAttribute(Attribute $attribute)
{
$this->attribute = $attribute;
}
/**
* Get attribute
*
* @return \Rakit\Validation\Attribute|null
*/
public function getAttribute()
{
return $this->attribute;
}
/**
* Get parameters
*
* @return array
*/
public function getParameters(): array
{
return $this->params;
}
/**
* Set params
*
* @param array $params
* @return \Rakit\Validation\Rule
*/
public function setParameters(array $params): Rule
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* Set parameters
*
* @param string $key
* @param mixed $value
* @return \Rakit\Validation\Rule
*/
public function setParameter(string $key, $value): Rule
{
$this->params[$key] = $value;
return $this;
}
/**
* Fill $params to $this->params
*
* @param array $params
* @return \Rakit\Validation\Rule
*/
public function fillParameters(array $params): Rule
{
foreach ($this->fillableParams as $key) {
if (empty($params)) {
break;
}
$this->params[$key] = array_shift($params);
}
return $this;
}
/**
* Get parameter from given $key, return null if it not exists
*
* @param string $key
* @return mixed
*/
public function parameter(string $key)
{
return isset($this->params[$key])? $this->params[$key] : null;
}
/**
* Set parameter text that can be displayed in error message using ':param_key'
*
* @param string $key
* @param string $text
* @return void
*/
public function setParameterText(string $key, string $text)
{
$this->paramsTexts[$key] = $text;
}
/**
* Get $paramsTexts
*
* @return array
*/
public function getParametersTexts(): array
{
return $this->paramsTexts;
}
/**
* Check whether this rule is implicit
*
* @return boolean
*/
public function isImplicit(): bool
{
return $this->implicit;
}
/**
* Just alias of setMessage
*
* @param string $message
* @return \Rakit\Validation\Rule
*/
public function message(string $message): Rule
{
return $this->setMessage($message);
}
/**
* Set message
*
* @param string $message
* @return \Rakit\Validation\Rule
*/
public function setMessage(string $message): Rule
{
$this->message = $message;
return $this;
}
/**
* Get message
*
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* Check given $params must be exists
*
* @param array $params
* @return void
* @throws \Rakit\Validation\MissingRequiredParameterException
*/
protected function requireParameters(array $params)
{
foreach ($params as $param) {
if (!isset($this->params[$param])) {
$rule = $this->getKey();
throw new MissingRequiredParameterException("Missing required parameter '{$param}' on rule '{$rule}'");
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace Rakit\Validation;
use Exception;
class RuleNotFoundException extends Exception
{
}
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace Rakit\Validation;
class RuleQuashException extends \Exception
{
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Accepted extends Rule
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute must be accepted";
/**
* Check the $value is accepted
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$acceptables = ['yes', 'on', '1', 1, true, 'true'];
return in_array($value, $acceptables, true);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class After extends Rule
{
use Traits\DateUtilsTrait;
/** @var string */
protected $message = "The :attribute must be a date after :time.";
/** @var array */
protected $fillableParams = ['time'];
/**
* Check the value is valid
*
* @param mixed $value
* @return bool
* @throws \Exception
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$time = $this->parameter('time');
if (!$this->isValidDate($value)) {
throw $this->throwException($value);
}
if (!$this->isValidDate($time)) {
throw $this->throwException($time);
}
return $this->getTimeStamp($time) < $this->getTimeStamp($value);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Alpha extends Rule
{
/** @var string */
protected $message = "The :attribute only allows alphabet characters";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return is_string($value) && preg_match('/^[\pL\pM]+$/u', $value);
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class AlphaDash extends Rule
{
/** @var string */
protected $message = "The :attribute only allows a-z, 0-9, _ and -";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
if (! is_string($value) && ! is_numeric($value)) {
return false;
}
return preg_match('/^[\pL\pM\pN_-]+$/u', $value) > 0;
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class AlphaNum extends Rule
{
/** @var string */
protected $message = "The :attribute only allows alphabet and numeric";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
if (! is_string($value) && ! is_numeric($value)) {
return false;
}
return preg_match('/^[\pL\pM\pN]+$/u', $value) > 0;
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class AlphaSpaces extends Rule
{
/** @var string */
protected $message = "The :attribute may only allows alphabet and spaces";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
if (! is_string($value)) {
return false;
}
return preg_match('/^[\pL\pM\s]+$/u', $value) > 0;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Before extends Rule
{
use Traits\DateUtilsTrait;
/** @var string */
protected $message = "The :attribute must be a date before :time.";
/** @var array */
protected $fillableParams = ['time'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
* @throws \Exception
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$time = $this->parameter('time');
if (!$this->isValidDate($value)) {
throw $this->throwException($value);
}
if (!$this->isValidDate($time)) {
throw $this->throwException($time);
}
return $this->getTimeStamp($time) > $this->getTimeStamp($value);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Between extends Rule
{
use Traits\SizeTrait;
/** @var string */
protected $message = "The :attribute must be between :min and :max";
/** @var array */
protected $fillableParams = ['min', 'max'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$min = $this->getBytesSize($this->parameter('min'));
$max = $this->getBytesSize($this->parameter('max'));
$valueSize = $this->getValueSize($value);
if (!is_numeric($valueSize)) {
return false;
}
return ($valueSize >= $min && $valueSize <= $max);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Boolean extends Rule
{
/** @var string */
protected $message = "The :attribute must be a boolean";
/**
* Check the value is valid
*
* @param mixed $value
* @return bool
* @throws \Exception
*/
public function check($value): bool
{
return \in_array($value, [\true, \false, "true", "false", 1, 0, "0", "1", "y", "n"], \true);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
use InvalidArgumentException;
use Closure;
class Callback extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid";
/** @var array */
protected $fillableParams = ['callback'];
/**
* Set the Callback closure
*
* @param Closure $callback
* @return self
*/
public function setCallback(Closure $callback): Rule
{
return $this->setParameter('callback', $callback);
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
* @throws \Exception
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$callback = $this->parameter('callback');
if (false === $callback instanceof Closure) {
$key = $this->attribute->getKey();
throw new InvalidArgumentException("Callback rule for '{$key}' is not callable.");
}
$callback = $callback->bindTo($this);
$invalidMessage = $callback($value);
if (is_string($invalidMessage)) {
$this->setMessage($invalidMessage);
return false;
} elseif (false === $invalidMessage) {
return false;
}
return true;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Date extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid date format";
/** @var array */
protected $fillableParams = ['format'];
/** @var array */
protected $params = [
'format' => 'Y-m-d'
];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$format = $this->parameter('format');
return date_create_from_format($format, $value) !== false;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
use Rakit\Validation\Rules\Interfaces\ModifyValue;
class Defaults extends Rule implements ModifyValue
{
/** @var string */
protected $message = "The :attribute default is :default";
/** @var array */
protected $fillableParams = ['default'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$default = $this->parameter('default');
return true;
}
/**
* {@inheritDoc}
*/
public function modifyValue($value)
{
return $this->isEmptyValue($value) ? $this->parameter('default') : $value;
}
/**
* Check $value is empty value
*
* @param mixed $value
* @return boolean
*/
protected function isEmptyValue($value): bool
{
$requiredValidator = new Required;
return false === $requiredValidator->check($value, []);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Different extends Rule
{
/** @var string */
protected $message = "The :attribute must be different with :field";
/** @var array */
protected $fillableParams = ['field'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$field = $this->parameter('field');
$anotherValue = $this->validation->getValue($field);
return $value != $anotherValue;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Digits extends Rule
{
/** @var string */
protected $message = "The :attribute must be numeric and must have an exact length of :length";
/** @var array */
protected $fillableParams = ['length'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$length = (int) $this->parameter('length');
return ! preg_match('/[^0-9]/', $value)
&& strlen((string) $value) == $length;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class DigitsBetween extends Rule
{
/** @var string */
protected $message = "The :attribute must have a length between the given :min and :max";
/** @var array */
protected $fillableParams = ['min', 'max'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$min = (int) $this->parameter('min');
$max = (int) $this->parameter('max');
$length = strlen((string) $value);
return ! preg_match('/[^0-9]/', $value)
&& $length >= $min && $length <= $max;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Email extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid email";
/**
* Check $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Helper;
use Rakit\Validation\Rule;
class Extension extends Rule
{
/** @var string */
protected $message = "The :attribute must be a :allowed_extensions file";
/**
* Given $params and assign the $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
if (count($params) == 1 && is_array($params[0])) {
$params = $params[0];
}
$this->params['allowed_extensions'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['allowed_extensions']);
$allowedExtensions = $this->parameter('allowed_extensions');
foreach ($allowedExtensions as $key => $ext) {
$allowedExtensions[$key] = ltrim($ext, '.');
}
$or = $this->validation ? $this->validation->getTranslation('or') : 'or';
$allowedExtensionsText = Helper::join(Helper::wraps($allowedExtensions, ".", ""), ', ', ", {$or} ");
$this->setParameterText('allowed_extensions', $allowedExtensionsText);
$ext = strtolower(pathinfo($value, PATHINFO_EXTENSION));
return ($ext && in_array($ext, $allowedExtensions)) ? true : false;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Helper;
use Rakit\Validation\Rule;
class In extends Rule
{
/** @var string */
protected $message = "The :attribute only allows :allowed_values";
/** @var bool */
protected $strict = false;
/**
* Given $params and assign the $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
if (count($params) == 1 && is_array($params[0])) {
$params = $params[0];
}
$this->params['allowed_values'] = $params;
return $this;
}
/**
* Set strict value
*
* @param bool $strict
* @return void
*/
public function strict(bool $strict = true)
{
$this->strict = $strict;
}
/**
* Check $value is existed
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['allowed_values']);
$allowedValues = $this->parameter('allowed_values');
$or = $this->validation ? $this->validation->getTranslation('or') : 'or';
$allowedValuesText = Helper::join(Helper::wraps($allowedValues, "'"), ', ', ", {$or} ");
$this->setParameterText('allowed_values', $allowedValuesText);
return in_array($value, $allowedValues, $this->strict);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Integer extends Rule
{
/** @var string */
protected $message = "The :attribute must be integer";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return filter_var($value, FILTER_VALIDATE_INT) !== false;
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Rakit\Validation\Rules\Interfaces;
interface BeforeValidate
{
/**
* Before validate hook
*
* @return void
*/
public function beforeValidate();
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Rakit\Validation\Rules\Interfaces;
interface ModifyValue
{
/**
* Modify given value
* so in current and next rules returned value will be used
*
* @param mixed $value
* @return mixed
*/
public function modifyValue($value);
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Ip extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid IP Address";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return filter_var($value, FILTER_VALIDATE_IP) !== false;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Ipv4 extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid IPv4 Address";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Ipv6 extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid IPv6 Address";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Json extends Rule
{
/** @var string */
protected $message = "The :attribute must be a valid JSON string";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
if (! is_string($value) || empty($value)) {
return false;
}
json_decode($value);
if (json_last_error() !== JSON_ERROR_NONE) {
return false;
}
return true;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Lowercase extends Rule
{
/** @var string */
protected $message = "The :attribute must be lowercase";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return mb_strtolower($value, mb_detect_encoding($value)) === $value;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Max extends Rule
{
use Traits\SizeTrait;
/** @var string */
protected $message = "The :attribute maximum is :max";
/** @var array */
protected $fillableParams = ['max'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$max = $this->getBytesSize($this->parameter('max'));
$valueSize = $this->getValueSize($value);
if (!is_numeric($valueSize)) {
return false;
}
return $valueSize <= $max;
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Helper;
use Rakit\Validation\MimeTypeGuesser;
use Rakit\Validation\Rule;
class Mimes extends Rule
{
use Traits\FileTrait;
/** @var string */
protected $message = "The :attribute file type must be :allowed_types";
/** @var string|int */
protected $maxSize = null;
/** @var string|int */
protected $minSize = null;
/** @var array */
protected $allowedTypes = [];
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->allowTypes($params);
return $this;
}
/**
* Given $types and assign $this->params
*
* @param mixed $types
* @return self
*/
public function allowTypes($types): Rule
{
if (is_string($types)) {
$types = explode('|', $types);
}
$this->params['allowed_types'] = $types;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$allowedTypes = $this->parameter('allowed_types');
if ($allowedTypes) {
$or = $this->validation ? $this->validation->getTranslation('or') : 'or';
$this->setParameterText('allowed_types', Helper::join(Helper::wraps($allowedTypes, "'"), ', ', ", {$or} "));
}
// below is Required rule job
if (!$this->isValueFromUploadedFiles($value) or $value['error'] == UPLOAD_ERR_NO_FILE) {
return true;
}
if (!$this->isUploadedFile($value)) {
return false;
}
// just make sure there is no error
if ($value['error']) {
return false;
}
if (!empty($allowedTypes)) {
$guesser = new MimeTypeGuesser;
$ext = $guesser->getExtension($value['type']);
unset($guesser);
if (!in_array($ext, $allowedTypes)) {
return false;
}
}
return true;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Min extends Rule
{
use Traits\SizeTrait;
/** @var string */
protected $message = "The :attribute minimum is :min";
/** @var array */
protected $fillableParams = ['min'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$min = $this->getBytesSize($this->parameter('min'));
$valueSize = $this->getValueSize($value);
if (!is_numeric($valueSize)) {
return false;
}
return $valueSize >= $min;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Helper;
use Rakit\Validation\Rule;
class NotIn extends Rule
{
/** @var string */
protected $message = "The :attribute is not allowing :disallowed_values";
/** @var bool */
protected $strict = false;
/**
* Given $params and assign the $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
if (count($params) == 1 and is_array($params[0])) {
$params = $params[0];
}
$this->params['disallowed_values'] = $params;
return $this;
}
/**
* Set strict value
*
* @param bool $strict
* @return void
*/
public function strict($strict = true)
{
$this->strict = $strict;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['disallowed_values']);
$disallowedValues = (array) $this->parameter('disallowed_values');
$and = $this->validation ? $this->validation->getTranslation('and') : 'and';
$disallowedValuesText = Helper::join(Helper::wraps($disallowedValues, "'"), ', ', ", {$and} ");
$this->setParameterText('disallowed_values', $disallowedValuesText);
return !in_array($value, $disallowedValues, $this->strict);
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Nullable extends Rule
{
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return true;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Numeric extends Rule
{
/** @var string */
protected $message = "The :attribute must be numeric";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return is_numeric($value);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Present extends Rule
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute must be present";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->setAttributeAsRequired();
return $this->validation->hasValue($this->attribute->getKey());
}
/**
* Set attribute is required if $this->attribute is set
*
* @return void
*/
protected function setAttributeAsRequired()
{
if ($this->attribute) {
$this->attribute->setRequired(true);
}
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Regex extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid format";
/** @var array */
protected $fillableParams = ['regex'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$regex = $this->parameter('regex');
return preg_match($regex, $value) > 0;
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Required extends Rule
{
use Traits\FileTrait;
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->setAttributeAsRequired();
if ($this->attribute and $this->attribute->hasRule('uploaded_file')) {
return $this->isValueFromUploadedFiles($value) and $value['error'] != UPLOAD_ERR_NO_FILE;
}
if (is_string($value)) {
return mb_strlen(trim($value), 'UTF-8') > 0;
}
if (is_array($value)) {
return count($value) > 0;
}
return !is_null($value);
}
/**
* Set attribute is required if $this->attribute is set
*
* @return void
*/
protected function setAttributeAsRequired()
{
if ($this->attribute) {
$this->attribute->setRequired(true);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class RequiredIf extends Required
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Given $params and assign the $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->params['field'] = array_shift($params);
$this->params['values'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['field', 'values']);
$anotherAttribute = $this->parameter('field');
$definedValues = $this->parameter('values');
$anotherValue = $this->getAttribute()->getValue($anotherAttribute);
$validator = $this->validation->getValidator();
$requiredValidator = $validator('required');
if (in_array($anotherValue, $definedValues)) {
$this->setAttributeAsRequired();
return $requiredValidator->check($value, []);
}
return true;
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class RequiredUnless extends Required
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Given $params and assign the $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->params['field'] = array_shift($params);
$this->params['values'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['field', 'values']);
$anotherAttribute = $this->parameter('field');
$definedValues = $this->parameter('values');
$anotherValue = $this->getAttribute()->getValue($anotherAttribute);
$validator = $this->validation->getValidator();
$requiredValidator = $validator('required');
if (!in_array($anotherValue, $definedValues)) {
$this->setAttributeAsRequired();
return $requiredValidator->check($value, []);
}
return true;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class RequiredWith extends Required
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->params['fields'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['fields']);
$fields = $this->parameter('fields');
$validator = $this->validation->getValidator();
$requiredValidator = $validator('required');
foreach ($fields as $field) {
if ($this->validation->hasValue($field)) {
$this->setAttributeAsRequired();
return $requiredValidator->check($value, []);
}
}
return true;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class RequiredWithAll extends Required
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->params['fields'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['fields']);
$fields = $this->parameter('fields');
$validator = $this->validation->getValidator();
$requiredValidator = $validator('required');
foreach ($fields as $field) {
if (!$this->validation->hasValue($field)) {
return true;
}
}
$this->setAttributeAsRequired();
return $requiredValidator->check($value, []);
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class RequiredWithout extends Required
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->params['fields'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['fields']);
$fields = $this->parameter('fields');
$validator = $this->validation->getValidator();
$requiredValidator = $validator('required');
foreach ($fields as $field) {
if (!$this->validation->hasValue($field)) {
$this->setAttributeAsRequired();
return $requiredValidator->check($value, []);
}
}
return true;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class RequiredWithoutAll extends Required
{
/** @var bool */
protected $implicit = true;
/** @var string */
protected $message = "The :attribute is required";
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->params['fields'] = $params;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters(['fields']);
$fields = $this->parameter('fields');
$validator = $this->validation->getValidator();
$requiredValidator = $validator('required');
foreach ($fields as $field) {
if ($this->validation->hasValue($field)) {
return true;
}
}
$this->setAttributeAsRequired();
return $requiredValidator->check($value, []);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Same extends Rule
{
/** @var string */
protected $message = "The :attribute must be same with :field";
/** @var array */
protected $fillableParams = ['field'];
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$this->requireParameters($this->fillableParams);
$field = $this->parameter('field');
$anotherValue = $this->getAttribute()->getValue($field);
return $value == $anotherValue;
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Rakit\Validation\Rules\Traits;
use Exception;
trait DateUtilsTrait
{
/**
* Check the $date is valid
*
* @param string $date
* @return bool
*/
protected function isValidDate(string $date): bool
{
return (strtotime($date) !== false);
}
/**
* Throw exception
*
* @param string $value
* @return Exception
*/
protected function throwException(string $value)
{
// phpcs:ignore
return new Exception("Expected a valid date, got '{$value}' instead. 2016-12-08, 2016-12-02 14:58, tomorrow are considered valid dates");
}
/**
* Given $date and get the time stamp
*
* @param mixed $date
* @return int
*/
protected function getTimeStamp($date): int
{
return strtotime($date);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace Rakit\Validation\Rules\Traits;
use InvalidArgumentException;
use Rakit\Validation\Helper;
trait FileTrait
{
/**
* Check whether value is from $_FILES
*
* @param mixed $value
* @return bool
*/
public function isValueFromUploadedFiles($value): bool
{
if (!is_array($value)) {
return false;
}
$keys = ['name', 'type', 'tmp_name', 'size', 'error'];
foreach ($keys as $key) {
if (!array_key_exists($key, $value)) {
return false;
}
}
return true;
}
/**
* Check the $value is uploaded file
*
* @param mixed $value
* @return bool
*/
public function isUploadedFile($value): bool
{
return $this->isValueFromUploadedFiles($value) && is_uploaded_file($value['tmp_name']);
}
/**
* Resolve uploaded file value
*
* @param mixed $value
* @return array|null
*/
public function resolveUploadedFileValue($value)
{
if (!$this->isValueFromUploadedFiles($value)) {
return null;
}
// Here $value should be an array:
// [
// 'name' => string|array,
// 'type' => string|array,
// 'size' => int|array,
// 'tmp_name' => string|array,
// 'error' => string|array,
// ]
// Flatten $value to it's array dot format,
// so our array must be something like:
// ['name' => string, 'type' => string, 'size' => int, ...]
// or for multiple values:
// ['name.0' => string, 'name.1' => string, 'type.0' => string, 'type.1' => string, ...]
// or for nested array:
// ['name.foo.bar' => string, 'name.foo.baz' => string, 'type.foo.bar' => string, 'type.foo.baz' => string, ...]
$arrayDots = Helper::arrayDot($value);
$results = [];
foreach ($arrayDots as $key => $val) {
// Move first key to last key
// name.foo.bar -> foo.bar.name
$splits = explode(".", $key);
$firstKey = array_shift($splits);
$key = count($splits) ? implode(".", $splits) . ".{$firstKey}" : $firstKey;
Helper::arraySet($results, $key, $val);
}
return $results;
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace Rakit\Validation\Rules\Traits;
use InvalidArgumentException;
trait SizeTrait
{
/**
* Get size (int) value from given $value
*
* @param int|string $value
* @return float|false
*/
protected function getValueSize($value)
{
if ($this->getAttribute()
&& ($this->getAttribute()->hasRule('numeric') || $this->getAttribute()->hasRule('integer'))
&& is_numeric($value)
) {
$value = (float) $value;
}
if (is_int($value) || is_float($value)) {
return (float) $value;
} elseif (is_string($value)) {
return (float) mb_strlen($value, 'UTF-8');
} elseif ($this->isUploadedFileValue($value)) {
return (float) $value['size'];
} elseif (is_array($value)) {
return (float) count($value);
} else {
return false;
}
}
/**
* Given $size and get the bytes
*
* @param string|int $size
* @return float
* @throws InvalidArgumentException
*/
protected function getBytesSize($size)
{
if (is_numeric($size)) {
return (float) $size;
}
if (!is_string($size)) {
throw new InvalidArgumentException("Size must be string or numeric Bytes", 1);
}
if (!preg_match("/^(?<number>((\d+)?\.)?\d+)(?<format>(B|K|M|G|T|P)B?)?$/i", $size, $match)) {
throw new InvalidArgumentException("Size is not valid format", 1);
}
$number = (float) $match['number'];
$format = isset($match['format']) ? $match['format'] : '';
switch (strtoupper($format)) {
case "KB":
case "K":
return $number * 1024;
case "MB":
case "M":
return $number * pow(1024, 2);
case "GB":
case "G":
return $number * pow(1024, 3);
case "TB":
case "T":
return $number * pow(1024, 4);
case "PB":
case "P":
return $number * pow(1024, 5);
default:
return $number;
}
}
/**
* Check whether value is from $_FILES
*
* @param mixed $value
* @return bool
*/
public function isUploadedFileValue($value): bool
{
if (!is_array($value)) {
return false;
}
$keys = ['name', 'type', 'tmp_name', 'size', 'error'];
foreach ($keys as $key) {
if (!array_key_exists($key, $value)) {
return false;
}
}
return true;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class TypeArray extends Rule
{
/** @var string */
protected $message = "The :attribute must be array";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return is_array($value);
}
}
+184
View File
@@ -0,0 +1,184 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Helper;
use Rakit\Validation\MimeTypeGuesser;
use Rakit\Validation\Rule;
use Rakit\Validation\Rules\Interfaces\BeforeValidate;
class UploadedFile extends Rule implements BeforeValidate
{
use Traits\FileTrait, Traits\SizeTrait;
/** @var string */
protected $message = "The :attribute is not valid uploaded file";
/** @var string|int */
protected $maxSize = null;
/** @var string|int */
protected $minSize = null;
/** @var array */
protected $allowedTypes = [];
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
$this->minSize(array_shift($params));
$this->maxSize(array_shift($params));
$this->fileTypes($params);
return $this;
}
/**
* Given $size and set the max size
*
* @param string|int $size
* @return self
*/
public function maxSize($size): Rule
{
$this->params['max_size'] = $size;
return $this;
}
/**
* Given $size and set the min size
*
* @param string|int $size
* @return self
*/
public function minSize($size): Rule
{
$this->params['min_size'] = $size;
return $this;
}
/**
* Given $min and $max then set the range size
*
* @param string|int $min
* @param string|int $max
* @return self
*/
public function sizeBetween($min, $max): Rule
{
$this->minSize($min);
$this->maxSize($max);
return $this;
}
/**
* Given $types and assign $this->params
*
* @param mixed $types
* @return self
*/
public function fileTypes($types): Rule
{
if (is_string($types)) {
$types = explode('|', $types);
}
$this->params['allowed_types'] = $types;
return $this;
}
/**
* {@inheritDoc}
*/
public function beforeValidate()
{
$attribute = $this->getAttribute();
// We only resolve uploaded file value
// from complex attribute such as 'files.photo', 'images.*', 'images.foo.bar', etc.
if (!$attribute->isUsingDotNotation()) {
return;
}
$keys = explode(".", $attribute->getKey());
$firstKey = array_shift($keys);
$firstKeyValue = $this->validation->getValue($firstKey);
$resolvedValue = $this->resolveUploadedFileValue($firstKeyValue);
// Return original value if $value can't be resolved as uploaded file value
if (!$resolvedValue) {
return;
}
$this->validation->setValue($firstKey, $resolvedValue);
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$minSize = $this->parameter('min_size');
$maxSize = $this->parameter('max_size');
$allowedTypes = $this->parameter('allowed_types');
if ($allowedTypes) {
$or = $this->validation ? $this->validation->getTranslation('or') : 'or';
$this->setParameterText('allowed_types', Helper::join(Helper::wraps($allowedTypes, "'"), ', ', ", {$or} "));
}
// below is Required rule job
if (!$this->isValueFromUploadedFiles($value) or $value['error'] == UPLOAD_ERR_NO_FILE) {
return true;
}
if (!$this->isUploadedFile($value)) {
return false;
}
// just make sure there is no error
if ($value['error']) {
return false;
}
if ($minSize) {
$bytesMinSize = $this->getBytesSize($minSize);
if ($value['size'] < $bytesMinSize) {
$this->setMessage('The :attribute file is too small, minimum size is :min_size');
return false;
}
}
if ($maxSize) {
$bytesMaxSize = $this->getBytesSize($maxSize);
if ($value['size'] > $bytesMaxSize) {
$this->setMessage('The :attribute file is too large, maximum size is :max_size');
return false;
}
}
if (!empty($allowedTypes)) {
$guesser = new MimeTypeGuesser;
$ext = $guesser->getExtension($value['type']);
unset($guesser);
if (!in_array($ext, $allowedTypes)) {
$this->setMessage('The :attribute file type must be :allowed_types');
return false;
}
}
return true;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Uppercase extends Rule
{
/** @var string */
protected $message = "The :attribute must be uppercase";
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
return mb_strtoupper($value, mb_detect_encoding($value)) === $value;
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
namespace Rakit\Validation\Rules;
use Rakit\Validation\Rule;
class Url extends Rule
{
/** @var string */
protected $message = "The :attribute is not valid url";
/**
* Given $params and assign $this->params
*
* @param array $params
* @return self
*/
public function fillParameters(array $params): Rule
{
if (count($params) == 1 and is_array($params[0])) {
$params = $params[0];
}
return $this->forScheme($params);
}
/**
* Given $schemes and assign $this->params
*
* @param array $schemes
* @return self
*/
public function forScheme($schemes): Rule
{
$this->params['schemes'] = (array) $schemes;
return $this;
}
/**
* Check the $value is valid
*
* @param mixed $value
* @return bool
*/
public function check($value): bool
{
$schemes = $this->parameter('schemes');
if (!$schemes) {
return $this->validateCommonScheme($value);
} else {
foreach ($schemes as $scheme) {
$method = 'validate' . ucfirst($scheme) .'Scheme';
if (method_exists($this, $method)) {
if ($this->{$method}($value)) {
return true;
}
} elseif ($this->validateCommonScheme($value, $scheme)) {
return true;
}
}
return false;
}
}
/**
* Validate $value is valid URL format
*
* @param mixed $value
* @return bool
*/
public function validateBasic($value): bool
{
return filter_var($value, FILTER_VALIDATE_URL) !== false;
}
/**
* Validate $value is correct $scheme format
*
* @param mixed $value
* @param null $scheme
* @return bool
*/
public function validateCommonScheme($value, $scheme = null): bool
{
if (!$scheme) {
return $this->validateBasic($value) && (bool) preg_match("/^\w+:\/\//i", $value);
} else {
return $this->validateBasic($value) && (bool) preg_match("/^{$scheme}:\/\//", $value);
}
}
/**
* Validate the $value is mailto scheme format
*
* @param mixed $value
* @return bool
*/
public function validateMailtoScheme($value): bool
{
return $this->validateBasic($value) && preg_match("/^mailto:/", $value);
}
/**
* Validate the $value is jdbc scheme format
*
* @param mixed $value
* @return bool
*/
public function validateJdbcScheme($value): bool
{
return (bool) preg_match("/^jdbc:\w+:\/\//", $value);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace Rakit\Validation\Traits;
trait MessagesTrait
{
/** @var array */
protected $messages = [];
/**
* Given $key and $message to set message
*
* @param mixed $key
* @param mixed $message
* @return void
*/
public function setMessage(string $key, string $message)
{
$this->messages[$key] = $message;
}
/**
* Given $messages and set multiple messages
*
* @param array $messages
* @return void
*/
public function setMessages(array $messages)
{
$this->messages = array_merge($this->messages, $messages);
}
/**
* Given message from given $key
*
* @param string $key
* @return string
*/
public function getMessage(string $key): string
{
return array_key_exists($key, $this->messages) ? $this->messages[$key] : $key;
}
/**
* Get all $messages
*
* @return array
*/
public function getMessages(): array
{
return $this->messages;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace Rakit\Validation\Traits;
trait TranslationsTrait
{
/** @var array */
protected $translations = [];
/**
* Given $key and $translation to set translation
*
* @param mixed $key
* @param mixed $translation
* @return void
*/
public function setTranslation(string $key, string $translation)
{
$this->translations[$key] = $translation;
}
/**
* Given $translations and set multiple translations
*
* @param array $translations
* @return void
*/
public function setTranslations(array $translations)
{
$this->translations = array_merge($this->translations, $translations);
}
/**
* Given translation from given $key
*
* @param string $key
* @return string
*/
public function getTranslation(string $key): string
{
return array_key_exists($key, $this->translations) ? $this->translations[$key] : $key;
}
/**
* Get all $translations
*
* @return array
*/
public function getTranslations(): array
{
return $this->translations;
}
}
+716
View File
@@ -0,0 +1,716 @@
<?php
namespace Rakit\Validation;
use Closure;
use Rakit\Validation\Rules\Interfaces\BeforeValidate;
use Rakit\Validation\Rules\Interfaces\ModifyValue;
use Rakit\Validation\Rules\Required;
class Validation
{
use Traits\TranslationsTrait, Traits\MessagesTrait;
/** @var mixed */
protected $validator;
/** @var array */
protected $inputs = [];
/** @var array */
protected $attributes = [];
/** @var array */
protected $aliases = [];
/** @var string */
protected $messageSeparator = ':';
/** @var array */
protected $validData = [];
/** @var array */
protected $invalidData = [];
/** @var ErrorBag */
public $errors;
/**
* Constructor
*
* @param \Rakit\Validation\Validator $validator
* @param array $inputs
* @param array $rules
* @param array $messages
* @return void
*/
public function __construct(
Validator $validator,
array $inputs,
array $rules,
array $messages = []
) {
$this->validator = $validator;
$this->inputs = $this->resolveInputAttributes($inputs);
$this->messages = $messages;
$this->errors = new ErrorBag;
foreach ($rules as $attributeKey => $rules) {
$this->addAttribute($attributeKey, $rules);
}
}
/**
* Add attribute rules
*
* @param string $attributeKey
* @param string|array $rules
* @return void
*/
public function addAttribute(string $attributeKey, $rules)
{
$resolvedRules = $this->resolveRules($rules);
$attribute = new Attribute($this, $attributeKey, $this->getAlias($attributeKey), $resolvedRules);
$this->attributes[$attributeKey] = $attribute;
}
/**
* Get attribute by key
*
* @param string $attributeKey
* @return null|\Rakit\Validation\Attribute
*/
public function getAttribute(string $attributeKey)
{
return isset($this->attributes[$attributeKey])? $this->attributes[$attributeKey] : null;
}
/**
* Run validation
*
* @param array $inputs
* @return void
*/
public function validate(array $inputs = [])
{
$this->errors = new ErrorBag; // reset error bag
$this->inputs = array_merge($this->inputs, $this->resolveInputAttributes($inputs));
// Before validation hooks
foreach ($this->attributes as $attributeKey => $attribute) {
foreach ($attribute->getRules() as $rule) {
if ($rule instanceof BeforeValidate) {
$rule->beforeValidate();
}
}
}
foreach ($this->attributes as $attributeKey => $attribute) {
$this->validateAttribute($attribute);
}
}
/**
* Get ErrorBag instance
*
* @return \Rakit\Validation\ErrorBag
*/
public function errors(): ErrorBag
{
return $this->errors;
}
/**
* Validate attribute
*
* @param \Rakit\Validation\Attribute $attribute
* @return void
*/
protected function validateAttribute(Attribute $attribute)
{
if ($this->isArrayAttribute($attribute)) {
$attributes = $this->parseArrayAttribute($attribute);
foreach ($attributes as $i => $attr) {
$this->validateAttribute($attr);
}
return;
}
$attributeKey = $attribute->getKey();
$rules = $attribute->getRules();
$value = $this->getValue($attributeKey);
$isEmptyValue = $this->isEmptyValue($value);
if ($attribute->hasRule('nullable') && $isEmptyValue) {
$rules = [];
}
$isValid = true;
foreach ($rules as $ruleValidator) {
$ruleValidator->setAttribute($attribute);
if ($ruleValidator instanceof ModifyValue) {
$value = $ruleValidator->modifyValue($value);
$isEmptyValue = $this->isEmptyValue($value);
}
$valid = $ruleValidator->check($value);
if ($isEmptyValue and $this->ruleIsOptional($attribute, $ruleValidator)) {
continue;
}
if (!$valid) {
$isValid = false;
$this->addError($attribute, $value, $ruleValidator);
if ($ruleValidator->isImplicit()) {
break;
}
}
}
if ($isValid) {
$this->setValidData($attribute, $value);
} else {
$this->setInvalidData($attribute, $value);
}
}
/**
* Check whether given $attribute is array attribute
*
* @param \Rakit\Validation\Attribute $attribute
* @return bool
*/
protected function isArrayAttribute(Attribute $attribute): bool
{
$key = $attribute->getKey();
return strpos($key, '*') !== false;
}
/**
* Parse array attribute into it's child attributes
*
* @param \Rakit\Validation\Attribute $attribute
* @return array
*/
protected function parseArrayAttribute(Attribute $attribute): array
{
$attributeKey = $attribute->getKey();
$data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey));
$pattern = str_replace('\*', '([^\.]+)', preg_quote($attributeKey));
$data = array_merge($data, $this->extractValuesForWildcards(
$data,
$attributeKey
));
$attributes = [];
foreach ($data as $key => $value) {
if ((bool) preg_match('/^'.$pattern.'\z/', $key, $match)) {
$attr = new Attribute($this, $key, null, $attribute->getRules());
$attr->setPrimaryAttribute($attribute);
$attr->setKeyIndexes(array_slice($match, 1));
$attributes[] = $attr;
}
}
// set other attributes to each attributes
foreach ($attributes as $i => $attr) {
$otherAttributes = $attributes;
unset($otherAttributes[$i]);
$attr->setOtherAttributes($otherAttributes);
}
return $attributes;
}
/**
* Gather a copy of the attribute data filled with any missing attributes.
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L334
*
* @param string $attribute
* @return array
*/
protected function initializeAttributeOnData(string $attributeKey): array
{
$explicitPath = $this->getLeadingExplicitAttributePath($attributeKey);
$data = $this->extractDataFromPath($explicitPath);
$asteriskPos = strpos($attributeKey, '*');
if (false === $asteriskPos || $asteriskPos === (mb_strlen($attributeKey, 'UTF-8') - 1)) {
return $data;
}
return Helper::arraySet($data, $attributeKey, null, true);
}
/**
* Get all of the exact attribute values for a given wildcard attribute.
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L354
*
* @param array $data
* @param string $attributeKey
* @return array
*/
public function extractValuesForWildcards(array $data, string $attributeKey): array
{
$keys = [];
$pattern = str_replace('\*', '[^\.]+', preg_quote($attributeKey));
foreach ($data as $key => $value) {
if ((bool) preg_match('/^'.$pattern.'/', $key, $matches)) {
$keys[] = $matches[0];
}
}
$keys = array_unique($keys);
$data = [];
foreach ($keys as $key) {
$data[$key] = Helper::arrayGet($this->inputs, $key);
}
return $data;
}
/**
* Get the explicit part of the attribute name.
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L2817
*
* E.g. 'foo.bar.*.baz' -> 'foo.bar'
*
* Allows us to not spin through all of the flattened data for some operations.
*
* @param string $attributeKey
* @return string|null null when root wildcard
*/
protected function getLeadingExplicitAttributePath(string $attributeKey)
{
return rtrim(explode('*', $attributeKey)[0], '.') ?: null;
}
/**
* Extract data based on the given dot-notated path.
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L2830
*
* Used to extract a sub-section of the data for faster iteration.
*
* @param string|null $attributeKey
* @return array
*/
protected function extractDataFromPath($attributeKey): array
{
$results = [];
$value = Helper::arrayGet($this->inputs, $attributeKey, '__missing__');
if ($value != '__missing__') {
Helper::arraySet($results, $attributeKey, $value);
}
return $results;
}
/**
* Add error to the $this->errors
*
* @param \Rakit\Validation\Attribute $attribute
* @param mixed $value
* @param \Rakit\Validation\Rule $ruleValidator
* @return void
*/
protected function addError(Attribute $attribute, $value, Rule $ruleValidator)
{
$ruleName = $ruleValidator->getKey();
$message = $this->resolveMessage($attribute, $value, $ruleValidator);
$this->errors->add($attribute->getKey(), $ruleName, $message);
}
/**
* Check $value is empty value
*
* @param mixed $value
* @return boolean
*/
protected function isEmptyValue($value): bool
{
$requiredValidator = new Required;
return false === $requiredValidator->check($value, []);
}
/**
* Check the rule is optional
*
* @param \Rakit\Validation\Attribute $attribute
* @param \Rakit\Validation\Rule $rule
* @return bool
*/
protected function ruleIsOptional(Attribute $attribute, Rule $rule): bool
{
return false === $attribute->isRequired() and
false === $rule->isImplicit() and
false === $rule instanceof Required;
}
/**
* Resolve attribute name
*
* @param \Rakit\Validation\Attribute $attribute
* @return string
*/
protected function resolveAttributeName(Attribute $attribute): string
{
$primaryAttribute = $attribute->getPrimaryAttribute();
if (isset($this->aliases[$attribute->getKey()])) {
return $this->aliases[$attribute->getKey()];
} elseif ($primaryAttribute and isset($this->aliases[$primaryAttribute->getKey()])) {
return $this->aliases[$primaryAttribute->getKey()];
} elseif ($this->validator->isUsingHumanizedKey()) {
return $attribute->getHumanizedKey();
} else {
return $attribute->getKey();
}
}
/**
* Resolve message
*
* @param \Rakit\Validation\Attribute $attribute
* @param mixed $value
* @param \Rakit\Validation\Rule $validator
* @return mixed
*/
protected function resolveMessage(Attribute $attribute, $value, Rule $validator): string
{
$primaryAttribute = $attribute->getPrimaryAttribute();
$params = array_merge($validator->getParameters(), $validator->getParametersTexts());
$attributeKey = $attribute->getKey();
$ruleKey = $validator->getKey();
$alias = $attribute->getAlias() ?: $this->resolveAttributeName($attribute);
$message = $validator->getMessage(); // default rule message
$messageKeys = [
$attributeKey.$this->messageSeparator.$ruleKey,
$attributeKey,
$ruleKey
];
if ($primaryAttribute) {
// insert primaryAttribute keys
// $messageKeys = [
// $attributeKey.$this->messageSeparator.$ruleKey,
// >> here [1] <<
// $attributeKey,
// >> and here [3] <<
// $ruleKey
// ];
$primaryAttributeKey = $primaryAttribute->getKey();
array_splice($messageKeys, 1, 0, $primaryAttributeKey.$this->messageSeparator.$ruleKey);
array_splice($messageKeys, 3, 0, $primaryAttributeKey);
}
foreach ($messageKeys as $key) {
if (isset($this->messages[$key])) {
$message = $this->messages[$key];
break;
}
}
// Replace message params
$vars = array_merge($params, [
'attribute' => $alias,
'value' => $value,
]);
foreach ($vars as $key => $value) {
$value = $this->stringify($value);
$message = str_replace(':'.$key, $value, $message);
}
// Replace key indexes
$keyIndexes = $attribute->getKeyIndexes();
foreach ($keyIndexes as $pathIndex => $index) {
$replacers = [
"[{$pathIndex}]" => $index,
];
if (is_numeric($index)) {
$replacers["{{$pathIndex}}"] = $index + 1;
}
$message = str_replace(array_keys($replacers), array_values($replacers), $message);
}
return $message;
}
/**
* Stringify $value
*
* @param mixed $value
* @return string
*/
protected function stringify($value): string
{
if (is_string($value) || is_numeric($value)) {
return $value;
} elseif (is_array($value) || is_object($value)) {
return json_encode($value);
} else {
return '';
}
}
/**
* Resolve $rules
*
* @param mixed $rules
* @return array
*/
protected function resolveRules($rules): array
{
if (is_string($rules)) {
$rules = explode('|', $rules);
}
$resolvedRules = [];
$validatorFactory = $this->getValidator();
foreach ($rules as $i => $rule) {
if (empty($rule)) {
continue;
}
$params = [];
if (is_string($rule)) {
list($rulename, $params) = $this->parseRule($rule);
$validator = call_user_func_array($validatorFactory, array_merge([$rulename], $params));
} elseif ($rule instanceof Rule) {
$validator = $rule;
} elseif ($rule instanceof Closure) {
$validator = call_user_func_array($validatorFactory, ['callback', $rule]);
} else {
$ruleName = is_object($rule) ? get_class($rule) : gettype($rule);
$message = "Rule must be a string, Closure or '".Rule::class."' instance. ".$ruleName." given";
throw new \Exception();
}
$resolvedRules[] = $validator;
}
return $resolvedRules;
}
/**
* Parse $rule
*
* @param string $rule
* @return array
*/
protected function parseRule(string $rule): array
{
$exp = explode(':', $rule, 2);
$rulename = $exp[0];
if ($rulename !== 'regex') {
$params = isset($exp[1])? explode(',', $exp[1]) : [];
} else {
$params = [$exp[1]];
}
return [$rulename, $params];
}
/**
* Given $attributeKey and $alias then assign alias
*
* @param mixed $attributeKey
* @param mixed $alias
* @return void
*/
public function setAlias(string $attributeKey, string $alias)
{
$this->aliases[$attributeKey] = $alias;
}
/**
* Get attribute alias from given key
*
* @param mixed $attributeKey
* @return string|null
*/
public function getAlias(string $attributeKey)
{
return isset($this->aliases[$attributeKey])? $this->aliases[$attributeKey] : null;
}
/**
* Set attributes aliases
*
* @param array $aliases
* @return void
*/
public function setAliases(array $aliases)
{
$this->aliases = array_merge($this->aliases, $aliases);
}
/**
* Check validations are passed
*
* @return bool
*/
public function passes(): bool
{
return $this->errors->count() == 0;
}
/**
* Check validations are failed
*
* @return bool
*/
public function fails(): bool
{
return !$this->passes();
}
/**
* Given $key and get value
*
* @param string $key
* @return mixed
*/
public function getValue(string $key)
{
return Helper::arrayGet($this->inputs, $key);
}
/**
* Set input value
*
* @param string $key
* @param mixed $value
* @return void
*/
public function setValue(string $key, $value)
{
Helper::arraySet($this->inputs, $key, $value);
}
/**
* Given $key and check value is exsited
*
* @param string $key
* @return boolean
*/
public function hasValue(string $key): bool
{
return Helper::arrayHas($this->inputs, $key);
}
/**
* Get Validator class instance
*
* @return \Rakit\Validation\Validator
*/
public function getValidator(): Validator
{
return $this->validator;
}
/**
* Given $inputs and resolve input attributes
*
* @param array $inputs
* @return array
*/
protected function resolveInputAttributes(array $inputs): array
{
$resolvedInputs = [];
foreach ($inputs as $key => $rules) {
$exp = explode(':', $key);
if (count($exp) > 1) {
// set attribute alias
$this->aliases[$exp[0]] = $exp[1];
}
$resolvedInputs[$exp[0]] = $rules;
}
return $resolvedInputs;
}
/**
* Get validated data
*
* @return array
*/
public function getValidatedData(): array
{
return array_merge($this->validData, $this->invalidData);
}
/**
* Set valid data
*
* @param \Rakit\Validation\Attribute $attribute
* @param mixed $value
* @return void
*/
protected function setValidData(Attribute $attribute, $value)
{
$key = $attribute->getKey();
if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) {
Helper::arraySet($this->validData, $key, $value);
Helper::arrayUnset($this->invalidData, $key);
} else {
$this->validData[$key] = $value;
}
}
/**
* Get valid data
*
* @return array
*/
public function getValidData(): array
{
return $this->validData;
}
/**
* Set invalid data
*
* @param \Rakit\Validation\Attribute $attribute
* @param mixed $value
* @return void
*/
protected function setInvalidData(Attribute $attribute, $value)
{
$key = $attribute->getKey();
if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) {
Helper::arraySet($this->invalidData, $key, $value);
Helper::arrayUnset($this->validData, $key);
} else {
$this->invalidData[$key] = $value;
}
}
/**
* Get invalid data
*
* @return void
*/
public function getInvalidData(): array
{
return $this->invalidData;
}
}
+221
View File
@@ -0,0 +1,221 @@
<?php
namespace Rakit\Validation;
class Validator
{
use Traits\TranslationsTrait, Traits\MessagesTrait;
/** @var array */
protected $translations = [];
/** @var array */
protected $validators = [];
/** @var bool */
protected $allowRuleOverride = false;
/** @var bool */
protected $useHumanizedKeys = true;
/**
* Constructor
*
* @param array $messages
* @return void
*/
public function __construct(array $messages = [])
{
$this->messages = $messages;
$this->registerBaseValidators();
}
/**
* Register or override existing validator
*
* @param mixed $key
* @param \Rakit\Validation\Rule $rule
* @return void
*/
public function setValidator(string $key, Rule $rule)
{
$this->validators[$key] = $rule;
$rule->setKey($key);
}
/**
* Get validator object from given $key
*
* @param mixed $key
* @return mixed
*/
public function getValidator($key)
{
return isset($this->validators[$key]) ? $this->validators[$key] : null;
}
/**
* Validate $inputs
*
* @param array $inputs
* @param array $rules
* @param array $messages
* @return Validation
*/
public function validate(array $inputs, array $rules, array $messages = []): Validation
{
$validation = $this->make($inputs, $rules, $messages);
$validation->validate();
return $validation;
}
/**
* Given $inputs, $rules and $messages to make the Validation class instance
*
* @param array $inputs
* @param array $rules
* @param array $messages
* @return Validation
*/
public function make(array $inputs, array $rules, array $messages = []): Validation
{
$messages = array_merge($this->messages, $messages);
$validation = new Validation($this, $inputs, $rules, $messages);
$validation->setTranslations($this->getTranslations());
return $validation;
}
/**
* Magic invoke method to make Rule instance
*
* @param string $rule
* @return Rule
* @throws RuleNotFoundException
*/
public function __invoke(string $rule): Rule
{
$args = func_get_args();
$rule = array_shift($args);
$params = $args;
$validator = $this->getValidator($rule);
if (!$validator) {
throw new RuleNotFoundException("Validator '{$rule}' is not registered", 1);
}
$clonedValidator = clone $validator;
$clonedValidator->fillParameters($params);
return $clonedValidator;
}
/**
* Initialize base validators array
*
* @return void
*/
protected function registerBaseValidators()
{
$baseValidator = [
'required' => new Rules\Required,
'required_if' => new Rules\RequiredIf,
'required_unless' => new Rules\RequiredUnless,
'required_with' => new Rules\RequiredWith,
'required_without' => new Rules\RequiredWithout,
'required_with_all' => new Rules\RequiredWithAll,
'required_without_all' => new Rules\RequiredWithoutAll,
'email' => new Rules\Email,
'alpha' => new Rules\Alpha,
'numeric' => new Rules\Numeric,
'alpha_num' => new Rules\AlphaNum,
'alpha_dash' => new Rules\AlphaDash,
'alpha_spaces' => new Rules\AlphaSpaces,
'in' => new Rules\In,
'not_in' => new Rules\NotIn,
'min' => new Rules\Min,
'max' => new Rules\Max,
'between' => new Rules\Between,
'url' => new Rules\Url,
'integer' => new Rules\Integer,
'boolean' => new Rules\Boolean,
'ip' => new Rules\Ip,
'ipv4' => new Rules\Ipv4,
'ipv6' => new Rules\Ipv6,
'extension' => new Rules\Extension,
'array' => new Rules\TypeArray,
'same' => new Rules\Same,
'regex' => new Rules\Regex,
'date' => new Rules\Date,
'accepted' => new Rules\Accepted,
'present' => new Rules\Present,
'different' => new Rules\Different,
'uploaded_file' => new Rules\UploadedFile,
'mimes' => new Rules\Mimes,
'callback' => new Rules\Callback,
'before' => new Rules\Before,
'after' => new Rules\After,
'lowercase' => new Rules\Lowercase,
'uppercase' => new Rules\Uppercase,
'json' => new Rules\Json,
'digits' => new Rules\Digits,
'digits_between' => new Rules\DigitsBetween,
'defaults' => new Rules\Defaults,
'default' => new Rules\Defaults, // alias of defaults
'nullable' => new Rules\Nullable,
];
foreach ($baseValidator as $key => $validator) {
$this->setValidator($key, $validator);
}
}
/**
* Given $ruleName and $rule to add new validator
*
* @param string $ruleName
* @param \Rakit\Validation\Rule $rule
* @return void
*/
public function addValidator(string $ruleName, Rule $rule)
{
if (!$this->allowRuleOverride && array_key_exists($ruleName, $this->validators)) {
throw new RuleQuashException(
"You cannot override a built in rule. You have to rename your rule"
);
}
$this->setValidator($ruleName, $rule);
}
/**
* Set rule can allow to be overrided
*
* @param boolean $status
* @return void
*/
public function allowRuleOverride(bool $status = false)
{
$this->allowRuleOverride = $status;
}
/**
* Set this can use humanize keys
*
* @param boolean $useHumanizedKeys
* @return void
*/
public function setUseHumanizedKeys(bool $useHumanizedKeys = true)
{
$this->useHumanizedKeys = $useHumanizedKeys;
}
/**
* Get $this->useHumanizedKeys value
*
* @return void
*/
public function isUsingHumanizedKey(): bool
{
return $this->useHumanizedKeys;
}
}