diff --git a/src/Illuminate/Validation/Rules/Email.php b/src/Illuminate/Validation/Rules/Email.php index bb803e8a15cc..08ae4326e8ee 100644 --- a/src/Illuminate/Validation/Rules/Email.php +++ b/src/Illuminate/Validation/Rules/Email.php @@ -2,11 +2,13 @@ namespace Illuminate\Validation\Rules; +use Closure; 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\Str; use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; @@ -21,6 +23,8 @@ class Email implements Rule, DataAwareRule, ValidatorAwareRule public bool $nativeValidationWithUnicodeAllowed = false; public bool $rfcCompliant = false; public bool $strictRfcCompliant = false; + private array $allowedDomains = []; + private array $disallowedDomains = []; /** * The validator performing the validation. @@ -162,6 +166,32 @@ public function withNativeValidation(bool $allowUnicode = false) return $this; } + /** + * Ensure the domain of the email address matches one of the given patterns. + * + * @param array $domains + * @return $this + */ + public function domainIs($domains) + { + $this->allowedDomains = $domains; + + return $this; + } + + /** + * Ensure the domain of the email address does not match any of the given patterns. + * + * @param array $domains + * @return $this + */ + public function domainIsNot($domains) + { + $this->disallowedDomains = $domains; + + return $this; + } + /** * Specify additional validation rules that should be merged with the default rules during validation. * @@ -245,6 +275,22 @@ protected function buildValidationRules() $rules = ['email']; } + if ($this->allowedDomains) { + $rules[] = function (string $attribute, mixed $value, Closure $fail): void { + if (! $this->domainMatchesPattern($value, $this->allowedDomains)) { + $fail('The :attribute must be a valid email address.'); + } + }; + } + + if ($this->disallowedDomains) { + $rules[] = function (string $attribute, mixed $value, Closure $fail): void { + if ($this->domainMatchesPattern($value, $this->disallowedDomains)) { + $fail('The :attribute must be a valid email address.'); + } + }; + } + return array_merge(array_filter($rules), $this->customRules); } @@ -283,4 +329,18 @@ public function setData($data) return $this; } + + /** + * Determine whether the email address matches one of the given domains. + * + * @param string $value + * @param string[] $domains + * @return bool + */ + protected function domainMatchesPattern($value, $domains) + { + $domain = explode('@', $value)[1]; + + return Str::of($domain)->is($domains, true); + } } diff --git a/tests/Validation/ValidationEmailRuleTest.php b/tests/Validation/ValidationEmailRuleTest.php index cbff32a5edb1..d3de20244745 100644 --- a/tests/Validation/ValidationEmailRuleTest.php +++ b/tests/Validation/ValidationEmailRuleTest.php @@ -876,6 +876,70 @@ public function testValidationMessages() ); } + public function testDomainIs() + { + $this->passes( + (new Email())->domainIs(['example.com', 'test.com']), + 'passes@example.com', + ); + + $this->passes( + (new Email())->domainIs(['*']), + 'passes@example.com', + ); + + $this->passes( + (new Email())->domainIs(['*example.com']), + 'passes@example.com', + ); + + $this->passes( + (new Email())->domainIs(['*ex*le.com']), + 'passes@subdomain.example.com', + ); + + $this->passes( + (new Email())->domainIs(['*example.com']), + 'passes@subdomain.example.com', + ); + + $this->passes( + (new Email())->domainIs(['example*']), + 'passes@example.com', + ); + + $this->fails( + (new Email())->domainIs(['test.com']), + 'fails@example.com', + ['The '.self::ATTRIBUTE_REPLACED.' must be a valid email address.'] + ); + } + + public function testDomainIsNot() + { + $this->passes( + (new Email())->domainIsNot(['test.com', 'example.org']), + 'passes@example.com', + ); + + $this->passes( + (new Email())->domainIsNot(['*test.com']), + 'passes@example.com', + ); + + $this->fails( + (new Email())->domainIsNot(['example.com']), + 'passes@example.com', + ['The '.self::ATTRIBUTE_REPLACED.' must be a valid email address.'] + ); + + $this->fails( + (new Email())->domainIsNot(['example*']), + 'passes@example.com', + ['The '.self::ATTRIBUTE_REPLACED.' must be a valid email address.'] + ); + } + protected function setUp(): void { $container = Container::getInstance();