diff --git a/Classes/Validation/Validator/ConditionalValidator.php b/Classes/Validation/Validator/ConditionalValidator.php new file mode 100644 index 0000000..348cf85 --- /dev/null +++ b/Classes/Validation/Validator/ConditionalValidator.php @@ -0,0 +1,152 @@ + [[], 'List of entries with "condition" (eel expression) and "validators" (list of validators).', 'array', true], + 'fallbackValidators' => [[], 'List of validators that is used if no condition matched.', 'array'], + 'validationGroups' => [['Default'], 'Same as "Validation Groups" of Flow Framework', 'array'], + ]; + + /** + * @inheritDoc + * @throws InvalidValidationConfigurationException + * @throws NeosEelException + * @throws NoSuchValidatorException + */ + protected function isValid($value): void + { + $validatorConfigs = []; + + $hasMatch = false; + + foreach ($this->options['conditions'] as $condition) { + if ($this->eelEvaluator instanceof DependencyProxy) { + $this->eelEvaluator->_activateDependency(); + } + + if (!Utility::evaluateEelExpression($condition['condition'], $this->eelEvaluator, ['this' => $value])) { + continue; + } + + $hasMatch = true; + + foreach ($condition['validators'] as $validator => $options) { + if ($options === null) { + continue; + } + + $validatorConfigs[] = [ + 'validator' => $validator, + 'options' => $options, + ]; + } + } + + if (!$hasMatch) { + foreach ($this->options['fallbackValidators'] as $validator => $options) { + if ($options === null) { + continue; + } + + $validatorConfigs[] = [ + 'validator' => $validator, + 'options' => $options, + ]; + } + } + + foreach ($validatorConfigs as $validatorConfig) { + if (!$this->doesValidationGroupsMatch($validatorConfig)) { + continue; + } + + $this->handleValidationGroups($validatorConfig); + + $validator = $this->validatorResolver->createValidator( + $validatorConfig['validator'], + $validatorConfig['options'] + ); + + if ($validator === null) { + throw new InvalidValidationConfigurationException( + \sprintf( + 'Validator "%s" could not be resolved. Check your Validation.yaml', + $validatorConfig['validator'] + ), + 1402326139 + ); + } + + $this->getResult()->merge($validator->validate($value)); + } + } + + /** + * Check whether at least one configured group does match, if any is configured. + * + * @param array $validatorConfig + * @return bool + */ + protected function doesValidationGroupsMatch(array $validatorConfig): bool + { + return !isset($validatorConfig['options']['validationGroups']) + || \array_intersect( + $validatorConfig['options']['validationGroups'], + $this->options['validationGroups'] + ) !== []; + } + + /** + * Add validation groups for recursion if necessary. + * + * @param array $validatorConfig + */ + protected function handleValidationGroups(array &$validatorConfig): void + { + if (\in_array($validatorConfig['validator'], ['DigiComp.SettingValidator:Settings', 'DigiComp.SettingValidator:Conditional', 'DigiComp.SettingValidator:Properties', 'Neos.Flow:Collection'])) { + $validatorConfig['options']['validationGroups'] = $this->options['validationGroups']; + } elseif (isset($validatorConfig['options']['validationGroups'])) { + unset($validatorConfig['options']['validationGroups']); + } + } +} diff --git a/Classes/Validation/Validator/PropertiesValidator.php b/Classes/Validation/Validator/PropertiesValidator.php new file mode 100644 index 0000000..7fc8979 --- /dev/null +++ b/Classes/Validation/Validator/PropertiesValidator.php @@ -0,0 +1,119 @@ + [[], 'List of validators for properties. ', 'array', true], + 'validationGroups' => [['Default'], 'Same as "Validation Groups" of Flow Framework', 'array'], + ]; + + /** + * @inheritDoc + * @throws InvalidValidationConfigurationException + * @throws NoSuchValidatorException + */ + protected function isValid($value): void + { + $validatorConfigs = []; + + foreach ($this->options['validatorsForProperties'] as $property => $validators) { + foreach ($validators as $validator => $options) { + if ($options === null) { + continue; + } + + $validatorConfigs[] = [ + 'validator' => $validator, + 'options' => $options, + 'property' => $property, + ]; + } + } + + foreach ($validatorConfigs as $validatorConfig) { + if (!$this->doesValidationGroupsMatch($validatorConfig)) { + continue; + } + + $this->handleValidationGroups($validatorConfig); + + $validator = $this->validatorResolver->createValidator( + $validatorConfig['validator'], + $validatorConfig['options'] + ); + + if ($validator === null) { + throw new InvalidValidationConfigurationException( + \sprintf( + 'Validator "%s" could not be resolved. Check your Validation.yaml', + $validatorConfig['validator'] + ), + 1402326139 + ); + } + + $this->getResult()->forProperty($validatorConfig['property'])->merge( + $validator->validate(ObjectAccess::getPropertyPath($value, $validatorConfig['property'])) + ); + } + } + + /** + * Check whether at least one configured group does match, if any is configured. + * + * @param array $validatorConfig + * @return bool + */ + protected function doesValidationGroupsMatch(array $validatorConfig): bool + { + return !isset($validatorConfig['options']['validationGroups']) + || \array_intersect( + $validatorConfig['options']['validationGroups'], + $this->options['validationGroups'] + ) !== []; + } + + /** + * Add validation groups for recursion if necessary. + * + * @param array $validatorConfig + */ + protected function handleValidationGroups(array &$validatorConfig): void + { + if (\in_array($validatorConfig['validator'], ['DigiComp.SettingValidator:Settings', 'DigiComp.SettingValidator:Conditional', 'DigiComp.SettingValidator:Properties', 'Neos.Flow:Collection'])) { + $validatorConfig['options']['validationGroups'] = $this->options['validationGroups']; + } elseif (isset($validatorConfig['options']['validationGroups'])) { + unset($validatorConfig['options']['validationGroups']); + } + } +} diff --git a/Classes/Validation/Validator/SettingsValidator.php b/Classes/Validation/Validator/SettingsValidator.php index 46715de..d83129e 100644 --- a/Classes/Validation/Validator/SettingsValidator.php +++ b/Classes/Validation/Validator/SettingsValidator.php @@ -20,7 +20,6 @@ use Neos\Flow\Validation\Exception\InvalidValidationOptionsException; use Neos\Flow\Validation\Exception\NoSuchValidatorException; use Neos\Flow\Validation\Validator\AbstractValidator; use Neos\Flow\Validation\ValidatorResolver; -use Neos\Utility\ObjectAccess; use Neos\Utility\TypeHandling; /** @@ -58,6 +57,7 @@ class SettingsValidator extends AbstractValidator { $validations = $this->validations; + // TODO: feature idea - we could extend the library to automatically be part of the base conjunction validator $name = $this->options['name'] !== '' ? $this->options['name'] : TypeHandling::getTypeForValue($value); if (!isset($validations[$name])) { throw new InvalidValidationOptionsException( @@ -66,7 +66,44 @@ class SettingsValidator extends AbstractValidator ); } - foreach ($this->getConfigForValidation($validations[$name]) as $validatorConfig) { + // @deprecated - converts old "self" to new structure + if (isset($validations[$name]['self'])) { + foreach ($validations[$name]['self'] as $validator => $options) { + if (isset($validations[$name][$validator])) { + throw new \RuntimeException('The validator "' . $validator . '" is already defined on parent level.', 1725000364); + } + $validations[$name][$validator] = $options; + } + + unset($validations[$name]['self']); + } + + // @deprecated - converts old "properties" to new structure + if (isset($validations[$name]['properties'])) { + if (isset($validations[$name]['DigiComp.SettingValidator:Properties'])) { + throw new \RuntimeException('The validator "DigiComp.SettingValidator:Properties" is already defined on parent level.', 1725000396); + } + $validations[$name]['DigiComp.SettingValidator:Properties'] = [ + 'validatorsForProperties' => $validations[$name]['properties'], + ]; + + unset($validations[$name]['properties']); + } + + $validatorConfigs = []; + + foreach ($validations[$name] as $validator => $options) { + if ($options === null) { + continue; + } + + $validatorConfigs[] = [ + 'validator' => $validator, + 'options' => $options, + ]; + } + + foreach ($validatorConfigs as $validatorConfig) { if (!$this->doesValidationGroupsMatch($validatorConfig)) { continue; } @@ -88,54 +125,10 @@ class SettingsValidator extends AbstractValidator ); } - if (isset($validatorConfig['property'])) { - $this->getResult()->forProperty($validatorConfig['property'])->merge( - $validator->validate(ObjectAccess::getPropertyPath($value, $validatorConfig['property'])) - ); - } else { - $this->getResult()->merge($validator->validate($value)); - } + $this->getResult()->merge($validator->validate($value)); } } - /** - * @param array $validation - * @return array - */ - protected function getConfigForValidation(array $validation): array - { - $config = []; - - if (isset($validation['self'])) { - foreach ($validation['self'] as $validator => $options) { - if ($options === null) { - continue; - } - $config[] = [ - 'validator' => $validator, - 'options' => $options, - ]; - } - } - - if (isset($validation['properties'])) { - foreach ($validation['properties'] as $property => $propertyValidation) { - foreach ($propertyValidation as $validator => $options) { - if ($options === null) { - continue; - } - $config[] = [ - 'property' => $property, - 'validator' => $validator, - 'options' => $options, - ]; - } - } - } - - return $config; - } - /** * Check whether at least one configured group does match, if any is configured. * @@ -158,7 +151,7 @@ class SettingsValidator extends AbstractValidator */ protected function handleValidationGroups(array &$validatorConfig): void { - if ($validatorConfig['validator'] === 'DigiComp.SettingValidator:Settings') { + if (\in_array($validatorConfig['validator'], ['DigiComp.SettingValidator:Settings', 'DigiComp.SettingValidator:Conditional', 'DigiComp.SettingValidator:Properties', 'Neos.Flow:Collection'])) { $validatorConfig['options']['validationGroups'] = $this->options['validationGroups']; } elseif (isset($validatorConfig['options']['validationGroups'])) { unset($validatorConfig['options']['validationGroups']); diff --git a/Tests/Functional/SettingsValidatorTest.php b/Tests/Functional/SettingsValidatorTest.php index 54b4764..412f6c1 100644 --- a/Tests/Functional/SettingsValidatorTest.php +++ b/Tests/Functional/SettingsValidatorTest.php @@ -44,6 +44,7 @@ class SettingsValidatorTest extends FunctionalTestCase self::assertTrue($result->hasErrors()); self::assertCount(1, $result->getFlattenedErrors()); + self::assertCount(1, $result->forProperty('shouldBeTrueAndValidatedByAnnotation')->getErrors()); } /**