Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -0,0 +1,70 @@
<?php
namespace Illuminate\Validation;
use Illuminate\Contracts\Validation\Rule as RuleContract;
class ClosureValidationRule implements RuleContract
{
/**
* The callback that validates the attribute.
*
* @var \Closure
*/
public $callback;
/**
* Indicates if the validation callback failed.
*
* @var bool
*/
public $failed = false;
/**
* The validation error message.
*
* @var string|null
*/
public $message;
/**
* Create a new Closure based validation rule.
*
* @param \Closure $callback
* @return void
*/
public function __construct($callback)
{
$this->callback = $callback;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$this->failed = false;
$this->callback->__invoke($attribute, $value, function ($message) {
$this->failed = true;
$this->message = $message;
});
return ! $this->failed;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return $this->message;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Illuminate\Validation\Concerns;
use Egulias\EmailValidator\EmailLexer;
use Egulias\EmailValidator\Result\InvalidEmail;
use Egulias\EmailValidator\Validation\EmailValidation;
class FilterEmailValidation implements EmailValidation
{
/**
* The flags to pass to the filter_var function.
*
* @var int|null
*/
protected $flags;
/**
* Create a new validation instance.
*
* @param int $flags
* @return void
*/
public function __construct($flags = null)
{
$this->flags = $flags;
}
/**
* Create a new instance which allows any unicode characters in local-part.
*
* @return static
*/
public static function unicode()
{
return new static(FILTER_FLAG_EMAIL_UNICODE);
}
/**
* Returns true if the given email is valid.
*
* @param string $email
* @param \Egulias\EmailValidator\EmailLexer $emailLexer
* @return bool
*/
public function isValid(string $email, EmailLexer $emailLexer): bool
{
return is_null($this->flags)
? filter_var($email, FILTER_VALIDATE_EMAIL) !== false
: filter_var($email, FILTER_VALIDATE_EMAIL, $this->flags) !== false;
}
/**
* Returns the validation error.
*
* @return \Egulias\EmailValidator\Result\InvalidEmail|null
*/
public function getError(): ?InvalidEmail
{
return null;
}
/**
* Returns the validation warnings.
*
* @return \Egulias\EmailValidator\Warning\Warning[]
*/
public function getWarnings(): array
{
return [];
}
}

View File

@@ -20,9 +20,11 @@ trait FormatsMessages
*/
protected function getMessage($attribute, $rule)
{
$inlineMessage = $this->getFromLocalArray(
$attribute, $lowerRule = Str::snake($rule)
);
$attributeWithPlaceholders = $attribute;
$attribute = $this->replacePlaceholderInString($attribute);
$inlineMessage = $this->getInlineMessage($attribute, $rule);
// First we will retrieve the custom message for the validation rule if one
// exists. If a custom validation message is being used we'll return the
@@ -31,8 +33,14 @@ trait FormatsMessages
return $inlineMessage;
}
$lowerRule = Str::snake($rule);
$customKey = "validation.custom.{$attribute}.{$lowerRule}";
$customMessage = $this->getCustomMessageFromTranslator(
$customKey = "validation.custom.{$attribute}.{$lowerRule}"
in_array($rule, $this->sizeRules)
? [$customKey.".{$this->getAttributeType($attribute)}", $customKey]
: $customKey
);
// First we check for a custom defined validation message for the attribute
@@ -46,7 +54,7 @@ trait FormatsMessages
// specific error message for the type of attribute being validated such
// as a number, file or string which all have different message types.
elseif (in_array($rule, $this->sizeRules)) {
return $this->getSizeMessage($attribute, $rule);
return $this->getSizeMessage($attributeWithPlaceholders, $rule);
}
// Finally, if no developer specified messages have been set, and no other
@@ -54,7 +62,7 @@ trait FormatsMessages
// messages out of the translator service for this validation rule.
$key = "validation.{$lowerRule}";
if ($key != ($value = $this->translator->trans($key))) {
if ($key !== ($value = $this->translator->get($key))) {
return $value;
}
@@ -63,54 +71,100 @@ trait FormatsMessages
) ?: $key;
}
/**
* Get the proper inline error message for standard and size rules.
*
* @param string $attribute
* @param string $rule
* @return string|null
*/
protected function getInlineMessage($attribute, $rule)
{
$inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule));
return is_array($inlineEntry) && in_array($rule, $this->sizeRules)
? $inlineEntry[$this->getAttributeType($attribute)]
: $inlineEntry;
}
/**
* Get the inline message for a rule if it exists.
*
* @param string $attribute
* @param string $lowerRule
* @param array $source
* @param array|null $source
* @return string|null
*/
protected function getFromLocalArray($attribute, $lowerRule, $source = null)
{
$source = $source ?: $this->customMessages;
$keys = ["{$attribute}.{$lowerRule}", $lowerRule];
$keys = ["{$attribute}.{$lowerRule}", $lowerRule, $attribute];
// First we will check for a custom message for an attribute specific rule
// message for the fields, then we will check for a general custom line
// that is not attribute specific. If we find either we'll return it.
foreach ($keys as $key) {
foreach (array_keys($source) as $sourceKey) {
if (str_contains($sourceKey, '*')) {
$pattern = str_replace('\*', '([^.]*)', preg_quote($sourceKey, '#'));
if (preg_match('#^'.$pattern.'\z#u', $key) === 1) {
$message = $source[$sourceKey];
if (is_array($message) && isset($message[$lowerRule])) {
return $message[$lowerRule];
}
return $message;
}
continue;
}
if (Str::is($sourceKey, $key)) {
return $source[$sourceKey];
$message = $source[$sourceKey];
if ($sourceKey === $attribute && is_array($message) && isset($message[$lowerRule])) {
return $message[$lowerRule];
}
return $message;
}
}
}
}
/**
* Get the custom error message from translator.
* Get the custom error message from the translator.
*
* @param string $key
* @param array|string $keys
* @return string
*/
protected function getCustomMessageFromTranslator($key)
protected function getCustomMessageFromTranslator($keys)
{
if (($message = $this->translator->trans($key)) !== $key) {
return $message;
foreach (Arr::wrap($keys) as $key) {
if (($message = $this->translator->get($key)) !== $key) {
return $message;
}
// If an exact match was not found for the key, we will collapse all of these
// messages and loop through them and try to find a wildcard match for the
// given key. Otherwise, we will simply return the key's value back out.
$shortKey = preg_replace(
'/^validation\.custom\./', '', $key
);
$message = $this->getWildcardCustomMessages(Arr::dot(
(array) $this->translator->get('validation.custom')
), $shortKey, $key);
if ($message !== $key) {
return $message;
}
}
// If an exact match was not found for the key, we will collapse all of these
// messages and loop through them and try to find a wildcard match for the
// given key. Otherwise, we will simply return the key's value back out.
$shortKey = preg_replace(
'/^validation\.custom\./', '', $key
);
return $this->getWildcardCustomMessages(Arr::dot(
(array) $this->translator->trans('validation.custom')
), $shortKey, $key);
return Arr::last(Arr::wrap($keys));
}
/**
@@ -150,7 +204,7 @@ trait FormatsMessages
$key = "validation.{$lowerRule}.{$type}";
return $this->translator->trans($key);
return $this->translator->get($key);
}
/**
@@ -181,7 +235,7 @@ trait FormatsMessages
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array $parameters
* @return string
*/
public function makeReplacements($message, $attribute, $rule, $parameters)
@@ -190,6 +244,10 @@ trait FormatsMessages
$message, $this->getDisplayableAttribute($attribute)
);
$message = $this->replaceInputPlaceholder($message, $attribute);
$message = $this->replaceIndexPlaceholder($message, $attribute);
$message = $this->replacePositionPlaceholder($message, $attribute);
if (isset($this->replacers[Str::snake($rule)])) {
return $this->callReplacer($message, $attribute, Str::snake($rule), $parameters, $this);
} elseif (method_exists($this, $replacer = "replace{$rule}")) {
@@ -232,7 +290,9 @@ trait FormatsMessages
// an implicit attribute we will display the raw attribute's name and not
// modify it with any of these replacements before we display the name.
if (isset($this->implicitAttributes[$primaryAttribute])) {
return $attribute;
return ($formatter = $this->implicitAttributesFormatter)
? $formatter($attribute)
: $attribute;
}
return str_replace('_', ' ', Str::snake($attribute));
@@ -246,7 +306,7 @@ trait FormatsMessages
*/
protected function getAttributeFromTranslations($name)
{
return Arr::get($this->translator->trans('validation.attributes'), $name);
return Arr::get($this->translator->get('validation.attributes'), $name);
}
/**
@@ -265,11 +325,115 @@ trait FormatsMessages
);
}
/**
* Replace the :index placeholder in the given message.
*
* @param string $message
* @param string $attribute
* @return string
*/
protected function replaceIndexPlaceholder($message, $attribute)
{
return $this->replaceIndexOrPositionPlaceholder(
$message, $attribute, 'index'
);
}
/**
* Replace the :position placeholder in the given message.
*
* @param string $message
* @param string $attribute
* @return string
*/
protected function replacePositionPlaceholder($message, $attribute)
{
return $this->replaceIndexOrPositionPlaceholder(
$message, $attribute, 'position', fn ($segment) => $segment + 1
);
}
/**
* Replace the :index or :position placeholder in the given message.
*
* @param string $message
* @param string $attribute
* @param string $placeholder
* @param \Closure|null $modifier
* @return string
*/
protected function replaceIndexOrPositionPlaceholder($message, $attribute, $placeholder, Closure $modifier = null)
{
$segments = explode('.', $attribute);
$modifier ??= fn ($value) => $value;
$numericIndex = 1;
foreach ($segments as $segment) {
if (is_numeric($segment)) {
if ($numericIndex === 1) {
$message = str_ireplace(':'.$placeholder, $modifier((int) $segment), $message);
}
$message = str_ireplace(
':'.$this->numberToIndexOrPositionWord($numericIndex).'-'.$placeholder,
$modifier((int) $segment),
$message
);
$numericIndex++;
}
}
return $message;
}
/**
* Get the word for a index or position segment.
*
* @param int $value
* @return string
*/
protected function numberToIndexOrPositionWord(int $value)
{
return [
1 => 'first',
2 => 'second',
3 => 'third',
4 => 'fourth',
5 => 'fifth',
6 => 'sixth',
7 => 'seventh',
8 => 'eighth',
9 => 'ninth',
10 => 'tenth',
][(int) $value] ?? 'other';
}
/**
* Replace the :input placeholder in the given message.
*
* @param string $message
* @param string $attribute
* @return string
*/
protected function replaceInputPlaceholder($message, $attribute)
{
$actualValue = $this->getValue($attribute);
if (is_scalar($actualValue) || is_null($actualValue)) {
$message = str_replace(':input', $this->getDisplayableValue($attribute, $actualValue), $message);
}
return $message;
}
/**
* Get the displayable name of the value.
*
* @param string $attribute
* @param mixed $value
* @param mixed $value
* @return string
*/
public function getDisplayableValue($attribute, $value)
@@ -280,11 +444,19 @@ trait FormatsMessages
$key = "validation.values.{$attribute}.{$value}";
if (($line = $this->translator->trans($key)) !== $key) {
if (($line = $this->translator->get($key)) !== $key) {
return $line;
}
return $value;
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if (is_null($value)) {
return 'empty';
}
return (string) $value;
}
/**
@@ -313,7 +485,7 @@ trait FormatsMessages
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array $parameters
* @param \Illuminate\Validation\Validator $validator
* @return string|null
*/
@@ -322,7 +494,7 @@ trait FormatsMessages
$callback = $this->replacers[$rule];
if ($callback instanceof Closure) {
return call_user_func_array($callback, func_get_args());
return $callback(...func_get_args());
} elseif (is_string($callback)) {
return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator);
}
@@ -335,14 +507,14 @@ trait FormatsMessages
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array $parameters
* @param \Illuminate\Validation\Validator $validator
* @return string
*/
protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator)
{
list($class, $method) = Str::parseCallback($callback, 'replace');
[$class, $method] = Str::parseCallback($callback, 'replace');
return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1));
return $this->container->make($class)->{$method}(...array_slice(func_get_args(), 1));
}
}

