diff --git a/extensions/package-manager/src/AllValidatorRules.php b/extensions/package-manager/src/AllValidatorRules.php deleted file mode 100644 index e2d8757e75..0000000000 --- a/extensions/package-manager/src/AllValidatorRules.php +++ /dev/null @@ -1,31 +0,0 @@ -getRules(); - - $validator = $this->validator->make($attributes, $rules, $this->getMessages()); - - foreach ($this->configuration as $callable) { - $callable($this, $validator); - } - - return $validator; - } -} diff --git a/extensions/package-manager/src/ConfigureAuthValidator.php b/extensions/package-manager/src/ConfigureAuthValidator.php index 14a9a03ed4..528a12c403 100644 --- a/extensions/package-manager/src/ConfigureAuthValidator.php +++ b/extensions/package-manager/src/ConfigureAuthValidator.php @@ -13,7 +13,7 @@ class ConfigureAuthValidator extends AbstractValidator { - use AllValidatorRules; + protected bool $validateMissingKeys = true; protected array $rules = [ 'github-oauth' => ['sometimes', 'array'], diff --git a/extensions/package-manager/src/ConfigureComposerValidator.php b/extensions/package-manager/src/ConfigureComposerValidator.php index 6292e5539c..a58a172d08 100644 --- a/extensions/package-manager/src/ConfigureComposerValidator.php +++ b/extensions/package-manager/src/ConfigureComposerValidator.php @@ -13,7 +13,7 @@ class ConfigureComposerValidator extends AbstractValidator { - use AllValidatorRules; + protected bool $validateMissingKeys = true; protected array $rules = [ 'minimum-stability' => ['sometimes', 'in:stable,RC,beta,alpha,dev'], diff --git a/framework/core/src/Foundation/AbstractValidator.php b/framework/core/src/Foundation/AbstractValidator.php index 788b85a7c0..0e4048827a 100644 --- a/framework/core/src/Foundation/AbstractValidator.php +++ b/framework/core/src/Foundation/AbstractValidator.php @@ -10,7 +10,8 @@ namespace Flarum\Foundation; use Flarum\Locale\TranslatorInterface; -use Illuminate\Support\Arr; +use Illuminate\Support\Collection; +use Illuminate\Support\Str; use Illuminate\Validation\Factory; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Validator; @@ -22,13 +23,12 @@ abstract class AbstractValidator */ protected array $configuration = []; - /** - * @var array - */ protected array $rules = []; protected ?Validator $laravelValidator = null; + protected bool $validateMissingKeys = false; + public function __construct( protected Factory $validator, protected TranslatorInterface $translator @@ -54,6 +54,16 @@ public function assertValid(array $attributes): void } } + /** + * Whether to validate missing keys or to only validate provided data keys. + */ + public function validateMissingKeys(bool $validateMissingKeys = true): static + { + $this->validateMissingKeys = $validateMissingKeys; + + return $this; + } + public function prepare(array $attributes): static { $this->laravelValidator ??= $this->makeValidator($attributes); @@ -71,6 +81,27 @@ protected function getRules(): array return $this->rules; } + protected function getActiveRules(array $attributes): array + { + $rules = $this->getRules(); + + if ($this->validateMissingKeys) { + return $rules; + } + + return Collection::make($rules) + ->filter(function (mixed $rule, string $key) use ($attributes) { + foreach ($attributes as $attributeKey => $attributeValue) { + if ($attributeKey === $key || Str::startsWith($key, $attributeKey.'.')) { + return true; + } + } + + return false; + }) + ->all(); + } + protected function getMessages(): array { return []; @@ -78,7 +109,7 @@ protected function getMessages(): array protected function makeValidator(array $attributes): Validator { - $rules = Arr::only($this->getRules(), array_keys($attributes)); + $rules = $this->getActiveRules($attributes); $validator = $this->validator->make($attributes, $rules, $this->getMessages()); diff --git a/framework/core/tests/integration/extenders/ValidatorTest.php b/framework/core/tests/integration/extenders/ValidatorTest.php index eafaf4dae0..01bf94e6cd 100644 --- a/framework/core/tests/integration/extenders/ValidatorTest.php +++ b/framework/core/tests/integration/extenders/ValidatorTest.php @@ -18,7 +18,7 @@ class ValidatorTest extends TestCase { - private function extendToRequireLongPassword() + private function extendToRequireLongPassword(): void { $this->extend((new Extend\Validator(CustomUserValidator::class))->configure(function ($flarumValidator, $validator) { $validator->setRules([ @@ -30,7 +30,7 @@ private function extendToRequireLongPassword() })); } - private function extendToRequireLongPasswordViaInvokableClass() + private function extendToRequireLongPasswordViaInvokableClass(): void { $this->extend((new Extend\Validator(CustomUserValidator::class))->configure(CustomValidatorClass::class)); } @@ -74,6 +74,51 @@ public function custom_validation_rule_doesnt_affect_other_validators() // If we have gotten this far, no validation exception has been thrown, so the test is successful. $this->assertTrue(true); } + + #[Test] + public function validator_only_validates_provided_data_by_default() + { + /** @var SecondCustomValidator $validator */ + $validator = $this->app()->getContainer()->make(SecondCustomValidator::class); + + $validator->assertValid([ + 'my_key' => 'value', + ]); + + // If we have gotten this far, no validation exception has been thrown, so the test is successful. + $this->assertTrue(true); + } + + #[Test] + public function validator_includes_path_based_rules() + { + /** @var SecondCustomValidator $validator */ + $validator = $this->app()->getContainer()->make(SecondCustomValidator::class); + + $this->expectException(ValidationException::class); + + $validator->assertValid([ + 'my_key' => 'value', + 'my_third_key' => [null], + ]); + } + + #[Test] + public function validator_can_validate_missing_keys() + { + /** @var SecondCustomValidator $validator */ + $validator = $this->app()->getContainer()->make(SecondCustomValidator::class)->validateMissingKeys(); + + $this->expectException(ValidationException::class); + + $validator->validateMissingKeys()->assertValid([ + 'my_key' => 'value', + 'my_third_key' => [ + '2021-01-01 00:00:00', + '2021-01-02 00:00:00' + ] + ]); + } } class CustomValidatorClass @@ -142,3 +187,13 @@ class CustomValidator extends AbstractValidator 'name_plural' => ['required'] ]; } + +class SecondCustomValidator extends AbstractValidator +{ + protected array $rules = [ + 'my_key' => ['required'], + 'my_other_key' => ['required'], + 'my_third_key' => ['required', 'array'], + 'my_third_key.*' => ['required', 'date'] + ]; +}