From 07e12d378ec0875cdcfe4272d120d56e2312f713 Mon Sep 17 00:00:00 2001 From: Owen Andrews Date: Tue, 18 Jun 2024 01:22:09 +1000 Subject: [PATCH] [11.x] Fix nested rules custom attribute names (#51805) * Find wildcard custom attributes * Remove whitespace * Improve comment * Add test for wildcard attribute names in nested arrays * Move attribute matching logic * Add test for translated attribute names * Fix StyleCI issues * More StyleCI * More StyleCI * formatting --------- Co-authored-by: Taylor Otwell --- .../Validation/Concerns/FormatsMessages.php | 40 ++++++++++++++++--- tests/Validation/ValidationValidatorTest.php | 34 ++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/FormatsMessages.php b/src/Illuminate/Validation/Concerns/FormatsMessages.php index 39b68f9c61ce..c03e2636a51e 100644 --- a/src/Illuminate/Validation/Concerns/FormatsMessages.php +++ b/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -273,15 +273,15 @@ public function getDisplayableAttribute($attribute) // The developer may dynamically specify the array of custom attributes on this // validator instance. If the attribute exists in this array it is used over // the other ways of pulling the attribute name for this given attributes. - if (isset($this->customAttributes[$name])) { - return $this->customAttributes[$name]; + if ($inlineAttribute = $this->getAttributeFromLocalArray($name)) { + return $inlineAttribute; } // We allow for a developer to specify language lines for any attribute in this // application, which allows flexibility for displaying a unique displayable // version of the attribute name instead of the name used in an HTTP POST. - if ($line = $this->getAttributeFromTranslations($name)) { - return $line; + if ($translatedAttribute = $this->getAttributeFromTranslations($name)) { + return $translatedAttribute; } } @@ -301,11 +301,39 @@ public function getDisplayableAttribute($attribute) * Get the given attribute from the attribute translations. * * @param string $name - * @return string + * @return string|null */ protected function getAttributeFromTranslations($name) { - return Arr::get($this->translator->get('validation.attributes'), $name); + return $this->getAttributeFromLocalArray( + $name, Arr::dot((array) $this->translator->get('validation.attributes')) + ); + } + + /** + * Get the custom name for an attribute if it exists in the given array. + * + * @param string $attribute + * @param array|null $source + * @return string|null + */ + protected function getAttributeFromLocalArray($attribute, $source = null) + { + $source = $source ?: $this->customAttributes; + + if (isset($source[$attribute])) { + return $source[$attribute]; + } + + foreach (array_keys($source) as $sourceKey) { + if (str_contains($sourceKey, '*')) { + $pattern = str_replace('\*', '([^.]*)', preg_quote($sourceKey, '#')); + + if (preg_match('#^'.$pattern.'\z#u', $attribute) === 1) { + return $source[$sourceKey]; + } + } + } } /** diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 7defa977f82c..af2c22a60c51 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -23,6 +23,7 @@ use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; use Illuminate\Validation\DatabasePresenceVerifierInterface; +use Illuminate\Validation\Rule as ValidationRule; use Illuminate\Validation\Rules\Exists; use Illuminate\Validation\Rules\ProhibitedIf; use Illuminate\Validation\Rules\Unique; @@ -558,6 +559,39 @@ public function testAttributeNamesAreReplacedInArrays() $this->assertSame('First name is required!', $v->messages()->first('names.0')); } + public function testInlineAttributeNamesAreReplacedInArraysFromNestedRules() + { + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines(['validation.required' => ':attribute is required!'], 'en'); + $v = new Validator($trans, [ + 'users' => [['name' => 'Taylor']], + ], [ + 'users.*' => ValidationRule::forEach(fn () => ['id' => 'required']), + ], [], [ + 'users.*.id' => 'User ID', + ]); + $this->assertFalse($v->passes()); + $v->messages()->setFormat(':message'); + $this->assertSame('User ID is required!', $v->messages()->first('users.0.id')); + } + + public function testTranslatedAttributeNamesAreReplacedInArraysFromNestedRules() + { + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines([ + 'validation.required' => ':attribute is required!', + 'validation.attributes' => ['users.*.id' => 'User ID'], + ], 'en'); + $v = new Validator($trans, [ + 'users' => [['name' => 'Taylor']], + ], [ + 'users.*' => ValidationRule::forEach(fn () => ['id' => 'required']), + ]); + $this->assertFalse($v->passes()); + $v->messages()->setFormat(':message'); + $this->assertSame('User ID is required!', $v->messages()->first('users.0.id')); + } + public function testInputIsReplaced() { $trans = $this->getIlluminateArrayTranslator();