View File

@@ -6,13 +6,49 @@ use Illuminate\Support\Arr;
trait ReplacesAttributes
{
/**
* Replace all place-holders for the accepted_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceAcceptedIf($message, $attribute, $rule, $parameters)
{
$parameters[1] = $this->getDisplayableValue($parameters[0], Arr::get($this->data, $parameters[0]));
$parameters[0] = $this->getDisplayableAttribute($parameters[0]);
return str_replace([':other', ':value'], $parameters, $message);
}
/**
* Replace all place-holders for the declined_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDeclinedIf($message, $attribute, $rule, $parameters)
{
$parameters[1] = $this->getDisplayableValue($parameters[0], Arr::get($this->data, $parameters[0]));
$parameters[0] = $this->getDisplayableAttribute($parameters[0]);
return str_replace([':other', ':value'], $parameters, $message);
}
/**
* Replace all place-holders for the between rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceBetween($message, $attribute, $rule, $parameters)
@@ -26,7 +62,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDateFormat($message, $attribute, $rule, $parameters)
@@ -34,13 +70,33 @@ trait ReplacesAttributes
return str_replace(':format', $parameters[0], $message);
}
/**
* Replace all place-holders for the decimal rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,int> $parameters
* @return string
*/
protected function replaceDecimal($message, $attribute, $rule, $parameters)
{
return str_replace(
':decimal',
isset($parameters[1])
? $parameters[0].'-'.$parameters[1]
: $parameters[0],
$message
);
}
/**
* Replace all place-holders for the different rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDifferent($message, $attribute, $rule, $parameters)
@@ -54,7 +110,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDigits($message, $attribute, $rule, $parameters)
@@ -68,7 +124,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDigitsBetween($message, $attribute, $rule, $parameters)
@@ -82,7 +138,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMin($message, $attribute, $rule, $parameters)
@@ -90,13 +146,27 @@ trait ReplacesAttributes
return str_replace(':min', $parameters[0], $message);
}
/**
* Replace all place-holders for the min digits rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMinDigits($message, $attribute, $rule, $parameters)
{
return str_replace(':min', $parameters[0], $message);
}
/**
* Replace all place-holders for the max rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMax($message, $attribute, $rule, $parameters)
@@ -104,13 +174,41 @@ trait ReplacesAttributes
return str_replace(':max', $parameters[0], $message);
}
/**
* Replace all place-holders for the max digits rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMaxDigits($message, $attribute, $rule, $parameters)
{
return str_replace(':max', $parameters[0], $message);
}
/**
* Replace all place-holders for the multiple_of rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMultipleOf($message, $attribute, $rule, $parameters)
{
return str_replace(':value', $parameters[0] ?? '', $message);
}
/**
* Replace all place-holders for the in rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceIn($message, $attribute, $rule, $parameters)
@@ -128,7 +226,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceNotIn($message, $attribute, $rule, $parameters)
@@ -142,7 +240,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceInArray($message, $attribute, $rule, $parameters)
@@ -150,13 +248,31 @@ trait ReplacesAttributes
return str_replace(':other', $this->getDisplayableAttribute($parameters[0]), $message);
}
/**
* Replace all place-holders for the required_array_keys rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredArrayKeys($message, $attribute, $rule, $parameters)
{
foreach ($parameters as &$parameter) {
$parameter = $this->getDisplayableValue($attribute, $parameter);
}
return str_replace(':values', implode(', ', $parameters), $message);
}
/**
* Replace all place-holders for the mimetypes rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMimetypes($message, $attribute, $rule, $parameters)
@@ -170,7 +286,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceMimes($message, $attribute, $rule, $parameters)
@@ -184,7 +300,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredWith($message, $attribute, $rule, $parameters)
@@ -198,7 +314,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredWithAll($message, $attribute, $rule, $parameters)
@@ -212,7 +328,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredWithout($message, $attribute, $rule, $parameters)
@@ -226,7 +342,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredWithoutAll($message, $attribute, $rule, $parameters)
@@ -240,7 +356,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceSize($message, $attribute, $rule, $parameters)
@@ -248,13 +364,85 @@ trait ReplacesAttributes
return str_replace(':size', $parameters[0], $message);
}
/**
* Replace all place-holders for the gt rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceGt($message, $attribute, $rule, $parameters)
{
if (is_null($value = $this->getValue($parameters[0]))) {
return str_replace(':value', $this->getDisplayableAttribute($parameters[0]), $message);
}
return str_replace(':value', $this->getSize($attribute, $value), $message);
}
/**
* Replace all place-holders for the lt rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceLt($message, $attribute, $rule, $parameters)
{
if (is_null($value = $this->getValue($parameters[0]))) {
return str_replace(':value', $this->getDisplayableAttribute($parameters[0]), $message);
}
return str_replace(':value', $this->getSize($attribute, $value), $message);
}
/**
* Replace all place-holders for the gte rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceGte($message, $attribute, $rule, $parameters)
{
if (is_null($value = $this->getValue($parameters[0]))) {
return str_replace(':value', $this->getDisplayableAttribute($parameters[0]), $message);
}
return str_replace(':value', $this->getSize($attribute, $value), $message);
}
/**
* Replace all place-holders for the lte rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceLte($message, $attribute, $rule, $parameters)
{
if (is_null($value = $this->getValue($parameters[0]))) {
return str_replace(':value', $this->getDisplayableAttribute($parameters[0]), $message);
}
return str_replace(':value', $this->getSize($attribute, $value), $message);
}
/**
* Replace all place-holders for the required_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredIf($message, $attribute, $rule, $parameters)
@@ -266,20 +454,96 @@ trait ReplacesAttributes
return str_replace([':other', ':value'], $parameters, $message);
}
/**
* Replace all place-holders for the required_if_accepted rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredIfAccepted($message, $attribute, $rule, $parameters)
{
$parameters[0] = $this->getDisplayableAttribute($parameters[0]);
return str_replace([':other'], $parameters, $message);
}
/**
* Replace all place-holders for the required_unless rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceRequiredUnless($message, $attribute, $rule, $parameters)
{
$other = $this->getDisplayableAttribute(array_shift($parameters));
$other = $this->getDisplayableAttribute($parameters[0]);
return str_replace([':other', ':values'], [$other, implode(', ', $parameters)], $message);
$values = [];
foreach (array_slice($parameters, 1) as $value) {
$values[] = $this->getDisplayableValue($parameters[0], $value);
}
return str_replace([':other', ':values'], [$other, implode(', ', $values)], $message);
}
/**
* Replace all place-holders for the prohibited_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceProhibitedIf($message, $attribute, $rule, $parameters)
{
$parameters[1] = $this->getDisplayableValue($parameters[0], Arr::get($this->data, $parameters[0]));
$parameters[0] = $this->getDisplayableAttribute($parameters[0]);
return str_replace([':other', ':value'], $parameters, $message);
}
/**
* Replace all place-holders for the prohibited_unless rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceProhibitedUnless($message, $attribute, $rule, $parameters)
{
$other = $this->getDisplayableAttribute($parameters[0]);
$values = [];
foreach (array_slice($parameters, 1) as $value) {
$values[] = $this->getDisplayableValue($parameters[0], $value);
}
return str_replace([':other', ':values'], [$other, implode(', ', $values)], $message);
}
/**
* Replace all place-holders for the prohibited_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceProhibits($message, $attribute, $rule, $parameters)
{
return str_replace(':other', implode(' / ', $this->getAttributeList($parameters)), $message);
}
/**
@@ -288,7 +552,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceSame($message, $attribute, $rule, $parameters)
@@ -302,16 +566,16 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceBefore($message, $attribute, $rule, $parameters)
{
if (! (strtotime($parameters[0]))) {
if (! strtotime($parameters[0])) {
return str_replace(':date', $this->getDisplayableAttribute($parameters[0]), $message);
}
return str_replace(':date', $parameters[0], $message);
return str_replace(':date', $this->getDisplayableValue($attribute, $parameters[0]), $message);
}
/**
@@ -320,7 +584,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceBeforeOrEqual($message, $attribute, $rule, $parameters)
@@ -334,7 +598,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceAfter($message, $attribute, $rule, $parameters)
@@ -348,7 +612,7 @@ trait ReplacesAttributes
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceAfterOrEqual($message, $attribute, $rule, $parameters)
@@ -356,13 +620,27 @@ trait ReplacesAttributes
return $this->replaceBefore($message, $attribute, $rule, $parameters);
}
/**
* Replace all place-holders for the date_equals rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDateEquals($message, $attribute, $rule, $parameters)
{
return $this->replaceBefore($message, $attribute, $rule, $parameters);
}
/**
* Replace all place-holders for the dimensions rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDimensions($message, $attribute, $rule, $parameters)
@@ -377,4 +655,76 @@ trait ReplacesAttributes
return $message;
}
/**
* Replace all place-holders for the ends_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceEndsWith($message, $attribute, $rule, $parameters)
{
foreach ($parameters as &$parameter) {
$parameter = $this->getDisplayableValue($attribute, $parameter);
}
return str_replace(':values', implode(', ', $parameters), $message);
}
/**
* Replace all place-holders for the doesnt_end_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDoesntEndWith($message, $attribute, $rule, $parameters)
{
foreach ($parameters as &$parameter) {
$parameter = $this->getDisplayableValue($attribute, $parameter);
}
return str_replace(':values', implode(', ', $parameters), $message);
}
/**
* Replace all place-holders for the starts_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceStartsWith($message, $attribute, $rule, $parameters)
{
foreach ($parameters as &$parameter) {
$parameter = $this->getDisplayableValue($attribute, $parameter);
}
return str_replace(':values', implode(', ', $parameters), $message);
}
/**
* Replace all place-holders for the doesnt_start_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array<int,string> $parameters
* @return string
*/
protected function replaceDoesntStartWith($message, $attribute, $rule, $parameters)
{
foreach ($parameters as &$parameter) {
$parameter = $this->getDisplayableValue($attribute, $parameter);
}
return str_replace(':values', implode(', ', $parameters), $message);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Illuminate\Validation;
use Illuminate\Support\Fluent;
class ConditionalRules
{
/**
* The boolean condition indicating if the rules should be added to the attribute.
*
* @var callable|bool
*/
protected $condition;
/**
* The rules to be added to the attribute.
*
* @var array|string|\Closure
*/
protected $rules;
/**
* The rules to be added to the attribute if the condition fails.
*
* @var array|string|\Closure
*/
protected $defaultRules;
/**
* Create a new conditional rules instance.
*
* @param callable|bool $condition
* @param array|string|\Closure $rules
* @param array|string|\Closure $defaultRules
* @return void
*/
public function __construct($condition, $rules, $defaultRules = [])
{
$this->condition = $condition;
$this->rules = $rules;
$this->defaultRules = $defaultRules;
}
/**
* Determine if the conditional rules should be added.
*
* @param array $data
* @return bool
*/
public function passes(array $data = [])
{
return is_callable($this->condition)
? call_user_func($this->condition, new Fluent($data))
: $this->condition;
}
/**
* Get the rules.
*
* @param array $data
* @return array
*/
public function rules(array $data = [])
{
return is_string($this->rules)
? explode('|', $this->rules)
: value($this->rules, new Fluent($data));
}
/**
* Get the default rules.
*
* @param array $data
* @return array
*/
public function defaultRules(array $data = [])
{
return is_string($this->defaultRules)
? explode('|', $this->defaultRules)
: value($this->defaultRules, new Fluent($data));
}
}

View File

@@ -3,10 +3,9 @@
namespace Illuminate\Validation;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Database\ConnectionResolverInterface;
class DatabasePresenceVerifier implements PresenceVerifierInterface
class DatabasePresenceVerifier implements DatabasePresenceVerifierInterface
{
/**
* The database connection instance.
@@ -39,16 +38,16 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
* @param string $collection
* @param string $column
* @param string $value
* @param int $excludeId
* @param string $idColumn
* @param array $extra
* @param int|null $excludeId
* @param string|null $idColumn
* @param array $extra
* @return int
*/
public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = [])
{
$query = $this->table($collection)->where($column, '=', $value);
if (! is_null($excludeId) && $excludeId != 'NULL') {
if (! is_null($excludeId) && $excludeId !== 'NULL') {
$query->where($idColumn ?: 'id', '<>', $excludeId);
}
@@ -60,15 +59,15 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
*
* @param string $collection
* @param string $column
* @param array $values
* @param array $extra
* @param array $values
* @param array $extra
* @return int
*/
public function getMultiCount($collection, $column, array $values, array $extra = [])
{
$query = $this->table($collection)->whereIn($column, $values);
return $this->addConditions($query, $extra)->count();
return $this->addConditions($query, $extra)->distinct()->count($column);
}
/**
@@ -107,7 +106,7 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
$query->whereNull($key);
} elseif ($extraValue === 'NOT_NULL') {
$query->whereNotNull($key);
} elseif (Str::startsWith($extraValue, '!')) {
} elseif (str_starts_with($extraValue, '!')) {
$query->where($key, '!=', mb_substr($extraValue, 1));
} else {
$query->where($key, $extraValue);

View File

@@ -0,0 +1,14 @@
<?php
namespace Illuminate\Validation;
interface DatabasePresenceVerifierInterface extends PresenceVerifierInterface
{
/**
* Set the connection to be used.
*
* @param string $connection
* @return void
*/
public function setConnection($connection);
}

View File

@@ -3,10 +3,10 @@
namespace Illuminate\Validation;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Contracts\Validation\Factory as FactoryContract;
use Illuminate\Support\Str;
class Factory implements FactoryContract
{
@@ -27,57 +27,64 @@ class Factory implements FactoryContract
/**
* The IoC container instance.
*
* @var \Illuminate\Contracts\Container\Container
* @var \Illuminate\Contracts\Container\Container|null
*/
protected $container;
/**
* All of the custom validator extensions.
*
* @var array
* @var array<string, \Closure|string>
*/
protected $extensions = [];
/**
* All of the custom implicit validator extensions.
*
* @var array
* @var array<string, \Closure|string>
*/
protected $implicitExtensions = [];
/**
* All of the custom dependent validator extensions.
*
* @var array
* @var array<string, \Closure|string>
*/
protected $dependentExtensions = [];
/**
* All of the custom validator message replacers.
*
* @var array
* @var array<string, \Closure|string>
*/
protected $replacers = [];
/**
* All of the fallback messages for custom rules.
*
* @var array
* @var array<string, string>
*/
protected $fallbackMessages = [];
/**
* Indicates that unvalidated array keys should be excluded, even if the parent array was validated.
*
* @var bool
*/
protected $excludeUnvalidatedArrayKeys = true;
/**
* The Validator resolver instance.
*
* @var Closure
* @var \Closure
*/
protected $resolver;
/**
* Create a new Validator factory instance.
*
* @param \Illuminate\Contracts\Translation\Translator $translator
* @param \Illuminate\Contracts\Container\Container $container
* @param \Illuminate\Contracts\Translation\Translator $translator
* @param \Illuminate\Contracts\Container\Container|null $container
* @return void
*/
public function __construct(Translator $translator, Container $container = null)
@@ -97,13 +104,13 @@ class Factory implements FactoryContract
*/
public function make(array $data, array $rules, array $messages = [], array $customAttributes = [])
{
// The presence verifier is responsible for checking the unique and exists data
// for the validator. It is behind an interface so that multiple versions of
// it may be written besides database. We'll inject it into the validator.
$validator = $this->resolve(
$data, $rules, $messages, $customAttributes
);
// The presence verifier is responsible for checking the unique and exists data
// for the validator. It is behind an interface so that multiple versions of
// it may be written besides database. We'll inject it into the validator.
if (! is_null($this->verifier)) {
$validator->setPresenceVerifier($this->verifier);
}
@@ -115,6 +122,8 @@ class Factory implements FactoryContract
$validator->setContainer($this->container);
}
$validator->excludeUnvalidatedArrayKeys = $this->excludeUnvalidatedArrayKeys;
$this->addExtensions($validator);
return $validator;
@@ -127,13 +136,13 @@ class Factory implements FactoryContract
* @param array $rules
* @param array $messages
* @param array $customAttributes
* @return void
* @return array
*
* @throws \Illuminate\Validation\ValidationException
*/
public function validate(array $data, array $rules, array $messages = [], array $customAttributes = [])
{
$this->make($data, $rules, $messages, $customAttributes)->validate();
return $this->make($data, $rules, $messages, $customAttributes)->validate();
}
/**
@@ -165,8 +174,8 @@ class Factory implements FactoryContract
$validator->addExtensions($this->extensions);
// Next, we will add the implicit extensions, which are similar to the required
// and accepted rule in that they are run even if the attributes is not in a
// array of data that is given to a validator instances via instantiation.
// and accepted rule in that they're run even if the attributes aren't in an
// array of data which is given to a validator instance via instantiation.
$validator->addImplicitExtensions($this->implicitExtensions);
$validator->addDependentExtensions($this->dependentExtensions);
@@ -181,7 +190,7 @@ class Factory implements FactoryContract
*
* @param string $rule
* @param \Closure|string $extension
* @param string $message
* @param string|null $message
* @return void
*/
public function extend($rule, $extension, $message = null)
@@ -196,9 +205,9 @@ class Factory implements FactoryContract
/**
* Register a custom implicit validator extension.
*
* @param string $rule
* @param string $rule
* @param \Closure|string $extension
* @param string $message
* @param string|null $message
* @return void
*/
public function extendImplicit($rule, $extension, $message = null)
@@ -213,9 +222,9 @@ class Factory implements FactoryContract
/**
* Register a custom dependent validator extension.
*
* @param string $rule
* @param string $rule
* @param \Closure|string $extension
* @param string $message
* @param string|null $message
* @return void
*/
public function extendDependent($rule, $extension, $message = null)
@@ -230,7 +239,7 @@ class Factory implements FactoryContract
/**
* Register a custom validator message replacer.
*
* @param string $rule
* @param string $rule
* @param \Closure|string $replacer
* @return void
*/
@@ -239,6 +248,26 @@ class Factory implements FactoryContract
$this->replacers[$rule] = $replacer;
}
/**
* Indicate that unvalidated array keys should be included in validated data when the parent array is validated.
*
* @return void
*/
public function includeUnvalidatedArrayKeys()
{
$this->excludeUnvalidatedArrayKeys = false;
}
/**
* Indicate that unvalidated array keys should be excluded from the validated data, even if the parent array was validated.
*
* @return void
*/
public function excludeUnvalidatedArrayKeys()
{
$this->excludeUnvalidatedArrayKeys = true;
}
/**
* Set the Validator instance resolver.
*
@@ -280,4 +309,27 @@ class Factory implements FactoryContract
{
$this->verifier = $presenceVerifier;
}
/**
* Get the container instance used by the validation factory.
*
* @return \Illuminate\Contracts\Container\Container|null
*/
public function getContainer()
{
return $this->container;
}
/**
* Set the container instance used by the validation factory.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return $this
*/
public function setContainer(Container $container)
{
$this->container = $container;
return $this;
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace Illuminate\Validation;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ImplicitRule;
use Illuminate\Contracts\Validation\InvokableRule;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Translation\PotentiallyTranslatedString;
class InvokableValidationRule implements Rule, ValidatorAwareRule
{
/**
* The invokable that validates the attribute.
*
* @var \Illuminate\Contracts\Validation\InvokableRule
*/
protected $invokable;
/**
* Indicates if the validation invokable failed.
*
* @var bool
*/
protected $failed = false;
/**
* The validation error messages.
*
* @var array
*/
protected $messages = [];
/**
* The current validator.
*
* @var \Illuminate\Validation\Validator
*/
protected $validator;
/**
* The data under validation.
*
* @var array
*/
protected $data = [];
/**
* Create a new explicit Invokable validation rule.
*
* @param \Illuminate\Contracts\Validation\InvokableRule $invokable
* @return void
*/
protected function __construct(InvokableRule $invokable)
{
$this->invokable = $invokable;
}
/**
* Create a new implicit or explicit Invokable validation rule.
*
* @param \Illuminate\Contracts\Validation\InvokableRule $invokable
* @return \Illuminate\Contracts\Validation\Rule
*/
public static function make($invokable)
{
if ($invokable->implicit ?? false) {
return new class($invokable) extends InvokableValidationRule implements ImplicitRule
{
//
};
}
return new InvokableValidationRule($invokable);
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$this->failed = false;
if ($this->invokable instanceof DataAwareRule) {
$this->invokable->setData($this->validator->getData());
}
if ($this->invokable instanceof ValidatorAwareRule) {
$this->invokable->setValidator($this->validator);
}
$this->invokable->__invoke($attribute, $value, function ($attribute, $message = null) {
$this->failed = true;
return $this->pendingPotentiallyTranslatedString($attribute, $message);
});
return ! $this->failed;
}
/**
* Get the underlying invokable rule.
*
* @return \Illuminate\Contracts\Validation\InvokableRule
*/
public function invokable()
{
return $this->invokable;
}
/**
* Get the validation error messages.
*
* @return array
*/
public function message()
{
return $this->messages;
}
/**
* Set the data under validation.
*
* @param array $data
* @return $this
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Set the current validator.
*
* @param \Illuminate\Validation\Validator $validator
* @return $this
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* Create a pending potentially translated string.
*
* @param string $attribute
* @param ?string $message
* @return \Illuminate\Translation\PotentiallyTranslatedString
*/
protected function pendingPotentiallyTranslatedString($attribute, $message)
{
$destructor = $message === null
? fn ($message) => $this->messages[] = $message
: fn ($message) => $this->messages[$attribute] = $message;
return new class($message ?? $attribute, $this->validator->getTranslator(), $destructor) extends PotentiallyTranslatedString
{
/**
* The callback to call when the object destructs.
*
* @var \Closure
*/
protected $destructor;
/**
* Create a new pending potentially translated string.
*
* @param string $message
* @param \Illuminate\Contracts\Translation\Translator $translator
* @param \Closure $destructor
*/
public function __construct($message, $translator, $destructor)
{
parent::__construct($message, $translator);
$this->destructor = $destructor;
}
/**
* Handle the object's destruction.
*
* @return void
*/
public function __destruct()
{
($this->destructor)($this->toString());
}
};
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,55 @@
<?php
namespace Illuminate\Validation;
use Illuminate\Support\Arr;
class NestedRules
{
/**
* The callback to execute.
*
* @var callable
*/
protected $callback;
/**
* Create a new nested rule instance.
*
* @param callable $callback
* @return void
*/
public function __construct(callable $callback)
{
$this->callback = $callback;
}
/**
* Compile the callback into an array of rules.
*
* @param string $attribute
* @param mixed $value
* @param mixed $data
* @return \stdClass
*/
public function compile($attribute, $value, $data = null)
{
$rules = call_user_func($this->callback, $value, $attribute, $data);
$parser = new ValidationRuleParser(
Arr::undot(Arr::wrap($data))
);
if (is_array($rules) && Arr::isAssoc($rules)) {
$nested = [];
foreach ($rules as $key => $rule) {
$nested[$attribute.'.'.$key] = $rule;
}
return $parser->explode($nested);
}
return $parser->explode([$attribute => $rules]);
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Illuminate\Validation;
use Exception;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Support\Str;
class NotPwnedVerifier implements UncompromisedVerifier
{
/**
* The HTTP factory instance.
*
* @var \Illuminate\Http\Client\Factory
*/
protected $factory;
/**
* The number of seconds the request can run before timing out.
*
* @var int
*/
protected $timeout;
/**
* Create a new uncompromised verifier.
*
* @param \Illuminate\Http\Client\Factory $factory
* @param int|null $timeout
* @return void
*/
public function __construct($factory, $timeout = null)
{
$this->factory = $factory;
$this->timeout = $timeout ?? 30;
}
/**
* Verify that the given data has not been compromised in public breaches.
*
* @param array $data
* @return bool
*/
public function verify($data)
{
$value = $data['value'];
$threshold = $data['threshold'];
if (empty($value = (string) $value)) {
return false;
}
[$hash, $hashPrefix] = $this->getHash($value);
return ! $this->search($hashPrefix)
->contains(function ($line) use ($hash, $hashPrefix, $threshold) {
[$hashSuffix, $count] = explode(':', $line);
return $hashPrefix.$hashSuffix == $hash && $count > $threshold;
});
}
/**
* Get the hash and its first 5 chars.
*
* @param string $value
* @return array
*/
protected function getHash($value)
{
$hash = strtoupper(sha1((string) $value));
$hashPrefix = substr($hash, 0, 5);
return [$hash, $hashPrefix];
}
/**
* Search by the given hash prefix and returns all occurrences of leaked passwords.
*
* @param string $hashPrefix
* @return \Illuminate\Support\Collection
*/
protected function search($hashPrefix)
{
try {
$response = $this->factory->withHeaders([
'Add-Padding' => true,
])->timeout($this->timeout)->get(
'https://api.pwnedpasswords.com/range/'.$hashPrefix
);
} catch (Exception $e) {
report($e);
}
$body = (isset($response) && $response->successful())
? $response->body()
: '';
return Str::of($body)->trim()->explode("\n")->filter(function ($line) {
return str_contains($line, ':');
});
}
}

View File

@@ -10,9 +10,9 @@ interface PresenceVerifierInterface
* @param string $collection
* @param string $column
* @param string $value
* @param int $excludeId
* @param string $idColumn
* @param array $extra
* @param int|null $excludeId
* @param string|null $idColumn
* @param array $extra
* @return int
*/
public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = []);
@@ -22,8 +22,8 @@ interface PresenceVerifierInterface
*
* @param string $collection
* @param string $column
* @param array $values
* @param array $extra
* @param array $values
* @param array $extra
* @return int
*/
public function getMultiCount($collection, $column, array $values, array $extra = []);

View File

@@ -2,55 +2,46 @@
namespace Illuminate\Validation;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Validation\Rules\Dimensions;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\ExcludeIf;
use Illuminate\Validation\Rules\Exists;
use Illuminate\Validation\Rules\File;
use Illuminate\Validation\Rules\ImageFile;
use Illuminate\Validation\Rules\In;
use Illuminate\Validation\Rules\NotIn;
use Illuminate\Validation\Rules\ProhibitedIf;
use Illuminate\Validation\Rules\RequiredIf;
use Illuminate\Validation\Rules\Unique;
class Rule
{
use Macroable;
/**
* Get a dimensions constraint builder instance.
* Create a new conditional rule set.
*
* @param array $constraints
* @return \Illuminate\Validation\Rules\Dimensions
* @param callable|bool $condition
* @param array|string|\Closure $rules
* @param array|string|\Closure $defaultRules
* @return \Illuminate\Validation\ConditionalRules
*/
public static function dimensions(array $constraints = [])
public static function when($condition, $rules, $defaultRules = [])
{
return new Rules\Dimensions($constraints);
return new ConditionalRules($condition, $rules, $defaultRules);
}
/**
* Get a exists constraint builder instance.
* Create a new nested rule set.
*
* @param string $table
* @param string $column
* @return \Illuminate\Validation\Rules\Exists
* @param callable $callback
* @return \Illuminate\Validation\NestedRules
*/
public static function exists($table, $column = 'NULL')
public static function forEach($callback)
{
return new Rules\Exists($table, $column);
}
/**
* Get an in constraint builder instance.
*
* @param array|string $values
* @return \Illuminate\Validation\Rules\In
*/
public static function in($values)
{
return new Rules\In(is_array($values) ? $values : func_get_args());
}
/**
* Get a not_in constraint builder instance.
*
* @param array|string $values
* @return \Illuminate\Validation\Rules\NotIn
*/
public static function notIn($values)
{
return new Rules\NotIn(is_array($values) ? $values : func_get_args());
return new NestedRules($callback);
}
/**
@@ -62,6 +53,123 @@ class Rule
*/
public static function unique($table, $column = 'NULL')
{
return new Rules\Unique($table, $column);
return new Unique($table, $column);
}
/**
* Get an exists constraint builder instance.
*
* @param string $table
* @param string $column
* @return \Illuminate\Validation\Rules\Exists
*/
public static function exists($table, $column = 'NULL')
{
return new Exists($table, $column);
}
/**
* Get an in constraint builder instance.
*
* @param \Illuminate\Contracts\Support\Arrayable|array|string $values
* @return \Illuminate\Validation\Rules\In
*/
public static function in($values)
{
if ($values instanceof Arrayable) {
$values = $values->toArray();
}
return new In(is_array($values) ? $values : func_get_args());
}
/**
* Get a not_in constraint builder instance.
*
* @param \Illuminate\Contracts\Support\Arrayable|array|string $values
* @return \Illuminate\Validation\Rules\NotIn
*/
public static function notIn($values)
{
if ($values instanceof Arrayable) {
$values = $values->toArray();
}
return new NotIn(is_array($values) ? $values : func_get_args());
}
/**
* Get a required_if constraint builder instance.
*
* @param callable|bool $callback
* @return \Illuminate\Validation\Rules\RequiredIf
*/
public static function requiredIf($callback)
{
return new RequiredIf($callback);
}
/**
* Get a exclude_if constraint builder instance.
*
* @param callable|bool $callback
* @return \Illuminate\Validation\Rules\ExcludeIf
*/
public static function excludeIf($callback)
{
return new ExcludeIf($callback);
}
/**
* Get a prohibited_if constraint builder instance.
*
* @param callable|bool $callback
* @return \Illuminate\Validation\Rules\ProhibitedIf
*/
public static function prohibitedIf($callback)
{
return new ProhibitedIf($callback);
}
/**
* Get an enum constraint builder instance.
*
* @param string $type
* @return \Illuminate\Validation\Rules\Enum
*/
public static function enum($type)
{
return new Enum($type);
}
/**
* Get a file constraint builder instance.
*
* @return \Illuminate\Validation\Rules\File
*/
public static function file()
{
return new File;
}
/**
* Get an image file constraint builder instance.
*
* @return \Illuminate\Validation\Rules\ImageFile
*/
public static function imageFile()
{
return new ImageFile;
}
/**
* Get a dimensions constraint builder instance.
*
* @param array $constraints
* @return \Illuminate\Validation\Rules\Dimensions
*/
public static function dimensions(array $constraints = [])
{
return new Dimensions($constraints);
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace Illuminate\Validation\Rules;
use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Model;
trait DatabaseRule
{
/**
* The table to run the query against.
*
* @var string
*/
protected $table;
/**
* The column to check on.
*
* @var string
*/
protected $column;
/**
* The extra where clauses for the query.
*
* @var array
*/
protected $wheres = [];
/**
* The array of custom query callbacks.
*
* @var array
*/
protected $using = [];
/**
* Create a new rule instance.
*
* @param string $table
* @param string $column
* @return void
*/
public function __construct($table, $column = 'NULL')
{
$this->column = $column;
$this->table = $this->resolveTableName($table);
}
/**
* Resolves the name of the table from the given string.
*
* @param string $table
* @return string
*/
public function resolveTableName($table)
{
if (! str_contains($table, '\\') || ! class_exists($table)) {
return $table;
}
if (is_subclass_of($table, Model::class)) {
$model = new $table;
if (str_contains($model->getTable(), '.')) {
return $table;
}
return implode('.', array_map(function (string $part) {
return trim($part, '.');
}, array_filter([$model->getConnectionName(), $model->getTable()])));
}
return $table;
}
/**
* Set a "where" constraint on the query.
*
* @param \Closure|string $column
* @param \Illuminate\Contracts\Support\Arrayable|array|string|int|null $value
* @return $this
*/
public function where($column, $value = null)
{
if ($value instanceof Arrayable || is_array($value)) {
return $this->whereIn($column, $value);
}
if ($column instanceof Closure) {
return $this->using($column);
}
if (is_null($value)) {
return $this->whereNull($column);
}
$this->wheres[] = compact('column', 'value');
return $this;
}
/**
* Set a "where not" constraint on the query.
*
* @param string $column
* @param \Illuminate\Contracts\Support\Arrayable|array|string $value
* @return $this
*/
public function whereNot($column, $value)
{
if ($value instanceof Arrayable || is_array($value)) {
return $this->whereNotIn($column, $value);
}
return $this->where($column, '!'.$value);
}
/**
* Set a "where null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNull($column)
{
return $this->where($column, 'NULL');
}
/**
* Set a "where not null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNotNull($column)
{
return $this->where($column, 'NOT_NULL');
}
/**
* Set a "where in" constraint on the query.
*
* @param string $column
* @param \Illuminate\Contracts\Support\Arrayable|array $values
* @return $this
*/
public function whereIn($column, $values)
{
return $this->where(function ($query) use ($column, $values) {
$query->whereIn($column, $values);
});
}
/**
* Set a "where not in" constraint on the query.
*
* @param string $column
* @param \Illuminate\Contracts\Support\Arrayable|array $values
* @return $this
*/
public function whereNotIn($column, $values)
{
return $this->where(function ($query) use ($column, $values) {
$query->whereNotIn($column, $values);
});
}
/**
* Ignore soft deleted models during the existence check.
*
* @param string $deletedAtColumn
* @return $this
*/
public function withoutTrashed($deletedAtColumn = 'deleted_at')
{
$this->whereNull($deletedAtColumn);
return $this;
}
/**
* Only include soft deleted models during the existence check.
*
* @param string $deletedAtColumn
* @return $this
*/
public function onlyTrashed($deletedAtColumn = 'deleted_at')
{
$this->whereNotNull($deletedAtColumn);
return $this;
}
/**
* Register a custom query callback.
*
* @param \Closure $callback
* @return $this
*/
public function using(Closure $callback)
{
$this->using[] = $callback;
return $this;
}
/**
* Get the custom query callbacks for the rule.
*
* @return array
*/
public function queryCallbacks()
{
return $this->using;
}
/**
* Format the where clauses.
*
* @return string
*/
protected function formatWheres()
{
return collect($this->wheres)->map(function ($where) {
return $where['column'].','.'"'.str_replace('"', '""', $where['value']).'"';
})->implode(',');
}
}

View File

@@ -2,8 +2,12 @@
namespace Illuminate\Validation\Rules;
use Illuminate\Support\Traits\Conditionable;
class Dimensions
{
use Conditionable;
/**
* The constraints for the dimensions rule.
*
@@ -14,7 +18,7 @@ class Dimensions
/**
* Create a new dimensions rule instance.
*
* @param array $constraints;
* @param array $constraints
* @return void
*/
public function __construct(array $constraints = [])

View File

@@ -0,0 +1,65 @@
<?php
namespace Illuminate\Validation\Rules;
use Illuminate\Contracts\Validation\Rule;
use TypeError;
class Enum implements Rule
{
/**
* The type of the enum.
*
* @var string
*/
protected $type;
/**
* Create a new rule instance.
*
* @param string $type
* @return void
*/
public function __construct($type)
{
$this->type = $type;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
if ($value instanceof $this->type) {
return true;
}
if (is_null($value) || ! function_exists('enum_exists') || ! enum_exists($this->type) || ! method_exists($this->type, 'tryFrom')) {
return false;
}
try {
return ! is_null($this->type::tryFrom($value));
} catch (TypeError $e) {
return false;
}
}
/**
* Get the validation error message.
*
* @return array
*/
public function message()
{
$message = trans('validation.enum');
return $message === 'validation.enum'
? ['The selected :attribute is invalid.']
: $message;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Illuminate\Validation\Rules;
use Closure;
use InvalidArgumentException;
class ExcludeIf
{
/**
* The condition that validates the attribute.
*
* @var \Closure|bool
*/
public $condition;
/**
* Create a new exclude validation rule based on a condition.
*
* @param \Closure|bool $condition
* @return void
*
* @throws \InvalidArgumentException
*/
public function __construct($condition)
{
if ($condition instanceof Closure || is_bool($condition)) {
$this->condition = $condition;
} else {
throw new InvalidArgumentException('The provided condition must be a callable or boolean.');
}
}
/**
* Convert the rule to a validation string.
*
* @return string
*/
public function __toString()
{
if (is_callable($this->condition)) {
return call_user_func($this->condition) ? 'exclude' : '';
}
return $this->condition ? 'exclude' : '';
}
}

View File

@@ -2,137 +2,11 @@
namespace Illuminate\Validation\Rules;
use Closure;
use Illuminate\Support\Traits\Conditionable;
class Exists
{
/**
* The table to run the query against.
*
* @var string
*/
protected $table;
/**
* The column to check for existence on.
*
* @var string
*/
protected $column;
/**
* There extra where clauses for the query.
*
* @var array
*/
protected $wheres = [];
/**
* The custom query callback.
*
* @var \Closure|null
*/
protected $using;
/**
* Create a new exists rule instance.
*
* @param string $table
* @param string $column
* @return void
*/
public function __construct($table, $column = 'NULL')
{
$this->table = $table;
$this->column = $column;
}
/**
* Set a "where" constraint on the query.
*
* @param string $column
* @param string $value
* @return $this
*/
public function where($column, $value = null)
{
if ($column instanceof Closure) {
return $this->using($column);
}
$this->wheres[] = compact('column', 'value');
return $this;
}
/**
* Set a "where not" constraint on the query.
*
* @param string $column
* @param string $value
* @return $this
*/
public function whereNot($column, $value)
{
return $this->where($column, '!'.$value);
}
/**
* Set a "where null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNull($column)
{
return $this->where($column, 'NULL');
}
/**
* Set a "where not null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNotNull($column)
{
return $this->where($column, 'NOT_NULL');
}
/**
* Register a custom query callback.
*
* @param \Closure $callback
* @return $this
*/
public function using(Closure $callback)
{
$this->using = $callback;
return $this;
}
/**
* Format the where clauses.
*
* @return string
*/
protected function formatWheres()
{
return collect($this->wheres)->map(function ($where) {
return $where['column'].','.$where['value'];
})->implode(',');
}
/**
* Get the custom query callbacks for the rule.
*
* @return array
*/
public function queryCallbacks()
{
return $this->using ? [$this->using] : [];
}
use Conditionable, DatabaseRule;
/**
* Convert the rule to a validation string.

View File

@@ -0,0 +1,329 @@
<?php
namespace Illuminate\Validation\Rules;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
class File implements Rule, DataAwareRule, ValidatorAwareRule
{
use Conditionable;
use Macroable;
/**
* The MIME types that the given file should match. This array may also contain file extensions.
*
* @var array
*/
protected $allowedMimetypes = [];
/**
* The minimum size in kilobytes that the file can be.
*
* @var null|int
*/
protected $minimumFileSize = null;
/**
* The maximum size in kilobytes that the file can be.
*
* @var null|int
*/
protected $maximumFileSize = null;
/**
* An array of custom rules that will be merged into the validation rules.
*
* @var array
*/
protected $customRules = [];
/**
* The error message after validation, if any.
*
* @var array
*/
protected $messages = [];
/**
* The data under validation.
*
* @var array
*/
protected $data;
/**
* The validator performing the validation.
*
* @var \Illuminate\Validation\Validator
*/
protected $validator;
/**
* The callback that will generate the "default" version of the file rule.
*
* @var string|array|callable|null
*/
public static $defaultCallback;
/**
* Set the default callback to be used for determining the file default rules.
*
* If no arguments are passed, the default file rule configuration will be returned.
*
* @param static|callable|null $callback
* @return static|null
*/
public static function defaults($callback = null)
{
if (is_null($callback)) {
return static::default();
}
if (! is_callable($callback) && ! $callback instanceof static) {
throw new InvalidArgumentException('The given callback should be callable or an instance of '.static::class);
}
static::$defaultCallback = $callback;
}
/**
* Get the default configuration of the file rule.
*
* @return static
*/
public static function default()
{
$file = is_callable(static::$defaultCallback)
? call_user_func(static::$defaultCallback)
: static::$defaultCallback;
return $file instanceof Rule ? $file : new self();
}
/**
* Limit the uploaded file to only image types.
*
* @return ImageFile
*/
public static function image()
{
return new ImageFile();
}
/**
* Limit the uploaded file to the given MIME types or file extensions.
*
* @param string|array<int, string> $mimetypes
* @return static
*/
public static function types($mimetypes)
{
return tap(new static(), fn ($file) => $file->allowedMimetypes = (array) $mimetypes);
}
/**
* Indicate that the uploaded file should be exactly a certain size in kilobytes.
*
* @param int $kilobytes
* @return $this
*/
public function size($kilobytes)
{
$this->minimumFileSize = $kilobytes;
$this->maximumFileSize = $kilobytes;
return $this;
}
/**
* Indicate that the uploaded file should be between a minimum and maximum size in kilobytes.
*
* @param int $minKilobytes
* @param int $maxKilobytes
* @return $this
*/
public function between($minKilobytes, $maxKilobytes)
{
$this->minimumFileSize = $minKilobytes;
$this->maximumFileSize = $maxKilobytes;
return $this;
}
/**
* Indicate that the uploaded file should be no less than the given number of kilobytes.
*
* @param int $kilobytes
* @return $this
*/
public function min($kilobytes)
{
$this->minimumFileSize = $kilobytes;
return $this;
}
/**
* Indicate that the uploaded file should be no more than the given number of kilobytes.
*
* @param int $kilobytes
* @return $this
*/
public function max($kilobytes)
{
$this->maximumFileSize = $kilobytes;
return $this;
}
/**
* Specify additional validation rules that should be merged with the default rules during validation.
*
* @param string|array $rules
* @return $this
*/
public function rules($rules)
{
$this->customRules = array_merge($this->customRules, Arr::wrap($rules));
return $this;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$this->messages = [];
$validator = Validator::make(
$this->data,
[$attribute => $this->buildValidationRules()],
$this->validator->customMessages,
$this->validator->customAttributes
);
if ($validator->fails()) {
return $this->fail($validator->messages()->all());
}
return true;
}
/**
* Build the array of underlying validation rules based on the current state.
*
* @return array
*/
protected function buildValidationRules()
{
$rules = ['file'];
$rules = array_merge($rules, $this->buildMimetypes());
$rules[] = match (true) {
is_null($this->minimumFileSize) && is_null($this->maximumFileSize) => null,
is_null($this->maximumFileSize) => "min:{$this->minimumFileSize}",
is_null($this->minimumFileSize) => "max:{$this->maximumFileSize}",
$this->minimumFileSize !== $this->maximumFileSize => "between:{$this->minimumFileSize},{$this->maximumFileSize}",
default => "size:{$this->minimumFileSize}",
};
return array_merge(array_filter($rules), $this->customRules);
}
/**
* Separate the given mimetypes from extensions and return an array of correct rules to validate against.
*
* @return array
*/
protected function buildMimetypes()
{
if (count($this->allowedMimetypes) === 0) {
return [];
}
$rules = [];
$mimetypes = array_filter(
$this->allowedMimetypes,
fn ($type) => str_contains($type, '/')
);
$mimes = array_diff($this->allowedMimetypes, $mimetypes);
if (count($mimetypes) > 0) {
$rules[] = 'mimetypes:'.implode(',', $mimetypes);
}
if (count($mimes) > 0) {
$rules[] = 'mimes:'.implode(',', $mimes);
}
return $rules;
}
/**
* Adds the given failures, and return false.
*
* @param array|string $messages
* @return bool
*/
protected function fail($messages)
{
$messages = collect(Arr::wrap($messages))->map(function ($message) {
return $this->validator->getTranslator()->get($message);
})->all();
$this->messages = array_merge($this->messages, $messages);
return false;
}
/**
* Get the validation error message.
*
* @return array
*/
public function message()
{
return $this->messages;
}
/**
* Set the current validator.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return $this
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* Set the current data under validation.
*
* @param array $data
* @return $this
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Illuminate\Validation\Rules;
class ImageFile extends File
{
/**
* Create a new image file rule instance.
*
* @return void
*/
public function __construct()
{
$this->rules('image');
}
/**
* The dimension constraints for the uploaded file.
*
* @param \Illuminate\Validation\Rules\Dimensions $dimensions
*/
public function dimensions($dimensions)
{
$this->rules($dimensions);
return $this;
}
}

View File

@@ -6,6 +6,8 @@ class In
{
/**
* The name of the rule.
*
* @var string
*/
protected $rule = 'in';
@@ -31,9 +33,15 @@ class In
* Convert the rule to a validation string.
*
* @return string
*
* @see \Illuminate\Validation\ValidationRuleParser::parseParameters
*/
public function __toString()
{
return $this->rule.':'.implode(',', $this->values);
$values = array_map(function ($value) {
return '"'.str_replace('"', '""', $value).'"';
}, $this->values);
return $this->rule.':'.implode(',', $values);
}
}

View File

@@ -6,6 +6,8 @@ class NotIn
{
/**
* The name of the rule.
*
* @var string
*/
protected $rule = 'not_in';
@@ -34,6 +36,10 @@ class NotIn
*/
public function __toString()
{
return $this->rule.':'.implode(',', $this->values);
$values = array_map(function ($value) {
return '"'.str_replace('"', '""', $value).'"';
}, $this->values);
return $this->rule.':'.implode(',', $values);
}
}

View File

@@ -0,0 +1,395 @@
<?php
namespace Illuminate\Validation\Rules;
use Illuminate\Container\Container;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Traits\Conditionable;
use InvalidArgumentException;
class Password implements Rule, DataAwareRule, ValidatorAwareRule
{
use Conditionable;
/**
* The validator performing the validation.
*
* @var \Illuminate\Contracts\Validation\Validator
*/
protected $validator;
/**
* The data under validation.
*
* @var array
*/
protected $data;
/**
* The minimum size of the password.
*
* @var int
*/
protected $min = 8;
/**
* If the password requires at least one uppercase and one lowercase letter.
*
* @var bool
*/
protected $mixedCase = false;
/**
* If the password requires at least one letter.
*
* @var bool
*/
protected $letters = false;
/**
* If the password requires at least one number.
*
* @var bool
*/
protected $numbers = false;
/**
* If the password requires at least one symbol.
*
* @var bool
*/
protected $symbols = false;
/**
* If the password should not have been compromised in data leaks.
*
* @var bool
*/
protected $uncompromised = false;
/**
* The number of times a password can appear in data leaks before being considered compromised.
*
* @var int
*/
protected $compromisedThreshold = 0;
/**
* Additional validation rules that should be merged into the default rules during validation.
*
* @var array
*/
protected $customRules = [];
/**
* The failure messages, if any.
*
* @var array
*/
protected $messages = [];
/**
* The callback that will generate the "default" version of the password rule.
*
* @var string|array|callable|null
*/
public static $defaultCallback;
/**
* Create a new rule instance.
*
* @param int $min
* @return void
*/
public function __construct($min)
{
$this->min = max((int) $min, 1);
}
/**
* Set the default callback to be used for determining a password's default rules.
*
* If no arguments are passed, the default password rule configuration will be returned.
*
* @param static|callable|null $callback
* @return static|null
*/
public static function defaults($callback = null)
{
if (is_null($callback)) {
return static::default();
}
if (! is_callable($callback) && ! $callback instanceof static) {
throw new InvalidArgumentException('The given callback should be callable or an instance of '.static::class);
}
static::$defaultCallback = $callback;
}
/**
* Get the default configuration of the password rule.
*
* @return static
*/
public static function default()
{
$password = is_callable(static::$defaultCallback)
? call_user_func(static::$defaultCallback)
: static::$defaultCallback;
return $password instanceof Rule ? $password : static::min(8);
}
/**
* Get the default configuration of the password rule and mark the field as required.
*
* @return array
*/
public static function required()
{
return ['required', static::default()];
}
/**
* Get the default configuration of the password rule and mark the field as sometimes being required.
*
* @return array
*/
public static function sometimes()
{
return ['sometimes', static::default()];
}
/**
* Set the performing validator.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return $this
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* Set the data under validation.
*
* @param array $data
* @return $this
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Sets the minimum size of the password.
*
* @param int $size
* @return $this
*/
public static function min($size)
{
return new static($size);
}
/**
* Ensures the password has not been compromised in data leaks.
*
* @param int $threshold
* @return $this
*/
public function uncompromised($threshold = 0)
{
$this->uncompromised = true;
$this->compromisedThreshold = $threshold;
return $this;
}
/**
* Makes the password require at least one uppercase and one lowercase letter.
*
* @return $this
*/
public function mixedCase()
{
$this->mixedCase = true;
return $this;
}
/**
* Makes the password require at least one letter.
*
* @return $this
*/
public function letters()
{
$this->letters = true;
return $this;
}
/**
* Makes the password require at least one number.
*
* @return $this
*/
public function numbers()
{
$this->numbers = true;
return $this;
}
/**
* Makes the password require at least one symbol.
*
* @return $this
*/
public function symbols()
{
$this->symbols = true;
return $this;
}
/**
* Specify additional validation rules that should be merged with the default rules during validation.
*
* @param string|array $rules
* @return $this
*/
public function rules($rules)
{
$this->customRules = Arr::wrap($rules);
return $this;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$this->messages = [];
$validator = Validator::make(
$this->data,
[$attribute => array_merge(['string', 'min:'.$this->min], $this->customRules)],
$this->validator->customMessages,
$this->validator->customAttributes
)->after(function ($validator) use ($attribute, $value) {
if (! is_string($value)) {
return;
}
if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) {
$validator->errors()->add(
$attribute,
$this->getErrorMessage('validation.password.mixed')
);
}
if ($this->letters && ! preg_match('/\pL/u', $value)) {
$validator->errors()->add(
$attribute,
$this->getErrorMessage('validation.password.letters')
);
}
if ($this->symbols && ! preg_match('/\p{Z}|\p{S}|\p{P}/u', $value)) {
$validator->errors()->add(
$attribute,
$this->getErrorMessage('validation.password.symbols')
);
}
if ($this->numbers && ! preg_match('/\pN/u', $value)) {
$validator->errors()->add(
$attribute,
$this->getErrorMessage('validation.password.numbers')
);
}
});
if ($validator->fails()) {
return $this->fail($validator->messages()->all());
}
if ($this->uncompromised && ! Container::getInstance()->make(UncompromisedVerifier::class)->verify([
'value' => $value,
'threshold' => $this->compromisedThreshold,
])) {
return $this->fail($this->getErrorMessage('validation.password.uncompromised'));
}
return true;
}
/**
* Get the validation error message.
*
* @return array
*/
public function message()
{
return $this->messages;
}
/**
* Get the translated password error message.
*
* @param string $key
* @return string
*/
protected function getErrorMessage($key)
{
if (($message = $this->validator->getTranslator()->get($key)) !== $key) {
return $message;
}
$messages = [
'validation.password.mixed' => 'The :attribute must contain at least one uppercase and one lowercase letter.',
'validation.password.letters' => 'The :attribute must contain at least one letter.',
'validation.password.symbols' => 'The :attribute must contain at least one symbol.',
'validation.password.numbers' => 'The :attribute must contain at least one number.',
'validation.password.uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
];
return $messages[$key];
}
/**
* Adds the given failures, and return false.
*
* @param array|string $messages
* @return bool
*/
protected function fail($messages)
{
$messages = collect(Arr::wrap($messages))->map(function ($message) {
return $this->validator->getTranslator()->get($message);
})->all();
$this->messages = array_merge($this->messages, $messages);
return false;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Illuminate\Validation\Rules;
use Closure;
use InvalidArgumentException;
class ProhibitedIf
{
/**
* The condition that validates the attribute.
*
* @var \Closure|bool
*/
public $condition;
/**
* Create a new prohibited validation rule based on a condition.
*
* @param \Closure|bool $condition
* @return void
*
* @throws \InvalidArgumentException
*/
public function __construct($condition)
{
if ($condition instanceof Closure || is_bool($condition)) {
$this->condition = $condition;
} else {
throw new InvalidArgumentException('The provided condition must be a callable or boolean.');
}
}
/**
* Convert the rule to a validation string.
*
* @return string
*/
public function __toString()
{
if (is_callable($this->condition)) {
return call_user_func($this->condition) ? 'prohibited' : '';
}
return $this->condition ? 'prohibited' : '';
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Illuminate\Validation\Rules;
use InvalidArgumentException;
class RequiredIf
{
/**
* The condition that validates the attribute.
*
* @var callable|bool
*/
public $condition;
/**
* Create a new required validation rule based on a condition.
*
* @param callable|bool $condition
* @return void
*/
public function __construct($condition)
{
if (! is_string($condition)) {
$this->condition = $condition;
} else {
throw new InvalidArgumentException('The provided condition must be a callable or boolean.');
}
}
/**
* Convert the rule to a validation string.
*
* @return string
*/
public function __toString()
{
if (is_callable($this->condition)) {
return call_user_func($this->condition) ? 'required' : '';
}
return $this->condition ? 'required' : '';
}
}

View File

@@ -2,23 +2,12 @@
namespace Illuminate\Validation\Rules;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Traits\Conditionable;
class Unique
{
/**
* The table to run the query against.
*
* @var string
*/
protected $table;
/**
* The column to check for uniqueness on.
*
* @var string
*/
protected $column;
use Conditionable, DatabaseRule;
/**
* The ID that should be ignored.
@@ -34,135 +23,40 @@ class Unique
*/
protected $idColumn = 'id';
/**
* There extra where clauses for the query.
*
* @var array
*/
protected $wheres = [];
/**
* The custom query callback.
*
* @var \Closure|null
*/
protected $using;
/**
* Create a new unique rule instance.
*
* @param string $table
* @param string $column
* @return void
*/
public function __construct($table, $column = 'NULL')
{
$this->table = $table;
$this->column = $column;
}
/**
* Set a "where" constraint on the query.
*
* @param string $column
* @param string $value
* @return $this
*/
public function where($column, $value = null)
{
if ($column instanceof Closure) {
return $this->using($column);
}
$this->wheres[] = compact('column', 'value');
return $this;
}
/**
* Set a "where not" constraint on the query.
*
* @param string $column
* @param string $value
* @return $this
*/
public function whereNot($column, $value)
{
return $this->where($column, '!'.$value);
}
/**
* Set a "where null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNull($column)
{
return $this->where($column, 'NULL');
}
/**
* Set a "where not null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNotNull($column)
{
return $this->where($column, 'NOT_NULL');
}
/**
* Ignore the given ID during the unique check.
*
* @param mixed $id
* @param string $idColumn
* @param string|null $idColumn
* @return $this
*/
public function ignore($id, $idColumn = 'id')
public function ignore($id, $idColumn = null)
{
if ($id instanceof Model) {
return $this->ignoreModel($id, $idColumn);
}
$this->ignore = $id;
$this->idColumn = $idColumn;
$this->idColumn = $idColumn ?? 'id';
return $this;
}
/**
* Register a custom query callback.
* Ignore the given model during the unique check.
*
* @param \Closure $callback
* @param \Illuminate\Database\Eloquent\Model $model
* @param string|null $idColumn
* @return $this
*/
public function using(Closure $callback)
public function ignoreModel($model, $idColumn = null)
{
$this->using = $callback;
$this->idColumn = $idColumn ?? $model->getKeyName();
$this->ignore = $model->{$this->idColumn};
return $this;
}
/**
* Format the where clauses.
*
* @return string
*/
protected function formatWheres()
{
return collect($this->wheres)->map(function ($where) {
return $where['column'].','.$where['value'];
})->implode(',');
}
/**
* Get the custom query callbacks for the rule.
*
* @return array
*/
public function queryCallbacks()
{
return $this->using ? [$this->using] : [];
}
/**
* Convert the rule to a validation string.
*
@@ -173,7 +67,7 @@ class Unique
return rtrim(sprintf('unique:%s,%s,%s,%s,%s',
$this->table,
$this->column,
$this->ignore ? '"'.$this->ignore.'"' : 'NULL',
$this->ignore ? '"'.addslashes($this->ignore).'"' : 'NULL',
$this->idColumn,
$this->formatWheres()
), ',');

View File

@@ -2,6 +2,8 @@
namespace Illuminate\Validation;
use Illuminate\Foundation\Precognition;
/**
* Provides default implementation of ValidatesWhenResolved contract.
*/
@@ -12,17 +14,25 @@ trait ValidatesWhenResolvedTrait
*
* @return void
*/
public function validate()
public function validateResolved()
{
$this->prepareForValidation();
$instance = $this->getValidatorInstance();
if (! $this->passesAuthorization()) {
$this->failedAuthorization();
} elseif (! $instance->passes()) {
}
$instance = $this->getValidatorInstance();
if ($this->isPrecognitive()) {
$instance->after(Precognition::afterValidationHook($this));
}
if ($instance->fails()) {
$this->failedValidation($instance);
}
$this->passedValidation();
}
/**
@@ -32,7 +42,7 @@ trait ValidatesWhenResolvedTrait
*/
protected function prepareForValidation()
{
// no default action
//
}
/**
@@ -45,6 +55,16 @@ trait ValidatesWhenResolvedTrait
return $this->validator();
}
/**
* Handle a passed validation attempt.
*
* @return void
*/
protected function passedValidation()
{
//
}
/**
* Handle a failed validation attempt.
*

View File

@@ -3,10 +3,16 @@
namespace Illuminate\Validation;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class ValidationData
{
/**
* Initialize and gather data for the given attribute.
*
* @param string $attribute
* @param array $masterData
* @return array
*/
public static function initializeAndGatherData($attribute, $masterData)
{
$data = Arr::dot(static::initializeAttributeOnData($attribute, $masterData));
@@ -29,7 +35,7 @@ class ValidationData
$data = static::extractDataFromPath($explicitPath, $masterData);
if (! Str::contains($attribute, '*') || Str::endsWith($attribute, '*')) {
if (! str_contains($attribute, '*') || str_ends_with($attribute, '*')) {
return $data;
}
@@ -61,7 +67,7 @@ class ValidationData
$data = [];
foreach ($keys as $key) {
$data[$key] = array_get($masterData, $key);
$data[$key] = Arr::get($masterData, $key);
}
return $data;
@@ -82,7 +88,7 @@ class ValidationData
$value = Arr::get($masterData, $attribute, '__missing__');
if ($value != '__missing__') {
if ($value !== '__missing__') {
Arr::set($results, $attribute, $value);
}

View File

@@ -3,6 +3,8 @@
namespace Illuminate\Validation;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
class ValidationException extends Exception
{
@@ -20,21 +22,135 @@ class ValidationException extends Exception
*/
public $response;
/**
* The status code to use for the response.
*
* @var int
*/
public $status = 422;
/**
* The name of the error bag.
*
* @var string
*/
public $errorBag;
/**
* The path the client should be redirected to.
*
* @var string
*/
public $redirectTo;
/**
* Create a new exception instance.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @param \Symfony\Component\HttpFoundation\Response $response
* @param \Symfony\Component\HttpFoundation\Response|null $response
* @param string $errorBag
* @return void
*/
public function __construct($validator, $response = null)
public function __construct($validator, $response = null, $errorBag = 'default')
{
parent::__construct('The given data failed to pass validation.');
parent::__construct(static::summarize($validator));
$this->response = $response;
$this->errorBag = $errorBag;
$this->validator = $validator;
}
/**
* Create a new validation exception from a plain array of messages.
*
* @param array $messages
* @return static
*/
public static function withMessages(array $messages)
{
return new static(tap(ValidatorFacade::make([], []), function ($validator) use ($messages) {
foreach ($messages as $key => $value) {
foreach (Arr::wrap($value) as $message) {
$validator->errors()->add($key, $message);
}
}
}));
}
/**
* Create an error message summary from the validation errors.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return string
*/
protected static function summarize($validator)
{
$messages = $validator->errors()->all();
if (! count($messages) || ! is_string($messages[0])) {
return $validator->getTranslator()->get('The given data was invalid.');
}
$message = array_shift($messages);
if ($count = count($messages)) {
$pluralized = $count === 1 ? 'error' : 'errors';
$message .= ' '.$validator->getTranslator()->get("(and :count more $pluralized)", compact('count'));
}
return $message;
}
/**
* Get all of the validation error messages.
*
* @return array
*/
public function errors()
{
return $this->validator->errors()->messages();
}
/**
* Set the HTTP status code to be used for the response.
*
* @param int $status
* @return $this
*/
public function status($status)
{
$this->status = $status;
return $this;
}
/**
* Set the error bag on the exception.
*
* @param string $errorBag
* @return $this
*/
public function errorBag($errorBag)
{
$this->errorBag = $errorBag;
return $this;
}
/**
* Set the URL to redirect to on a validation error.
*
* @param string $url
* @return $this
*/
public function redirectTo($url)
{
$this->redirectTo = $url;
return $this;
}
/**
* Get the underlying response instance.
*

View File

@@ -2,6 +2,9 @@
namespace Illuminate\Validation;
use Closure;
use Illuminate\Contracts\Validation\InvokableRule;
use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Exists;
@@ -61,12 +64,12 @@ class ValidationRuleParser
protected function explodeRules($rules)
{
foreach ($rules as $key => $rule) {
if (Str::contains($key, '*')) {
if (str_contains($key, '*')) {
$rules = $this->explodeWildcardRules($rules, $key, [$rule]);
unset($rules[$key]);
} else {
$rules[$key] = $this->explodeExplicitRule($rule);
$rules[$key] = $this->explodeExplicitRule($rule, $key);
}
}
@@ -77,34 +80,59 @@ class ValidationRuleParser
* Explode the explicit rule into an array if necessary.
*
* @param mixed $rule
* @param string $attribute
* @return array
*/
protected function explodeExplicitRule($rule)
protected function explodeExplicitRule($rule, $attribute)
{
if (is_string($rule)) {
return explode('|', $rule);
} elseif (is_object($rule)) {
return [$this->prepareRule($rule)];
} else {
return array_map([$this, 'prepareRule'], $rule);
[$name] = static::parseStringRule($rule);
return static::ruleIsRegex($name) ? [$rule] : explode('|', $rule);
}
if (is_object($rule)) {
return Arr::wrap($this->prepareRule($rule, $attribute));
}
return array_map(
[$this, 'prepareRule'],
$rule,
array_fill((int) array_key_first($rule), count($rule), $attribute)
);
}
/**
* Prepare the given rule for the Validator.
*
* @param mixed $rule
* @param string $attribute
* @return mixed
*/
protected function prepareRule($rule)
protected function prepareRule($rule, $attribute)
{
if ($rule instanceof Closure) {
$rule = new ClosureValidationRule($rule);
}
if ($rule instanceof InvokableRule) {
$rule = InvokableValidationRule::make($rule);
}
if (! is_object($rule) ||
$rule instanceof RuleContract ||
($rule instanceof Exists && $rule->queryCallbacks()) ||
($rule instanceof Unique && $rule->queryCallbacks())) {
return $rule;
}
return strval($rule);
if ($rule instanceof NestedRules) {
return $rule->compile(
$attribute, $this->data[$attribute] ?? null, Arr::dot($this->data)
)->rules[$attribute];
}
return (string) $rule;
}
/**
@@ -124,9 +152,21 @@ class ValidationRuleParser
foreach ($data as $key => $value) {
if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) {
foreach ((array) $rules as $rule) {
$this->implicitAttributes[$attribute][] = $key;
if ($rule instanceof NestedRules) {
$compiled = $rule->compile($key, $value, $data);
$results = $this->mergeRules($results, $key, $rule);
$this->implicitAttributes = array_merge_recursive(
$compiled->implicitAttributes,
$this->implicitAttributes,
[$attribute => [$key]]
);
$results = $this->mergeRules($results, $compiled->rules);
} else {
$this->implicitAttributes[$attribute][] = $key;
$results = $this->mergeRules($results, $key, $rule);
}
}
}
}
@@ -170,7 +210,7 @@ class ValidationRuleParser
$merge = head($this->explodeRules([$rules]));
$results[$attribute] = array_merge(
isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute]) : [], $merge
isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute], $attribute) : [], $merge
);
return $results;
@@ -179,53 +219,57 @@ class ValidationRuleParser
/**
* Extract the rule name and parameters from a rule.
*
* @param array|string $rules
* @param array|string $rule
* @return array
*/
public static function parse($rules)
public static function parse($rule)
{
if (is_array($rules)) {
$rules = static::parseArrayRule($rules);
} else {
$rules = static::parseStringRule($rules);
if ($rule instanceof RuleContract || $rule instanceof NestedRules) {
return [$rule, []];
}
$rules[0] = static::normalizeRule($rules[0]);
if (is_array($rule)) {
$rule = static::parseArrayRule($rule);
} else {
$rule = static::parseStringRule($rule);
}
return $rules;
$rule[0] = static::normalizeRule($rule[0]);
return $rule;
}
/**
* Parse an array based rule.
*
* @param array $rules
* @param array $rule
* @return array
*/
protected static function parseArrayRule(array $rules)
protected static function parseArrayRule(array $rule)
{
return [Str::studly(trim(Arr::get($rules, 0))), array_slice($rules, 1)];
return [Str::studly(trim(Arr::get($rule, 0, ''))), array_slice($rule, 1)];
}
/**
* Parse a string based rule.
*
* @param string $rules
* @param string $rule
* @return array
*/
protected static function parseStringRule($rules)
protected static function parseStringRule($rule)
{
$parameters = [];
// The format for specifying validation rules and parameters follows an
// easy {rule}:{parameters} formatting convention. For instance the
// rule "Max:3" states that the value may only be three letters.
if (strpos($rules, ':') !== false) {
list($rules, $parameter) = explode(':', $rules, 2);
if (str_contains($rule, ':')) {
[$rule, $parameter] = explode(':', $rule, 2);
$parameters = static::parseParameters($rules, $parameter);
$parameters = static::parseParameters($rule, $parameter);
}
return [Str::studly(trim($rules)), $parameters];
return [Str::studly(trim($rule)), $parameters];
}
/**
@@ -237,11 +281,18 @@ class ValidationRuleParser
*/
protected static function parseParameters($rule, $parameter)
{
if (strtolower($rule) == 'regex') {
return [$parameter];
}
return static::ruleIsRegex($rule) ? [$parameter] : str_getcsv($parameter);
}
return str_getcsv($parameter);
/**
* Determine if the rule is a regular expression.
*
* @param string $rule
* @return bool
*/
protected static function ruleIsRegex($rule)
{
return in_array(strtolower($rule), ['regex', 'not_regex', 'notregex'], true);
}
/**
@@ -252,13 +303,41 @@ class ValidationRuleParser
*/
protected static function normalizeRule($rule)
{
switch ($rule) {
case 'Int':
return 'Integer';
case 'Bool':
return 'Boolean';
default:
return $rule;
}
return match ($rule) {
'Int' => 'Integer',
'Bool' => 'Boolean',
default => $rule,
};
}
/**
* Expand and conditional rules in the given array of rules.
*
* @param array $rules
* @param array $data
* @return array
*/
public static function filterConditionalRules($rules, array $data = [])
{
return collect($rules)->mapWithKeys(function ($attributeRules, $attribute) use ($data) {
if (! is_array($attributeRules) &&
! $attributeRules instanceof ConditionalRules) {
return [$attribute => $attributeRules];
}
if ($attributeRules instanceof ConditionalRules) {
return [$attribute => $attributeRules->passes($data)
? array_filter($attributeRules->rules($data))
: array_filter($attributeRules->defaultRules($data)), ];
}
return [$attribute => collect($attributeRules)->map(function ($rule) use ($data) {
if (! $rule instanceof ConditionalRules) {
return [$rule];
}
return $rule->passes($data) ? $rule->rules($data) : $rule->defaultRules($data);
})->filter()->flatten(1)->values()->all()];
})->all();
}
}

View File

@@ -2,17 +2,13 @@
namespace Illuminate\Validation;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Support\ServiceProvider;
class ValidationServiceProvider extends ServiceProvider
class ValidationServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
@@ -21,7 +17,7 @@ class ValidationServiceProvider extends ServiceProvider
public function register()
{
$this->registerPresenceVerifier();
$this->registerUncompromisedVerifier();
$this->registerValidationFactory();
}
@@ -58,6 +54,18 @@ class ValidationServiceProvider extends ServiceProvider
});
}
/**
* Register the uncompromised password verifier.
*
* @return void
*/
protected function registerUncompromisedVerifier()
{
$this->app->singleton(UncompromisedVerifier::class, function ($app) {
return new NotPwnedVerifier($app[HttpFactory::class]);
});
}
/**
* Get the services provided by the provider.
*

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,17 @@
}
],
"require": {
"php": ">=5.6.4",
"illuminate/container": "5.4.*",
"illuminate/contracts": "5.4.*",
"illuminate/support": "5.4.*",
"illuminate/translation": "5.4.*",
"symfony/http-foundation": "~3.2"
"php": "^8.0.2",
"ext-json": "*",
"egulias/email-validator": "^3.2.1",
"illuminate/collections": "^9.0",
"illuminate/container": "^9.0",
"illuminate/contracts": "^9.0",
"illuminate/macroable": "^9.0",
"illuminate/support": "^9.0",
"illuminate/translation": "^9.0",
"symfony/http-foundation": "^6.0",
"symfony/mime": "^6.0"
},
"autoload": {
"psr-4": {
@@ -28,11 +33,12 @@
},
"extra": {
"branch-alias": {
"dev-master": "5.4-dev"
"dev-master": "9.x-dev"
}
},
"suggest": {
"illuminate/database": "Required to use the database presence verifier (5.4.*)."
"ext-bcmath": "Required to use the multiple_of validation rule.",
"illuminate/database": "Required to use the database presence verifier (^9.0)."
},
"config": {
"sort-packages": true