diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index 3216d21..c81f7c3 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -138,7 +138,14 @@ public function getConstraints() { */ public function validate() { // @todo: Add the typed data manager as proper dependency. - return \Drupal::typedDataManager()->getValidator()->validate($this); + if ($this instanceof PrimitiveInterface) { + // Primitive data is always validated unwrapped. For that to work we have + // to manually pass on the constraints. + return \Drupal::typedDataManager()->getValidator()->validate($this->getValue(), $this->getConstraints()); + } + else { + return \Drupal::typedDataManager()->getValidator()->validate($this); + } } /** 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..0e1f415 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php @@ -0,0 +1,87 @@ +typedData = $typed_data; + $this->typedDataManager = $typed_data_manager; + } + + /** + * Returns the typed data object. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The typed data object. + */ + 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/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php index efc6906..16782fb 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php @@ -7,30 +7,48 @@ namespace Drupal\Core\Validation\Plugin\Validation\Constraint; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\OptionsProviderInterface; use Drupal\Core\TypedData\ComplexDataInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\ChoiceValidator; /** * Validates the AllowedValues constraint. */ -class AllowedValuesConstraintValidator extends ChoiceValidator { +class AllowedValuesConstraintValidator extends ChoiceValidator implements ContainerInjectionInterface { + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Constructs a new AllowedValuesConstraintValidator. + * + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + */ + public function __construct(AccountInterface $current_user) { + $this->currentUser = $current_user; + } /** * {@inheritdoc} */ public function validate($value, Constraint $constraint) { $typed_data = $this->context->getMetadata()->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 +67,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..d223957 --- 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,8 @@ use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\ListInterface; +use Drupal\Core\TypedData\Validation\TypedDataPropertyValidationEnvelope; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -34,19 +36,22 @@ public function validate($value, Constraint $constraint) { throw new UnexpectedTypeException($value, 'ComplexData'); } - $group = $this->context->getGroup(); - foreach ($constraint->properties as $name => $constraints) { $property = $value->get($name); $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); } } }