diff --git a/core/lib/Drupal/Core/TypedData/Plugin/Validation/Constraint/TypedDataAwareConstraint.php b/core/lib/Drupal/Core/TypedData/Plugin/Validation/Constraint/TypedDataAwareConstraint.php new file mode 100644 index 0000000..977760c --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Plugin/Validation/Constraint/TypedDataAwareConstraint.php @@ -0,0 +1,47 @@ +context->currentTypedData = $constraint->data; + + $this->context + ->getValidator() + ->inContext($this->context) + ->validate($value, $constraint->constraints); + + unset($this->context->currentTypedData); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index 3216d21..98261ac 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -138,7 +138,21 @@ public function getConstraints() { */ public function validate() { // @todo: Add the typed data manager as proper dependency. - return \Drupal::typedDataManager()->getValidator()->validate($this); + $manager = \Drupal::typedDataManager(); + $data_canonical = $manager + ->getCanonicalRepresentation($this); + + if (!(is_object($data_canonical) && $data_canonical instanceof TypedDataInterface)) { + // Wrap the constraints to make them aware of the typed data object. + $constraint = $manager->getValidationConstraintManager()->create('TypedDataAware', [ + 'data' => $this, + 'constraints' => $this->getConstraints() + ]); + return $manager->getValidator()->validate($data_canonical, [$constraint]); + } + else { + return $manager->getValidator()->validate($data_canonical); + } } /** diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php old mode 100644 new mode 100755 index fb75edc..3581eee --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -335,7 +335,7 @@ public function getValidator() { ->setMetadataFactory(new MetadataFactory($this)) ->setTranslator(new DrupalTranslator()) ->setConstraintValidatorFactory(new ConstraintValidatorFactory($this->classResolver)) - ->setApiVersion(Validation::API_VERSION_2_4) + ->setApiVersion(Validation::API_VERSION_2_5) ->getValidator(); } return $this->validator; diff --git a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php deleted file mode 100644 index 6fe5255..0000000 --- a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php +++ /dev/null @@ -1,115 +0,0 @@ -typedData = $typed_data; - $this->name = $name; - $this->factory = $factory; - $this->typedDataManager = $typed_data_manager; - } - - /** - * Implements MetadataInterface::accept(). - */ - public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) { - - // @todo: Do we have to care about groups? Symfony class metadata has - // $propagatedGroup. - - $visitor->visit($this, $this->typedDataManager->getCanonicalRepresentation($typed_data), $group, $propertyPath); - } - - /** - * Implements MetadataInterface::findConstraints(). - */ - public function findConstraints($group) { - return $this->typedData->getConstraints(); - } - - /** - * Returns the name of the property. - * - * @return string The property name. - */ - public function getPropertyName() { - return $this->name; - } - - /** - * Extracts the value of the property from the given container. - * - * @param mixed $container The container to extract the property value from. - * - * @return mixed The value of the property. - */ - public function getPropertyValue($container) { - return $this->typedDataManager->getCanonicalRepresentation($this->typedData); - } - - /** - * Returns the typed data object. - * - * @return \Drupal\Core\TypedData\TypedDataInterface - * The typed data object. - */ - public function getTypedData() { - return $this->typedData; - } -} diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php new file mode 100755 index 0000000..4b4d662 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php @@ -0,0 +1,83 @@ +typedData = $typed_data; + $this->typedDataManager = $typed_data_manager; + } + + /** + * {@inheritdoc} + */ + public function getTypedData() { + return $this->typedData; + } + + /** + * {@inheritdoc} + */ + public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) { + throw new BadMethodCallException('Not supported.'); + } + + /** + * {@inheritdoc} + */ + public function findConstraints($group) { + return $this->getConstraints(); + } + + /** + * {@inheritdoc} + */ + public function getConstraints() { + return $this->typedData->getConstraints(); + } + + /** + * {@inheritdoc} + */ + public function getTraversalStrategy() { + return TraversalStrategy::NONE; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php old mode 100644 new mode 100755 index fcd0557..8827ed9 --- a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php +++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php @@ -11,7 +11,7 @@ use Drupal\Core\TypedData\ListInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedDataManager; -use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; /** * Typed data implementation of the validator MetadataFactoryInterface. @@ -40,23 +40,24 @@ public function __construct(TypedDataManager $typed_data_manager) { * * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data * Some typed data object containing the value to validate. - * @param $name - * (optional) The name of the property to get metadata for. Leave empty, if - * the data is the root of the typed data tree. */ - public function getMetadataFor($typed_data, $name = '') { + public function getMetadataFor($typed_data) { if (!$typed_data instanceof TypedDataInterface) { throw new \InvalidArgumentException('The passed value must be a typed data object.'); } - $is_container = $typed_data instanceof ComplexDataInterface || $typed_data instanceof ListInterface; - $class = '\Drupal\Core\TypedData\Validation\\' . ($is_container ? 'PropertyContainerMetadata' : 'Metadata'); - return new $class($typed_data, $name, $this, $this->typedDataManager); + $class = '\Drupal\Core\TypedData\Validation\PropertyMetadata'; + if ($typed_data instanceof ComplexDataInterface || $typed_data instanceof ListInterface) { + // When validation is started on a container, we need its class metadata. + $class = '\Drupal\Core\TypedData\Validation\PropertyContainerClassMetadata'; + } + return new $class($typed_data, $this->typedDataManager); } /** - * Implements MetadataFactoryInterface::hasMetadataFor(). + * {@inheritdoc} */ public function hasMetadataFor($value) { return $value instanceof TypedDataInterface; } + } diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerClassMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerClassMetadata.php new file mode 100644 index 0000000..14e0061 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerClassMetadata.php @@ -0,0 +1,111 @@ +typedData instanceof ListInterface) { + // @todo: When https://www.drupal.org/node/2164601 gets committed, + // simplify this to return the list indexes based on the list count. + return array_keys(iterator_to_array($this->typedData)); + } + elseif ($this->typedData instanceof ComplexDataInterface) { + return array_keys($this->typedData->getProperties()); + } + else { + throw new \LogicException("This class should be only used for property containers."); + } + } + + /** + * {@inheritdoc} + */ + public function hasPropertyMetadata($property_name) { + if ($this->typedData instanceof ComplexDataInterface) { + return (bool) $this->typedData->getDataDefinition()->getPropertyDefinition($property_name); + } + elseif ($this->typedData instanceof ListInterface) { + try { + $item = $this->typedData->get($property_name); + return TRUE; + } + catch (\InvalidArgumentException $exception) { + return FALSE; + } + } + else { + throw new \LogicException("This class should be only used for property containers."); + } + } + + /** + * {@inheritdoc} + */ + public function getPropertyMetadata($property_name) { + $property = $this->typedData->get($property_name); + $class = '\Drupal\Core\TypedData\Validation\PropertyMetadata'; + + if ($property instanceof ComplexDataInterface || $property instanceof ListInterface) { + $class = '\Drupal\Core\TypedData\Validation\PropertyContainerPropertyMetadata'; + } + return [ + new $class($property, $this->typedDataManager) + ]; + } + + /** + * {@inheritdoc} + */ + public function getCascadingStrategy() { + return CascadingStrategy::TRUE; + } + + /** + * {@inheritdoc} + */ + public function getClassName() { + return get_class($this->typedData); + } + + /** + * {@inheritdoc} + */ + public function getGroupSequence() { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function hasGroupSequence() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isGroupSequenceProvider() { + return FALSE; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php deleted file mode 100644 index 80d3320..0000000 --- a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php +++ /dev/null @@ -1,72 +0,0 @@ -isEmpty()) { - $data = NULL; - } - else { - $data = $this->typedDataManager->getCanonicalRepresentation($typed_data); - } - $visitor->visit($this, $data, $group, $propertyPath); - $pathPrefix = isset($propertyPath) && $propertyPath !== '' ? $propertyPath . '.' : ''; - - // Only continue validating if the data is not empty. - if ($data) { - foreach ($typed_data as $name => $data) { - $metadata = $this->factory->getMetadataFor($data, $name); - $metadata->accept($visitor, $data, $group, $pathPrefix . $name); - } - } - } - - /** - * Implements PropertyMetadataContainerInterface::hasPropertyMetadata(). - */ - public function hasPropertyMetadata($property_name) { - try { - $exists = (bool)$this->getPropertyMetadata($property_name); - } - catch (\LogicException $e) { - $exists = FALSE; - } - return $exists; - } - - /** - * Implements PropertyMetadataContainerInterface::getPropertyMetadata(). - */ - public function getPropertyMetadata($property_name) { - if ($this->typedData instanceof ListInterface) { - return array(new Metadata($this->typedData[$property_name], $property_name, $this->factory, $this->typedDataManager)); - } - elseif ($this->typedData instanceof ComplexDataInterface) { - return array(new Metadata($this->typedData->get($property_name), $property_name, $this->factory, $this->typedDataManager)); - } - else { - throw new \LogicException("There are no known properties."); - } - } -} diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerPropertyMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerPropertyMetadata.php new file mode 100644 index 0000000..40d5c12 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerPropertyMetadata.php @@ -0,0 +1,66 @@ +typedData->isEmpty()) { + return NULL; + } + else { + return $this->typedDataManager->getCanonicalRepresentation($this->typedData); + } + } + + /** + * {@inheritdoc} + */ + public function getPropertyName() { + return $this->typedData->getName(); + } + + /** + * {@inheritdoc} + */ + public function getClassName() { + return get_class($this->typedData); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyMetadata.php new file mode 100755 index 0000000..94ceb91 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/PropertyMetadata.php @@ -0,0 +1,46 @@ +typedData); + } + + /** + * {@inheritdoc} + */ + public function getCascadingStrategy() { + return CascadingStrategy::NONE; + } + + /** + * {@inheritdoc} + */ + public function getPropertyValue($container) { + return $this->typedDataManager->getCanonicalRepresentation($this->typedData); + } + + /** + * {@inheritdoc} + */ + public function getPropertyName() { + return $this->typedData->getName(); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php b/core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php new file mode 100644 index 0000000..061133b --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php @@ -0,0 +1,56 @@ +context; + /** @var \Symfony\Component\Validator\Context\ExecutionContextInterface $context */ + $metadata = $context->getMetadata(); + if ($metadata instanceof TypedDataMetadataInterface) { + return $metadata->getTypedData(); + } + else { + $root = $context->getRoot(); + if ($root instanceof TypedDataInterface) { + // Apply the property path again from the root to fetch the validated + // data property as typed data. + $data = $root; + foreach (explode('.', $context->getPropertyPath()) as $part) { + $data = $data->get($part); + } + return $data; + } + // In cases where the validation root data has an unwrapped canonical + // representation the typed data object is passed through on the context + // by TypedDataAwareConstraintValidator. + elseif (isset($context->currentTypedData)) { + return $context->currentTypedData; + } + else { + throw new \LogicException("The constraints should have been wrapped in TypedDataAwareConstraintValidator."); + } + } + } + +} \ No newline at end of file diff --git a/core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadataInterface.php b/core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadataInterface.php new file mode 100644 index 0000000..3f7d0ae --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadataInterface.php @@ -0,0 +1,25 @@ +currentUser = $current_user; + } /** * {@inheritdoc} */ public function validate($value, Constraint $constraint) { - $typed_data = $this->context->getMetadata()->getTypedData(); - + $typed_data = $this->getTypedData(); if ($typed_data instanceof OptionsProviderInterface) { - $account = \Drupal::currentUser(); - $allowed_values = $typed_data->getSettableValues($account); + $allowed_values = $typed_data->getSettableValues($this->currentUser); $constraint->choices = $allowed_values; // If the data is complex, we have to validate its main property. if ($typed_data instanceof ComplexDataInterface) { - $name = $typed_data->getDataDefinition()->getMainPropertyName(); + $name = $value->getDataDefinition()->getMainPropertyName(); if (!isset($name)) { throw new \LogicException('Cannot validate allowed values for complex data without a main property.'); } @@ -49,4 +70,11 @@ public function validate($value, Constraint $constraint) { parent::validate($value, $constraint); } + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('current_user')); + } + } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php old mode 100644 new mode 100755 index add46eb..917b8a1 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php @@ -9,6 +9,9 @@ use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\ListInterface; +use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait; +use Drupal\Core\TypedData\Validation\TypedDataPropertyValidationEnvelope; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -18,6 +21,8 @@ */ class ComplexDataConstraintValidator extends ConstraintValidator { + use TypedDataAwareValidatorTrait; + /** * {@inheritdoc} */ @@ -28,25 +33,35 @@ public function validate($value, Constraint $constraint) { // If un-wrapped data has been passed, fetch the typed data object first. if (!$value instanceof TypedDataInterface) { - $value = $this->context->getMetadata()->getTypedData(); + $value = $this->getTypedData(); } if (!$value instanceof ComplexDataInterface) { throw new UnexpectedTypeException($value, 'ComplexData'); } - $group = $this->context->getGroup(); - foreach ($constraint->properties as $name => $constraints) { $property = $value->get($name); + + // See TypedDataAwareValidatorTrait. + // @todo: Find a better solution upstream and remove this. + $this->context->currentTypedData = $property; + $is_container = $property instanceof ComplexDataInterface || $property instanceof ListInterface; if (!$is_container) { + // @todo: Use canonical representation. $property = $property->getValue(); } elseif ($property->isEmpty()) { - // @see \Drupal\Core\TypedData\Validation\PropertyContainerMetadata::accept(); + // @see \Drupal\Core\TypedData\Validation\ClassMetadata::accept(); $property = NULL; } - $this->context->validateValue($property, $constraints, $name, $group); + + $this->context + ->getValidator() + ->inContext($this->context) + ->validate($property, $constraints); + + unset($this->context->currentTypedData); } } } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php index d1bdc0f..d873820 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php @@ -15,6 +15,7 @@ use Drupal\Core\TypedData\Type\IntegerInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\Type\UriInterface; +use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -23,6 +24,8 @@ */ class PrimitiveTypeConstraintValidator extends ConstraintValidator { + use TypedDataAwareValidatorTrait; + /** * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate(). */ @@ -32,7 +35,7 @@ public function validate($value, Constraint $constraint) { return; } - $typed_data = $this->context->getMetadata()->getTypedData(); + $typed_data = $this->getTypedData(); $valid = TRUE; if ($typed_data instanceof BinaryInterface && !is_resource($value)) { $valid = FALSE; diff --git a/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php b/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php index 76bc17d..6e9fa3b 100644 --- a/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php +++ b/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php @@ -8,6 +8,7 @@ namespace Drupal\user\Plugin\Validation\Constraint; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\ExecutionContextInterface; @@ -58,7 +59,7 @@ public function validatedBy() { public function validate($items, Constraint $constraint) { /** @var \Drupal\Core\Field\FieldItemListInterface $items */ /** @var \Drupal\user\UserInterface $account */ - $account = $this->context->getMetadata()->getTypedData()->getEntity(); + $account = $items->getEntity(); $existing_value = NULL; if ($account->id()) { $account_unchanged = \Drupal::entityManager()